FastAPI-compatible web framework with Zig HTTP core — 20x faster with Python 3.14 free-threading
Project description
TurboAPI
FastAPI-compatible Python framework. Zig HTTP core. 20x faster.
Drop-in replacement · Zig-native validation · Zero-copy responses · Free-threading · dhi models
Status · Quick Start · Benchmarks · Architecture · Migrate · Why Python? · Observability · Security
Status
Alpha software — read before using in production
TurboAPI works and has 275+ passing tests, but:
- No TLS — put nginx or Caddy in front for HTTPS
- No slow-loris protection — requires a reverse proxy with read timeouts
- No configurable max body size — hardcoded 16MB cap
- WebSocket support is in progress, not production-ready
- HTTP/2 is not yet implemented
- Free-threaded Python 3.14t is itself relatively new — some C extensions may not be thread-safe
See SECURITY.md for the full threat model and deployment recommendations.
| What works today | What's in progress |
|---|---|
| 150k req/s (22x FastAPI) on Apple Silicon | WebSocket support |
| FastAPI-compatible route decorators | HTTP/2 and TLS |
| Zig HTTP server with 24-thread pool + keep-alive | Cloudflare Workers WASM target |
| Zig-native JSON schema validation (dhi) | Fiber-based concurrency (via zag) |
| Zero-alloc response pipeline (stack buffers) | |
| Zig-native CORS (0% overhead, pre-rendered headers) | |
| Response caching for noargs handlers | |
| Static routes (pre-rendered at startup) | |
| Async handler support | |
| Full security stack (OAuth2, Bearer, API Key) | |
| Python 3.14t free-threaded support | |
| Native FFI handlers (C/Zig, no Python at all) | |
| Fuzz-tested HTTP parser, router, validator |
⚡ Quick Start
Requirements: Python 3.14+ free-threaded (3.14t), Zig 0.15+
Option 1: Docker (easiest)
git clone https://github.com/justrach/turboAPI.git
cd turboAPI
docker compose up
This builds Python 3.14t from source, compiles the Zig backend, and runs the example app. Hit http://localhost:8000 to verify.
Option 2: Local install
# Install free-threaded Python
uv python install 3.14t
# Install turboapi
pip install turboapi
# Or build from source (see below)
from turboapi import TurboAPI
from dhi import BaseModel
app = TurboAPI()
class Item(BaseModel):
name: str
price: float
quantity: int = 1
@app.get("/")
def hello():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
def get_item(item_id: int):
return {"item_id": item_id, "name": "Widget"}
@app.post("/items")
def create_item(item: Item):
return {"item": item.model_dump(), "created": True}
if __name__ == "__main__":
app.run()
python3.14t app.py
# 🚀 TurboNet-Zig server listening on 127.0.0.1:8000
The app also exposes an ASGI __call__ fallback — you can use uvicorn main:app to test your route definitions before building the native backend, but this is pure-Python and much slower. For production, always use app.run() with the compiled Zig backend.
Benchmarks
All numbers verified with correct, identical JSON responses. wrk -t4 -c100 -d10s, Python 3.14t free-threaded, Apple Silicon M3 Pro.
Throughput (req/s) — no middleware
─────────────────────────────────────────────────────────────
GET /health (static) ██████████████████████████████████████████████████ 149,340 <- pre-rendered, zero Python
GET /ping ████████████████████████████████████████████████ 150,000 <- cached after 1st call
(simple_sync_noargs) ██ 6,847 <- FastAPI
GET /items/{id} ████████████████████████████████████████████████ ~143,000 <- vectorcall (Zig arg assembly)
███ 8,666
POST /users (dhi) ████████████████████████████████████████ ~124,000 <- model_sync + pre-GIL validation
███ 8,200
─────────────────────────────────────────────────────────────
With CORSMiddleware stacked (all routes, all methods):
─────────────────────────────────────────────────────────────
GET /ping + CORS ████████████████████████████████████████████████ ~139,000 <- Zig-native CORS, ~0% overhead
GET /json + CORS ███████████████████████████████████████████████ ~138,000
POST /items + CORS ████████████████████████████████████████ ~124,000
─────────────────────────────────────────────────────────────
Avg latency (no middleware): 0.15ms FastAPI: 14.6ms (~22x)
Avg latency (with CORS): 0.16ms FastAPI: 14.6ms (~21x)
- Static routes:
app.static_route("GET", "/health", '{"status":"ok"}')-- response pre-rendered at startup, singlewriteAll - Response caching: noargs handlers cached after first Python call -- subsequent requests skip Python entirely
- Zero-arg GET:
PyObject_CallNoArgs-- no tuple/kwargs allocation - Parameterized GET:
PyObject_Vectorcallwith Zig-assembled positional args -- noparse_qs, no kwargs dict - POST (dhi model): Zig validates JSON schema before acquiring the GIL -- invalid bodies return
422without touching Python - CORS: Zig-native -- headers pre-rendered once at startup, injected via
memcpy. 0% overhead (was 24% with Python middleware). OPTIONS preflight handled in Zig.
⚙️ Architecture
Request lifecycle
Every HTTP request flows through the same pipeline. The key idea: Python only runs your business logic. Everything else — parsing, routing, validation, response writing — happens in Zig.
┌──────────────────────────────────────────────────────┐
│ Zig HTTP Core │
HTTP Request ──────►│ │
│ TCP accept ──► header parse ──► route match │
│ (24-thread pool) (8KB buf) (radix trie) │
│ │
│ Content-Length body read (dynamic alloc, 16MB cap) │
└────────────────────┬─────────────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌─────────────────────┐ ┌──────────────┐
│ Native FFI │ │ model_sync │ │ simple_sync │
│ (no Python) │ │ │ │ body_sync │
│ │ │ JSON parse in Zig │ │ │
│ C handler ───┤ │ dhi schema validate │ │ Acquire GIL │
│ direct call │ │ ▼ fail → 422 │ │ call handler│
│ (no GIL) │ │ ▼ pass → Zig builds │ │ zero-copy │
│ │ │ Python dict from │ │ write │
└──────┬────────┘ │ parsed JSON │ └──────┬───────┘
│ │ model(**data) │ │
│ │ handler(model) │ │
│ │ zero-copy write │ │
│ └──────────┬────────────┘ │
│ │ │
└────────────────────────┴──────────────────────┘
│
┌────▼─────┐
│ Response │
│ (keep- │
│ alive) │
└──────────┘
What "zero-copy" means
On the response path, Zig calls PyUnicode_AsUTF8() to get a pointer to the Python string's internal buffer, then calls write() directly on the socket. No memcpy, no temporary buffers, no heap allocation. The Python string stays alive because we hold a reference to it.
Handler classification
At startup, each route is analyzed once and assigned the lightest dispatch path:
| Handler type | What it skips | When used |
|---|---|---|
native_ffi |
Python entirely — no GIL, no interpreter | C/Zig shared library handlers |
simple_sync_noargs |
GIL lookup, tuple/kwargs alloc — uses PyObject_CallNoArgs |
Zero-param GET handlers |
model_sync |
json.loads — Zig parses JSON and builds Python dict |
POST with a dhi.BaseModel param |
simple_sync |
header parsing, body parsing, regex | GET handlers with path/query params |
body_sync |
header parsing, regex | POST without model params |
enhanced |
nothing — full Python dispatch | Depends(), middleware, complex types |
Zig-side JSON parsing (model_sync)
For model_sync routes, the JSON request body is parsed twice in Zig, zero times in Python:
- dhi validation —
dhi_validator.zigparses the JSON and validates field types, constraints (min_length,gt, etc.), nested objects, and unions. Invalid requests get a422without acquiring the GIL. - Python dict construction —
jsonValueToPyObject()inserver.zigrecursively converts the parsedstd.json.Valuetree into Python objects (PyDict,PyList,PyUnicode,PyLong,PyFloat,PyBool,Py_None). The resulting dict is passed to the handler asbody_dict.
The Python handler receives a pre-built dict and just does model_class(**data) — no json.loads, no parsing overhead.
🚀 Features
Drop-in FastAPI replacement
# Before
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
# After
from turboapi import TurboAPI as FastAPI, Depends, HTTPException
from dhi import BaseModel
Everything else stays the same. Routes, decorators, dependency injection, middleware — all compatible.
Zig-native validation via dhi
from dhi import BaseModel, Field
class CreateUser(BaseModel):
name: str = Field(min_length=1, max_length=100)
email: str
age: int = Field(gt=0, le=150)
@app.post("/users")
def create_user(user: CreateUser):
return {"created": True, "user": user.model_dump()}
Model schemas are extracted at startup and compiled into Zig. Invalid requests get rejected with a 422 before touching Python — no GIL acquired, no handler called. Valid requests are passed to your handler with a real model instance.
Async handlers
@app.get("/async")
async def async_handler():
data = await fetch_from_database()
return {"data": data}
Async handlers are automatically detected and awaited via asyncio.run().
Full security stack
from turboapi import Depends, HTTPException
from turboapi.security import OAuth2PasswordBearer, HTTPBearer, APIKeyHeader
oauth2 = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/protected")
def protected(token: str = Depends(oauth2)):
if token != "secret":
raise HTTPException(status_code=401, detail="Invalid token")
return {"user": "authenticated"}
OAuth2, HTTP Bearer/Basic, API Key (header/query/cookie) — all supported with correct status codes (401/403).
Native FFI handlers
Skip Python entirely for maximum throughput:
# Register a handler from a compiled shared library
app.add_native_route("GET", "/fast", "./libhandler.so", "handle_request")
The Zig server calls the C function directly — no GIL, no interpreter, no overhead.
🔄 Migrating from FastAPI
Step 1: Swap the imports
# Before
from fastapi import FastAPI, Depends, HTTPException, Query, Path
from pydantic import BaseModel
# After
from turboapi import TurboAPI as FastAPI, Depends, HTTPException, Query, Path
from dhi import BaseModel
Step 2: Use the built-in server
# FastAPI way (still works)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
# TurboAPI way (20x faster)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
Step 3: Run with free-threading
# Install free-threaded Python
uv python install 3.14t
python3.14t app.py
Feature Parity
| Feature | Status |
|---|---|
| Route decorators (@get, @post, etc.) | ✅ |
| Path parameters with type coercion | ✅ |
| Query parameters | ✅ |
| JSON request body | ✅ |
| Async handlers | ✅ |
Dependency injection (Depends()) |
✅ |
| OAuth2 (Password, AuthCode) | ✅ |
| HTTP Bearer / Basic auth | ✅ |
| API Key (Header / Query / Cookie) | ✅ |
| CORS middleware | ✅ |
| GZip middleware | ✅ |
| HTTPException with status codes | ✅ |
| Custom responses (JSON, HTML, Redirect) | ✅ |
| Background tasks | ✅ |
| APIRouter with prefixes | ✅ |
| Native FFI handlers (C/Zig, no Python) | ✅ |
| Zig-native JSON schema validation (dhi) | ✅ |
| Zig-side JSON→Python dict (no json.loads) | ✅ |
| Large body support (up to 16MB) | ✅ |
| Python 3.14t free-threaded | ✅ |
| WebSocket support | 🔧 In progress |
| HTTP/2 + TLS | 🔧 In progress |
📁 Project Structure
turboAPI/
├── python/turboapi/
│ ├── main_app.py # TurboAPI class (FastAPI-compatible, ASGI __call__)
│ ├── zig_integration.py # route registration, handler classification
│ ├── request_handler.py # enhanced/fast/fast_model handlers
│ ├── security.py # OAuth2, HTTPBearer, APIKey, Depends
│ ├── version_check.py # free-threading detection
│ └── turbonet.*.so # compiled Zig extension
├── zig/
│ ├── src/
│ │ ├── main.zig # Python C extension entry
│ │ ├── server.zig # HTTP server, thread pool, dispatch, JSON→PyObject
│ │ ├── router.zig # radix trie with path params + wildcards
│ │ ├── dhi_validator.zig # runtime JSON schema validation
│ │ └── py.zig # Python C-API wrappers
│ ├── build.zig # Zig build system
│ ├── build.zig.zon # dependencies (dhi fetched automatically)
│ └── build_turbonet.py # auto-detect Python, invoke zig build
├── tests/ # 275+ tests
├── benchmarks/
├── Dockerfile # Python 3.14t + Zig 0.15 + turbonet
├── docker-compose.yml
└── Makefile # make build, make test, make release
Building from Source
Requirements: Python 3.14t (free-threaded) and Zig 0.15+
# 1. Clone
git clone https://github.com/justrach/turboAPI.git
cd turboAPI
# 2. Install free-threaded Python (if you don't have it)
uv python install 3.14t
# 3. Build the Zig native backend (dhi dependency fetched automatically)
python3.14t zig/build_turbonet.py --install
# 4. Install the Python package
pip install -e ".[dev]"
# 5. Run tests
python -m pytest tests/ -p no:anchorpy \
--deselect tests/test_fastapi_parity.py::TestWebSocket -v
Or use the Makefile:
make build # debug build + install
make release # ReleaseFast build + install
make test # run Python tests
make zig-test # run Zig unit tests
Or just Docker:
docker compose up --build
🐍 Why Python?
The "just use Go/Rust" criticism is fair for pure throughput. TurboAPI's value proposition is different: Python ecosystem + near-native HTTP throughput.
What you keep with Python
- ML / AI libraries — PyTorch, transformers, LangChain, LlamaIndex, etc. None of these exist in Go or Rust at the same maturity level. If your API calls an LLM or does inference, Python is the only practical choice.
- ORM ecosystem — SQLAlchemy, Tortoise, Django ORM, Alembic. Rewriting this in Go is months of work.
- Team familiarity — most backend Python teams can be productive on day one. A Rust rewrite takes 6-12 months and a different hiring profile.
- Library coverage — Stripe SDK, Twilio, boto3, Celery, Redis, every database driver. Go/Rust alternatives exist but are thinner.
- FastAPI compatibility — if you're already on FastAPI, TurboAPI is a one-line import change, not a rewrite.
When to actually use Go or Rust instead
| Scenario | Recommendation |
|---|---|
| Pure JSON proxy, no business logic | Go (net/http or Gin) |
| Embedded systems, < 1MB binary | Rust |
| Existing Go/Rust team | Stay in your stack |
| Need >200k req/s with <0.1ms p99 | Native server, no Python |
| Need HTTP/2, gRPC today | Go (mature ecosystem) |
| Heavy Python ML/data dependencies | TurboAPI |
| FastAPI codebase, need 10-20x throughput | TurboAPI |
| Background workers + AI inference + HTTP | TurboAPI |
The realistic throughput story
req/s p50 latency Python needed?
Go net/http 250k+ 0.05ms No
TurboAPI (noargs) 144k 0.16ms Yes (thin layer)
TurboAPI (CORS) 110k 0.22ms Yes
FastAPI + uvicorn 6-8k 14ms Yes
Django REST 2-4k 25ms+ Yes
TurboAPI won't out-run a native Go server on raw req/s. It closes most of the gap while keeping your Python codebase intact.
🔭 Observability
TurboAPI handlers are regular Python functions — standard observability tools work without special adapters.
OpenTelemetry
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)
app = TurboAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
with tracer.start_as_current_span("get_user") as span:
span.set_attribute("user.id", user_id)
user = db.get(user_id)
return user.dict()
Prometheus
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
import time
REQUEST_COUNT = Counter("http_requests_total", "Total requests", ["method", "path", "status"])
REQUEST_LATENCY = Histogram("http_request_duration_seconds", "Request latency", ["path"])
class MetricsMiddleware:
async def __call__(self, request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration = time.perf_counter() - start
REQUEST_COUNT.labels(request.method, request.url.path, response.status_code).inc()
REQUEST_LATENCY.labels(request.url.path).observe(duration)
return response
app = TurboAPI()
app.add_middleware(MetricsMiddleware)
@app.get("/metrics")
def metrics():
from turboapi import Response
return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST)
Structured logging
import structlog
log = structlog.get_logger()
@app.get("/orders/{order_id}")
def get_order(order_id: int):
log.info("order.fetch", order_id=order_id)
order = db.fetch(order_id)
if not order:
log.warning("order.not_found", order_id=order_id)
raise HTTPException(status_code=404)
return order.dict()
Middleware-based tracing works today on enhanced-path routes (those using Depends(), or any route when middleware is added). The Zig fast-path routes bypass the Python middleware stack for throughput — if you need per-request tracing on every route, add a middleware and accept the ~24% throughput overhead.
🤝 Contributing
Open an issue before submitting a large PR so we can align on the approach.
git clone https://github.com/justrach/turboAPI.git
cd turboAPI
uv python install 3.14t
python3.14t zig/build_turbonet.py --install # build Zig backend
pip install -e ".[dev]" # install in dev mode
make hooks # install pre-commit hook
make test # verify everything works
Credits
- dhi — Pydantic-compatible validation, Zig + Python
- Zig 0.15 — HTTP server, JSON validation, zero-copy I/O
- Python 3.14t — free-threaded runtime, true parallelism
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file turboapi-1.0.13.tar.gz.
File metadata
- Download URL: turboapi-1.0.13.tar.gz
- Upload date:
- Size: 118.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7fde23b25736042679eebe249f4123acec748ceefc262a780ce0302b9885f147
|
|
| MD5 |
0b9af00d33f2185a81ee33beb6cc8c52
|
|
| BLAKE2b-256 |
f0171153f2b6afa0055778a68ac397e8917f148d0ce9913c6b73628dbe773ece
|
File details
Details for the file turboapi-1.0.13-cp314t-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.
File metadata
- Download URL: turboapi-1.0.13-cp314t-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- Upload date:
- Size: 958.1 kB
- Tags: CPython 3.14t, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14d3a3fbf5e0aa66dd8d057231cd0b722b0fa9ded34c7899c4aa8083cc288ba9
|
|
| MD5 |
c815f894e0b2a367bab0b7afd593b5de
|
|
| BLAKE2b-256 |
58f841a43cad6a36b0a1419cf71211888e604809118a9e1098b4824169ed984b
|
File details
Details for the file turboapi-1.0.13-cp314t-cp314t-macosx_10_15_universal2.whl.
File metadata
- Download URL: turboapi-1.0.13-cp314t-cp314t-macosx_10_15_universal2.whl
- Upload date:
- Size: 254.0 kB
- Tags: CPython 3.14t, macOS 10.15+ universal2 (ARM64, x86-64)
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f76ba92741e8dd82821a3c7f2c8012fb762026471a4e17c7ef95d86cf34e359
|
|
| MD5 |
3ec2ffd674b61ed7249945e8753c4a4f
|
|
| BLAKE2b-256 |
9d71030e43640752f9c20674a47a1a0d277455b18e64434b6e2a5026853fb5d7
|
File details
Details for the file turboapi-1.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.
File metadata
- Download URL: turboapi-1.0.13-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- Upload date:
- Size: 958.0 kB
- Tags: CPython 3.14, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0efdb575d481bde3debc1da797e86f11131d3056c042e98fb03ea6257b0e770b
|
|
| MD5 |
bcebe41128dbb7f9e02b51472b43e0f5
|
|
| BLAKE2b-256 |
a8e7e7540312a11613f6fbadd3761db39af8bd29be8ce9312bf4f20a4b41827e
|
File details
Details for the file turboapi-1.0.13-cp314-cp314-macosx_10_15_universal2.whl.
File metadata
- Download URL: turboapi-1.0.13-cp314-cp314-macosx_10_15_universal2.whl
- Upload date:
- Size: 254.0 kB
- Tags: CPython 3.14, macOS 10.15+ universal2 (ARM64, x86-64)
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
763903fa5696ab8cac48a6c7414fe45c47a7e8adcdb189c757829cedcae51ea9
|
|
| MD5 |
73edcd3fe64983b07f8158f64156dc69
|
|
| BLAKE2b-256 |
e808a36d3902f4197e36efbc8473928cace803e03131ef50bce0f57882bc589d
|
File details
Details for the file turboapi-1.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.
File metadata
- Download URL: turboapi-1.0.13-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- Upload date:
- Size: 957.0 kB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
11dafb2a8cea7e46440e06e7525305e395f44f5020ee77218faf04a4fd6e9761
|
|
| MD5 |
b286805b1223858ea75ba71427578368
|
|
| BLAKE2b-256 |
0940bd96c45858e21858178882f24b3c6b3151bb79203c25d3008d783403bc78
|
File details
Details for the file turboapi-1.0.13-cp313-cp313-macosx_10_13_universal2.whl.
File metadata
- Download URL: turboapi-1.0.13-cp313-cp313-macosx_10_13_universal2.whl
- Upload date:
- Size: 253.9 kB
- Tags: CPython 3.13, macOS 10.13+ universal2 (ARM64, x86-64)
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f2d466c98708f1f2aeefc868befef1d4b20bcdabce97e6fad89ab188d58d406
|
|
| MD5 |
58e10eb0852b378e5ff6cdb1183480d5
|
|
| BLAKE2b-256 |
9796d8b1688ff8a453ba67d6d86ae9e74af2a622bcc0502c018721a0bd996fea
|