Skip to content
← All projects
Web Active ★ Featured Sep 2026

JustAPI

A Python web framework with a Rust core: Python runs your handlers while Rust owns networking, TLS, routing, validation and serialization. 766k req/s hello-world, a p99 of 0.48 ms, and a 12 MB footprint.

Source ★ 6
JustAPI
rustpythonweb-frameworkhttp2http3asyncperformancepypiopen-source
hello-world
766k req/s
json echo
782k req/s
p99 hello-world
0.48 ms
route lookup
51 ns / 500 routes
native crud
181k req/s
resident memory
12 MB

JustAPI is a Python web framework where the framework itself is Rust. You write Python handlers; Rust owns the socket, TLS, HTTP/1.1, HTTP/2 and HTTP/3, routing, middleware, validation and JSON serialization. It installs with a single pip install, needs no uvicorn or gunicorn, and starts in milliseconds.

The constraint that shaped the project was written down early: if a feature can be implemented in Rust, it must be. Python is reserved for the code that is different for every application.

A request's path

Kernel epoll or io_uring into a tokio connection manager, TLS through rustls, HTTP parsing through hyper, routing through a matchit radix trie, then the middleware chain, then a zero-copy PyO3 boundary into your handler, then Rust serialization back to the socket. Routes marked native with a schema skip Python entirely: Rust validates the body and writes the response.

Performance, with the caveats

Hello-world at 100 concurrent connections over 30 seconds reaches 766k req/s, and 782k req/s on JSON echo, with a p99 of 0.48 ms and 12 MB resident memory — against 314k for Granian, 39k for Robyn and 36k for FastAPI with Uvicorn. Route lookup averages 51 ns on a 500-route table. Rust-native CRUD reaches 181k SELECT requests per second, about 125 times the same database fixture under FastAPI, and native async database awaits run on the database's tokio runtime with the GIL released.

Those numbers apply to the native fast path, which requires a schema. Handlers without one fall back to the Python dispatch path at roughly 60k req/s, and on GIL-locked CPython that path is capped around 100-120k req/s regardless of framework — that is the GIL rather than the framework. Free-threaded CPython 3.14t removes the ceiling and runs CPU-bound handlers about 12.4 times faster.

Native operations

Async database queries await on the database's own runtime, so the event loop never blocks. Server-sent events are generated in Rust with zero Python per event. There is a Rust-native CRUD path, adaptive batching for bursty workloads, and a multi-worker prefork mode with load-based auto-scaling.

Security and protocols

JWT authentication with per-route roles and scopes, GCRA rate limiting with a Redis backend, configurable CORS, JSON Schema validation compiled in Rust with a Pydantic v2 bridge, and security headers. On top of REST with OpenAPI 3.1 there is WebSocket, server-sent events, gRPC through Tonic, and GraphQL through async-graphql.

Migrating from FastAPI

The decorator API and Pydantic integration mirror FastAPI, so most applications migrate by changing the import and the app class, and the built-in transport removes the separate server process.

Quality gates

Every pull request is gated on tests, clippy with warnings denied, rustfmt, memory-safety sanitizers when the core is touched, an appended benchmark entry when performance is touched, and a recorded architecture decision when the design changes. The project carries an append-only benchmark ledger, an ADR log, and a 141-page documentation site.

The hard part

  • The GIL caps the Python path. On GIL-locked CPython the Python dispatch path tops out around 100-120k req/s no matter the framework. Three experiments proved no coroutine driver, bridge or multi-loop design beats asyncio's own stepping, so the win has to come from moving work into Rust or from free-threaded Python.
  • The fast path needs a schema. Routes without a schema fall back to the Python handler path, so the headline numbers only apply where validation is declared, and that has to be stated plainly.
  • Light async handlers are loop-bound. Handlers that only sleep or echo are capped by the asyncio loop, which is why the native operations (database, SSE, CRUD) matter more than micro-benchmarks.
  • Memory safety in a Rust core. The suite runs AddressSanitizer, Miri on all unsafe code and six fuzz targets, and cargo-deny audits the dependency supply chain.

Outcome

  • Published on PyPI, with wheels for CPython 3.11 through 3.14 including the free-threaded 3.14t build.
  • 766k req/s hello-world and a 0.48 ms p99 on the documented benchmark hardware, with the methodology published.
  • A 141-page documentation site, a FastAPI migration guide, an append-only benchmark ledger, and an ADR log from ADR-001 to ADR-093.
  • HTTP/3 over QUIC, which no other Python framework ships.
  • A CLI that scaffolds a full CRUD project across seven database backends and four API styles.

What I'd do differently

  • State the caveats next to the numbers. The non-native path, the GIL ceiling and the loop-bound async cases are documented on the same page as the headline results, because a benchmark without its limits is marketing.
  • Move the work, do not chase the loop. Three dead ends proved the coroutine dispatch was already optimal, which redirected the effort to native operations and free-threaded Python.
  • Gate quality in CI, not in review. Sanitizers, fuzzing, cargo-deny and the ADR requirement each catch a class of problem before it reaches main.
  • Do not ship unverified claims. The inference phases are excluded from this release because there was no real GPU run to stand behind.

Architecture & screenshots