πŸ“ docs: engineering article (ARTICLE.md) + Hacker News version (HACKERNEWS.md)

swadhinbiswas committed 2 months ago
61b6040
1 parent a225470
commit 61b6040
4 files changed
+550 -1
β–Ό.gitignore
+0-1
128
128
/split_server.py
129
129
/restore.py
130
130
/generate_commits.sh
131
-/demo_app.py
132
131
/protection.json
133
132
134
133
# Olist dataset (large, download separately)
135
134
β–ΌARTICLE.md
+425-0
1
+# JustAPI: a Python web framework with a Rust core β€” the story, the numbers, and what I actually learned
2
+
3
+> **Read this first.** I built JustAPI β€” I'm Swadhin Biswas β€” with agentic AI as
4
+> a guide: AI helped me design, implement, debug, and document a large part of
5
+> this project. I am not claiming this is the best framework in the world, and
6
+> I am not claiming every number here was measured on your hardware. I am
7
+> describing what I built, why I built it, what I verified, and β€” just as
8
+> important β€” what I tried that did not work. The documentation site is large,
9
+> much of it written with AI assistance, and not everything in it is
10
+> independently verified. If you find a claim that seems wrong, it probably
11
+> is. Verify it, and open an issue.
12
+>
13
+> **What this article is:** a technical write-up. **What it is not:** marketing.
14
+
15
+---
16
+
17
+## 1. What JustAPI is
18
+
19
+JustAPI is a Python web framework where the web server, the router, the TLS
20
+termination, the request validation, the JSON serialization, and the database
21
+access all run in Rust. Your Python code handles the application logic. That is
22
+the entire premise, and it is the one sentence that explains every design
23
+decision in the project.
24
+
25
+```python
26
+from justapi import JustAPIApp
27
+
28
+app = JustAPIApp()
29
+
30
+@app.get("/")
31
+def hello():
32
+ return {"Hello": "World"}
33
+
34
+app.run() # a real server, not a dev server
35
+```
36
+
37
+That is a complete program. It gives you HTTP/1.1 and HTTP/2, TLS, routing with
38
+path and query parameters, JSON serialization, automatic OpenAPI docs at
39
+`/docs`, and a server that does not need uvicorn or gunicorn. All of it runs in
40
+Rust. The Python process just runs your handler.
41
+
42
+The package is on PyPI (`pip install justapi`), it works on Linux, macOS, and
43
+Windows, and there are wheels for the free-threaded build of CPython 3.14t.
44
+
45
+---
46
+
47
+## 2. Why I built it (the story)
48
+
49
+The starting point was a simple observation that most Python web frameworks
50
+treat as a given: **the framework runs in Python.** FastAPI runs on Starlette,
51
+Starlette runs on uvicorn, and uvicorn is a Python event loop with a lot of
52
+asyncio machinery between the socket and your handler. Robyn and Granian moved
53
+the *server* to Rust, but the request still crosses into Python for routing,
54
+validation, and serialization.
55
+
56
+I asked a different question: what if almost nothing crossed into Python?
57
+What if the framework β€” the thing that does the same work on every request β€”
58
+lived entirely in Rust, and Python only ran the code that is different for each
59
+application?
60
+
61
+That question shaped a project with an unusual constraint, which I wrote down
62
+early and enforced throughout: **if a feature can be implemented in Rust, it
63
+must be implemented in Rust.** Not "can be implemented faster in Rust" β€” *can
64
+be implemented in Rust*. Python in the framework is reserved for the parts that
65
+have to be Python: the glue between Rust values and your handlers.
66
+
67
+I did not do this alone. I worked with agentic AI throughout β€” using it as an
68
+engineering guide. I made the architecture decisions; the AI helped me turn
69
+them into working code, find the bugs I could not see, write the tests, and
70
+keep the project honest when I wanted to skip the boring parts. There are
71
+parts of this codebase I understand completely, parts I understand well enough
72
+to maintain, and parts the AI wrote that I reviewed and tested but would be
73
+lying if I said I could reproduce from memory. I am not hiding that; it is
74
+part of how this project exists.
75
+
76
+The project then went through the standard engineering motions, in order:
77
+
78
+1. A minimal server: tokio + hyper, a PyO3 boundary, one route. Measured
79
+ 766k req/s on the hello-world fixture.
80
+2. A native router (matchit radix trie, 51ns average lookup on 500 routes).
81
+3. A memory pipeline (per-request arena, buffer pools).
82
+4. Middleware in Rust: CORS, security headers, JWT, rate limiting.
83
+5. Serialization in Rust (serde_json, optional simd-json).
84
+6. TLS with rustls β€” no OpenSSL dependency.
85
+7. A native API surface, replacing the ASGI shim.
86
+8. Database access via sqlx: SQLite, PostgreSQL, MySQL, DuckDB.
87
+9. WebSockets, SSE, background tasks, a scheduler, a plugin system.
88
+10. Operational tooling: OpenTelemetry, Prometheus metrics, health checks,
89
+ circuit breakers.
90
+
91
+None of this was fast in the sense of "one weekend." It was fast in the sense
92
+of "the numbers are in the repo, the tests pass, and the failures are
93
+documented."
94
+
95
+The release history tells part of the story on its own:
96
+
97
+| Version | What changed |
98
+|---|---|
99
+| 2.0.8 | Multi-threaded tokio server, free-threaded CPython support, async handler fix (14Γ— async throughput) |
100
+| 2.0.9 | Native async DB awaits, Rust-native SSE, type stubs, tokenless CI publishing, first PyPI release |
101
+| 2.0.10 | The CLI shipped in the wheel (it was a stub before) |
102
+
103
+---
104
+
105
+## 3. The architecture
106
+
107
+The request path is a pipeline, and almost every stage is Rust:
108
+
109
+```
110
+Kernel (epoll/io_uring)
111
+ β†’ tokio connection manager (multi-threaded)
112
+ β†’ TLS (rustls)
113
+ β†’ HTTP parse (hyper)
114
+ β†’ Router (matchit, ~51ns lookup)
115
+ β†’ Middleware chain (auth, CORS, rate-limit, compression)
116
+ β†’ Python boundary (zero-copy PyO3, GIL pool)
117
+ β†’ Your Python handler
118
+ β†’ Rust serializer (serde_json / simd-json)
119
+ β†’ Response write β†’ socket
120
+```
121
+
122
+The crate layout mirrors the pipeline:
123
+
124
+```
125
+justapi/
126
+β”œβ”€β”€ crates/
127
+β”‚ β”œβ”€β”€ justapi-core/ # networking, routing, middleware, serialization, DB pool
128
+β”‚ β”œβ”€β”€ justapi-py/ # PyO3 bindings + the Python package
129
+β”‚ β”œβ”€β”€ justapi-cli/ # the `justapi-cli` binary (serve, create, check, profile)
130
+β”‚ └── justapi-bench/ # benchmark harness
131
+β”œβ”€β”€ python/justapi/ # pip-installable package (maturin-built)
132
+β”œβ”€β”€ BENCHMARKS.md # append-only performance ledger
133
+β”œβ”€β”€ DECISIONS.md # ADR log (ADR-001 through ADR-093)
134
+└── PLAN.md # roadmap with an honesty status on every phase
135
+```
136
+
137
+The dependency graph is intentionally small:
138
+
139
+```
140
+justapi-cli β†’ justapi-core
141
+justapi-py β†’ justapi-core
142
+justapi-bench β†’ justapi-core
143
+```
144
+
145
+## 4. The numbers (verified, with the hardware caveat)
146
+
147
+All numbers below are from `BENCHMARKS.md`, measured on a single machine with
148
+100 concurrent connections and 30-second runs. **Your machine will differ.**
149
+What matters is the *ratio* on the same hardware, not the absolute numbers.
150
+
151
+| Framework | Hello-world req/s | JSON echo req/s | p99 hello-world | RSS |
152
+|---|---|---|---|---|
153
+| JustAPI (native fast path) | 766k | 782k | 0.48 ms | 12 MB |
154
+| Granian (ASGI) | 314k | 145k | 0.74 ms | 37 MB |
155
+| Robyn | 39k | β€” | β€” | β€” |
156
+| FastAPI + Uvicorn | 36k | 33k | 24.63 ms | 29 MB |
157
+
158
+The "native fast path" needs two things: a route registered with `native=True`
159
+and a `Schema`. In that configuration, Rust validates the body and writes the
160
+response β€” Python never runs. Without a schema, you get the Python handler
161
+path, which is GIL-bound at roughly 60–120k req/s on this hardware, same as any
162
+other Python framework.
163
+
164
+Other verified numbers:
165
+
166
+- Route lookup: 51ns average on a 500-route table.
167
+- Native CRUD SELECT: 181k req/s β€” 125Γ— FastAPI on the same SQLite fixture.
168
+- Native async DB awaits: 320 RPS vs 6 RPS for the blocking path on a slow
169
+ query (a 2M-row recursive CTE) β€” 53Γ—.
170
+- Multi-worker prefork (`justapi serve --workers 4`): 1.88Γ— throughput
171
+ (99.7k RPS vs 53k).
172
+- Free-threaded CPython 3.14t, CPU-bound Python handler: 12.4Γ— vs
173
+ GIL-locked β€” the GIL ceiling disappears.
174
+
175
+**What I do not claim:** there is no "beats every framework on every
176
+workload" claim here. Light async handlers (no I/O, just `asyncio.sleep`) sit
177
+at 4–12k req/s β€” the asyncio loop floor β€” and I measured that Granian's
178
+direct loop dispatch is faster on that specific shape. I do not hide it; it is
179
+in the benchmarks and in the article below.
180
+
181
+## 5. The async story: what I tried, and what the measurements said
182
+
183
+This is the most important section in this article, because it is where I
184
+spent the most effort proving myself wrong.
185
+
186
+The initial async path was bad. An async handler doing `await
187
+asyncio.sleep(0.001)` served about **808 RPS** β€” a hard ceiling, not
188
+contention. The single GIL worker was blocking on each coroutine's full
189
+duration, serializing every async request. Fixing that (ADR-083) took async
190
+throughput from ~800 to **11,758 RPS** β€” 14Γ— β€” by making completion
191
+callback-driven instead of blocking.
192
+
193
+Then I got ambitious. The plan was: "make the awaits Rust-native." If a
194
+Python coroutine's `await` could complete on tokio instead of on the asyncio
195
+loop, per-await overhead would drop, and I would beat Granian on async.
196
+
197
+I built four approaches and measured every one:
198
+
199
+| Approach | Per-await overhead | Verdict |
200
+|---|---|---|
201
+| `future_into_py` (Rust future wrapped as an asyncio.Future) | 2Γ— slower than `asyncio.sleep` | The wrapper + cross-runtime wakeup costs more than the loop's own timer |
202
+| `into_future` (coroutine β†’ Rust future) | tied at best | Requires a running asyncio loop for task-locals; does not decouple |
203
+| Hand-rolled driver, GIL attach per await | ~1125 Β΅s/await | Thread-state setup dominates |
204
+| Hand-rolled driver, persistent thread | ~3.4 Β΅s/await solo | **86Γ— worse concurrent** (thread per coroutine) |
205
+| asyncio loop (status quo) | ~45 Β΅s/await | The only mechanism that scales |
206
+
207
+The multiplexing driver β€” one persistent Rust thread stepping many coroutines,
208
+the design that should have worked β€” measured **16 Β΅s/await vs asyncio's
209
+0.56 Β΅s/await on pure stepping** (ADR-091). The absolute floor, a bare
210
+`await noop()` in asyncio, is **0.039 Β΅s**.
211
+
212
+The conclusion, written into the ADR log: *the Python coroutine `send()`
213
+stepping is the irreducible floor, and asyncio already runs it at the minimum
214
+possible rate. Any Rust wrapper around `send()` adds overhead; it cannot
215
+subtract it.* A multi-loop dispatch experiment (ADR-092) also came back
216
+neutral-to-worse: the GIL worker is the single dispatch point, so extra loops
217
+only added GIL contention.
218
+
219
+I stopped. That is the part of engineering nobody writes articles about: the
220
+work you delete. The async path stays on asyncio, which is the right answer,
221
+and I went back to making the *operations* native instead of the coroutine.
222
+
223
+## 6. Native operations: where the real win is
224
+
225
+Since I cannot make Python coroutines faster, I made the framework operations
226
+they await faster β€” by taking Python out of them.
227
+
228
+**Native async DB awaits (`query_async`).** A normal async handler doing a
229
+database query would block the asyncio loop thread for the whole query. One
230
+slow Postgres query froze every other async handler on the server.
231
+`await app.db.query_async(...)` runs the SQL on the DB's own multi-threaded
232
+tokio runtime with the GIL released. The loop is never blocked. Measured: 53Γ—
233
+faster than the blocking path on slow queries, and it is the only path that is
234
+*safe* for real databases with network latency.
235
+
236
+**Rust-native SSE (`sse_native`).** A stream of events is generated entirely
237
+in Rust β€” tokio timers, an mpsc channel, the HTTP layer drains it. Zero Python
238
+per event. 100k events stream instantly.
239
+
240
+**`@native_async`.** A marker that routes async handlers to the fastest
241
+dispatch path, and on free-threaded CPython enables true parallel dispatch
242
+across workers.
243
+
244
+The pattern is consistent: Python declares the operation, Rust executes it,
245
+and nothing in the middle is Python.
246
+
247
+## 7. Bugs I found in my own framework (and fixed)
248
+
249
+The honest part of any framework article is the bug list. Some highlights:
250
+
251
+**Write-path collapse.** Concurrent Python-handler writes dropped to ~3 RPS at
252
+11 connections (from ~150 at 10). The request-scoped auto-transaction was
253
+double-acquiring pool connections β€” 2N for N writes. Removed. (ADR-080)
254
+
255
+**GIL pool not fork-safe.** A forked child inherited the parent's initialized
256
+GIL pool whose worker threads did not exist, so every Python request hung and
257
+returned 504s. The pool now tracks its PID and rebuilds after fork. This was
258
+also the root cause of a long-standing flaky test. (ADR-081)
259
+
260
+**Pydantic models as `body_schema` were broken.** `validate_body` treated a
261
+Pydantic model class as a plain callable and invoked
262
+`BaseModel(body_dict)`, which raised `TypeError`. This broke the single most
263
+important FastAPI migration path. Fixed by validating through
264
+`model_json_schema()` in the Rust engine. It shipped fixed in 2.0.9.
265
+
266
+**`app.run()` required an address.** The README quickstart showed
267
+`app.run()` with no arguments, which crashed with a `TypeError`. Now defaults
268
+to `127.0.0.1:8000`.
269
+
270
+**The CLI in the wheel was a stub.** The PyPI package's `justapi` command
271
+printed a help message and did nothing else. It shipped fixed in 2.0.10
272
+(serve, create, check, openapi β€” and it delegates to the Rust CLI when
273
+installed).
274
+
275
+**Unix-only APIs were not cfg-gated.** `UnixListener`-based functions failed
276
+to compile on Windows. Gated with `#[cfg(unix)]`.
277
+
278
+Each of these is in the changelog with the commit that fixed it. None of them
279
+was found by reading the code. They were found by running things, by
280
+benchmarking, and by trying to use the framework the way a user would.
281
+
282
+## 8. What JustAPI ships that no other Python framework ships
283
+
284
+- **HTTP/3 (QUIC)** β€” feature-gated, serves Python handlers over QUIC through
285
+ the full native pipeline. As far as I know, the only Python framework with
286
+ a working HTTP/3 transport.
287
+- **Free-threaded CPython wheels** (3.14t) with auto-detected parallel
288
+ dispatch.
289
+- **Rust-native SSE** where the stream never touches Python.
290
+- **Multi-worker prefork with auto-scaling** built into the CLI.
291
+- **OIDC trusted publishing** β€” the release pipeline uses zero tokens; a tag
292
+ push builds 9 platform wheels and publishes them.
293
+
294
+## 9. The honest caveats (read this before using it)
295
+
296
+1. **The documentation site is large and much of it is AI-written.** I
297
+ generated a lot of the docs with AI assistance. I verified the code and
298
+ the benchmarks; I did not independently verify every sentence in every
299
+ doc page. If you rely on a doc page, test the behavior yourself. Some doc
300
+ pages were already found to describe APIs that did not exist, and I fixed
301
+ them as I found them. There are probably more.
302
+
303
+2. **The LLM/inference features are unverified.** Phases 41–52 of the roadmap
304
+ (inference engine, KV cache, continuous batching, OpenAI-compatible server)
305
+ are implemented but were never run with real GPU weights. The only honest
306
+ status is "implemented, not verified." They are not part of the release
307
+ story, and I do not make performance claims about them. (ADR-067)
308
+
309
+3. **The absolute numbers are from one machine.** Ratios matter; absolutes
310
+ depend on hardware, OS, and load generator.
311
+
312
+4. **The roadmap contains 17 "implemented but unverified" phases.** I mark
313
+ them 🟑 in PLAN.md. That is not a typo. It means: code exists, tests pass,
314
+ and the real-world claim is not proven.
315
+
316
+5. **Async light handlers are loop-bound.** If your handler is
317
+ `await asyncio.sleep(0.001); return {}`, JustAPI is not faster than
318
+ Granian. I measured it. Use the native operations for DB/SSE/IO work.
319
+
320
+6. **There is no 1M RPS claim here.** The roadmap phase title once said
321
+ "1M RPS tuning." I never measured 1M RPS. I changed the status to
322
+ reflect that. (The phase overclaims; I say so in PLAN.md.)
323
+
324
+7. **First impressions matter, and mine were rough.** The first published
325
+ CLI was a stub. `app.run()` crashed without an argument. The docs had
326
+ fabricated API references. All fixed, but if you are evaluating the
327
+ project, the version history is honest about the rough edges.
328
+
329
+## 10. How to actually use it
330
+
331
+```bash
332
+pip install justapi==2.0.10
333
+```
334
+
335
+```python
336
+# main.py
337
+from justapi import JustAPIApp, native_async
338
+from pydantic import BaseModel
339
+
340
+app = JustAPIApp()
341
+app.set_database("sqlite://app.db")
342
+
343
+class Item(BaseModel):
344
+ name: str
345
+
346
+@app.get("/")
347
+def root():
348
+ return {"Hello": "World"}
349
+
350
+@app.get("/items/{item_id}")
351
+async def get_item(item_id: int):
352
+ return await app.db.query_async(
353
+ "SELECT * FROM items WHERE id = ?", [item_id])
354
+
355
+@app.post("/items", body_schema=Item)
356
+def create(request):
357
+ data = request.json()
358
+ return app.db.execute(
359
+ "INSERT INTO items (name) VALUES (?)", [data["name"]])
360
+
361
+# Rust-native SSE: no Python per event
362
+app.sse_native("/events", count=1000, interval_ms=0)
363
+
364
+app.run() # defaults to 127.0.0.1:8000
365
+```
366
+
367
+Then:
368
+
369
+```bash
370
+python main.py
371
+justapi check main.py # validate routes without serving
372
+justapi serve 0.0.0.0:8080 # or use the CLI
373
+```
374
+
375
+For the high-performance Rust CLI:
376
+
377
+```bash
378
+cargo install justapi-cli # provides serve --workers N --reload, create, profile
379
+```
380
+
381
+## 11. Where to verify everything in this article
382
+
383
+| Claim | Source |
384
+|---|---|
385
+| Benchmarks, methodology, hardware | `BENCHMARKS.md` in the repo |
386
+| Architecture decisions (all 93) | `DECISIONS.md` β€” ADR-001 through ADR-093 |
387
+| Roadmap status incl. unverified phases | `PLAN.md` (the honesty key is at the top) |
388
+| Async experiments and their failure | ADR-090, ADR-091, ADR-092 |
389
+| Native async DB awaits | ADR-093 |
390
+| Bug fixes | `CHANGELOG.md` (2.0.8 β†’ 2.0.10) |
391
+| Code | https://github.com/swadhinbiswas/JustAPI |
392
+| Docs | https://justapi.pages.dev |
393
+
394
+---
395
+
396
+## 12. The thing I actually learned
397
+
398
+The takeaway of this project is not "Rust is faster than Python." Everyone
399
+knows that. The takeaway is more specific and more useful:
400
+
401
+**The performance of a Python web framework is decided by how much Python
402
+runs per request β€” and the parts you cannot remove, you should not try to make
403
+faster by rewriting them in Rust.**
404
+
405
+I tried to make asyncio faster by moving it to Rust. The measurements said
406
+no. I made the operations *around* asyncio native instead, and that worked:
407
+53Γ— on slow database queries, 125Γ— on native CRUD, zero-Python SSE. The
408
+framework's job is to decide which parts of a request are yours and which are
409
+the framework's β€” and then to make the framework's parts not run Python at
410
+all.
411
+
412
+That, and the discipline to write down the failures, is what JustAPI is.
413
+
414
+There is a second lesson, specific to how I built this: **agentic AI is a
415
+force multiplier, not a substitute for judgment.** The AI guided me through
416
+Rust lifetimes, PyO3 FFI, and tokio internals I had never touched before. It
417
+also wrote things that were wrong, and I only caught them because I
418
+benchmarked, tested, and read the output. Use the tool; verify the work.
419
+
420
+---
421
+
422
+*Written by Swadhin Biswas, maintainer of JustAPI, with agentic AI as a
423
+guide. Corrections welcome β€” open an issue at
424
+https://github.com/swadhinbiswas/JustAPI/issues. If a number here disagrees
425
+with your measurement, your measurement wins.*
0
426
β–ΌHACKERNEWS.md
+75-0
1
+Show HN: JustAPI β€” a Python web framework where the framework itself is Rust
2
+
3
+I spent months building a Python web framework where almost nothing in the
4
+request path is Python. Networking, TLS, routing, validation, serialization,
5
+database access β€” all Rust. Your handler is the only Python in the pipeline.
6
+
7
+Why: every Python framework treats "the framework runs in Python" as a given.
8
+FastAPI sits on Starlette on uvicorn, and even Rust-served frameworks like
9
+Granian cross into Python for routing and serialization. I asked what happens
10
+if the framework work never crosses into Python at all.
11
+
12
+The measured numbers (one machine, 100 concurrent connections, 30s runs β€”
13
+ratios matter more than absolutes):
14
+
15
+ hello-world: 766k req/s (native fast path) vs Granian 314k, FastAPI 36k
16
+ JSON echo: 782k req/s vs Granian 145k, FastAPI 33k
17
+ route lookup: 51ns on a 500-route table
18
+ native CRUD SELECT: 181k req/s β€” 125x FastAPI on the same SQLite fixture
19
+ native async DB: 53x faster than the blocking path on slow queries
20
+ memory: 12 MB RSS vs Granian 37 MB, FastAPI 29 MB
21
+
22
+The part I'm most proud of is what didn't work.
23
+
24
+I spent real effort trying to make asyncio faster by moving it to Rust β€”
25
+driving Python coroutines from tokio. I built four approaches and measured
26
+every one. The best result was 16 microseconds per await vs asyncio's 0.56
27
+microseconds on pure stepping. The absolute floor, a bare "await noop()" in
28
+asyncio, is 0.039 microseconds. Python's coroutine stepping is the irreducible
29
+cost, and asyncio already pays it at the minimum rate. Any Rust wrapper only
30
+adds overhead. So I stopped, documented it in the ADR log (090-092), and
31
+instead made the operations the coroutines await native β€” DB queries run on
32
+Rust's tokio runtime with the GIL released, SSE streams never touch Python.
33
+
34
+What you get that other Python frameworks don't:
35
+
36
+ - HTTP/3 (QUIC) through the full native pipeline (feature-gated)
37
+ - free-threaded CPython 3.14t wheels with auto-detected parallel dispatch
38
+ - Rust-native SSE β€” zero Python per event
39
+ - multi-worker prefork with auto-scaling in the CLI
40
+ - tokenless PyPI publishing (OIDC trusted publisher β€” tag push = release)
41
+
42
+Bugs I found in my own framework, because I benchmarked instead of assuming:
43
+ - concurrent Python writes collapsed to 3 RPS at 11 connections (a
44
+ double-acquire in request-scoped transactions)
45
+ - the GIL pool wasn't fork-safe β€” every request 504'd after a fork
46
+ - Pydantic models as body_schema crashed with a TypeError (broke the
47
+ FastAPI migration path)
48
+ - app.run() required an address, so the README's first example crashed
49
+ - the CLI shipped in the wheel was a stub; fixed in 2.0.10
50
+ - Unix-only code wasn't cfg-gated; Windows didn't compile
51
+
52
+The honest caveats, because you will find these if you try it:
53
+ - the docs are large and much of it is AI-written; not every page is
54
+ independently verified. If a doc claims something, test it.
55
+ - the LLM/inference features (OpenAI-compatible server, KV cache, etc.) are
56
+ implemented but never run with real GPU weights. No claims.
57
+ - light async handlers (just asyncio.sleep, no I/O) are 4-12k req/s β€” the
58
+ asyncio floor. Granian is faster on that shape. Measured, not hidden.
59
+ - there is no "beats everything" claim. There is a benchmark ledger and a
60
+ roadmap that marks 17 phases as "implemented, not verified."
61
+
62
+I built this with agentic AI as a guide β€” it helped write a lot of the code
63
+and docs, and it also wrote things that were wrong, which I only caught by
64
+benchmarking and testing everything myself. Treat the AI-written parts with
65
+suspicion.
66
+
67
+If you want the real write-up (with architecture diagram and the full async
68
+failure log): https://github.com/swadhinbiswas/JustAPI/blob/main/ARTICLE.md
69
+
70
+PyPI: https://pypi.org/project/justapi/
71
+Repo: https://github.com/swadhinbiswas/JustAPI
72
+Benchmark ledger: https://github.com/swadhinbiswas/JustAPI/blob/main/BENCHMARKS.md
73
+ADR log (all 93 decisions, including the failures): https://github.com/swadhinbiswas/JustAPI/blob/main/DECISIONS.md
74
+
75
+Swadhin Biswas
0
76
β–Όdemo_app.py
+50-0
1
+import asyncio
2
+from justapi import JustAPIApp
3
+from pydantic import BaseModel
4
+from typing import Optional
5
+
6
+# 1. Initialize the App
7
+app = JustAPIApp()
8
+
9
+# 2. Add some Middleware (Auth, CORS, Security)
10
+# (assuming middleware plugin system is configured)
11
+
12
+# 3. Request Validation Models
13
+class ItemRequest(BaseModel):
14
+ name: str
15
+ price: float
16
+ description: Optional[str] = None
17
+
18
+# 4. Standard Async Route (High Concurrency)
19
+@app.get("/async-demo")
20
+async def async_demo():
21
+ await asyncio.sleep(0.01) # Simulate DB or IO delay
22
+ return {"status": "success", "message": "Handled without blocking the Tokio loop!"}
23
+
24
+# 5. Native CPU-bound Route
25
+@app.get("/cpu-heavy")
26
+def cpu_heavy():
27
+ # Will be offloaded natively via spawn_blocking
28
+ total = sum(i * i for i in range(10000))
29
+ return {"total": total}
30
+
31
+# 6. JSON Validation & Serialization Route
32
+@app.post("/items")
33
+async def create_item(item: ItemRequest):
34
+ # 'item' is fully validated in Rust before Python executes!
35
+ return {"message": "Item created successfully", "data": item.model_dump()}
36
+
37
+# 7. gRPC Integration Mock Route (assuming defined protobuf)
38
+@app.get("/grpc-status")
39
+def grpc_status():
40
+ return {"grpc_port": 50051, "status": "running concurrently"}
41
+
42
+# 8. Start the Application
43
+if __name__ == "__main__":
44
+ print("πŸš€ Starting JustAPI Demo Application on http://127.0.0.1:8080")
45
+ print("Try the following endpoints:")
46
+ print(" - GET http://127.0.0.1:8080/async-demo")
47
+ print(" - GET http://127.0.0.1:8080/cpu-heavy")
48
+ print(" - POST http://127.0.0.1:8080/items (Body: {\"name\": \"Test\", \"price\": 99.99})")
49
+
50
+ app.run("127.0.0.1:8080")
0
51