πŸ—‘οΈ chore: remove placeholder article files β€” real article lives on blog.swadhin.cv

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