# fastapi_rust
<div align="center">
<img src="fastapi_rust_illustration.webp" alt="fastapi_rust - High-performance Rust web framework with FastAPI-inspired ergonomics">
</div>
<div align="center">
**High-performance Rust web framework with FastAPI-inspired ergonomics**
*A Rust port inspired by [tiangolo/fastapi](https://github.com/tiangolo/fastapi) (Python), extended with [asupersync](https://github.com/Dicklesworthstone/asupersync) for structured concurrency, zero-copy parsing, and deterministic testing.*
[](LICENSE)
[](https://www.rust-lang.org/)
[]()
*Type-safe routing | Zero-copy parsing | Structured concurrency | OpenAPI generation*
</div>
<div align="center">
<h3>Quick Install</h3>
```bash
curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/fastapi_rust/main/install.sh?$(date +%s)" | bash
```
**Or add to your project:**
```toml
# Cargo.toml
[dependencies]
fastapi = { git = "https://github.com/Dicklesworthstone/fastapi_rust.git" }
asupersync = { git = "https://github.com/Dicklesworthstone/asupersync.git" }
serde = { version = "1", features = ["derive"] }
```
<p><em>Requires Rust 1.85+ (2024 edition). Co-developed with <a href="https://github.com/Dicklesworthstone/asupersync">asupersync</a>.</em></p>
</div>
---
## TL;DR
**The Problem**: Rust web frameworks either sacrifice developer ergonomics for performance (raw `hyper`) or hide allocations behind layers of abstraction (Axum + Tower). None leverage structured concurrency for cancel-correct request handling, and most require a massive dependency tree.
**The Solution**: fastapi_rust brings FastAPI's intuitive, type-driven API design to Rust with zero-copy HTTP parsing, compile-time route validation, and first-class integration with [asupersync](https://github.com/Dicklesworthstone/asupersync) for structured concurrency and deterministic testing.
### Why fastapi_rust?
| Feature | What It Does |
|---------|--------------|
| **Zero-copy HTTP parsing** | Requests parsed directly from buffers; no allocations on fast paths |
| **Compile-time route validation** | Invalid routes fail at build time via proc macros, not at runtime |
| **Structured concurrency** | Request handlers run in regions; cancellation is automatic and correct |
| **Type-driven extractors** | Declare parameter types; framework extracts and validates automatically |
| **Dependency discipline** | No Tokio/Hyper/Tower/Axum; direct deps kept small with a bias toward removal |
| **Deterministic testing** | Lab runtime for reproducible concurrent request tests |
| **FastAPI-compatible errors** | Validation errors match FastAPI's JSON format exactly |
---
## Quick Example
```rust
use fastapi_rust::prelude::*;
#[derive(Serialize, Deserialize, JsonSchema)]
struct Item {
id: i64,
name: String,
price: f64,
}
#[get("/items/{id}")]
async fn get_item(cx: &Cx, id: Path<i64>) -> Json<Item> {
cx.checkpoint()?; // Cancellation-safe yield point
Json(Item {
id: id.0,
name: "Widget".into(),
price: 29.99,
})
}
#[post("/items")]
async fn create_item(cx: &Cx, item: Json<Item>) -> Response {
// Automatic JSON deserialization with validation
// Wrong Content-Type -> 415
// Parse error -> 422 with detailed location
// Payload too large -> 413
Response::created().json(&item.0)
}
#[get("/search")]
async fn search(
cx: &Cx,
q: Query<SearchParams>, // ?q=...&limit=...
auth: Header<Option<Bearer>>, // Optional auth header
) -> Result<Json<Results>, HttpError> {
// All extraction happens automatically
// Wrong types -> compile error
// Missing required -> 422 response
}
fn main() {
let app = App::builder()
.title("My API")
.version("1.0.0")
.route_entry(get_item_route())
.route_entry(create_item_route())
.route_entry(search_route())
.middleware(RequestIdMiddleware::new())
.middleware(Cors::permissive())
.build();
// Run with asupersync
let rt = asupersync::runtime::RuntimeBuilder::current_thread()
.build()
.expect("runtime must build");
rt.block_on(async move {
serve(app, "0.0.0.0:8000")
.await
.expect("server must start");
});
}
```
---
## Design Philosophy
### 1. Extract Spec, Never Translate
We study FastAPI's behavior and ergonomics, then implement idiomatically in Rust. No line-by-line Python translation - Rust has better tools for these problems:
- Python decorators -> Rust procedural macros
- Pydantic validation -> Compile-time type checking + serde
- ASGI lifecycle -> Structured concurrency regions
- Runtime reflection -> Compile-time code generation
### 2. Zero-Cost Abstractions
| Technique | Implementation |
|-----------|----------------|
| No runtime reflection | Proc macros analyze types at compile time |
| No trait objects on hot paths | Monomorphization via generics |
| Pre-allocated buffers | 4KB default, configurable per-route |
| Zero-copy HTTP parsing | Borrowed types reference request buffer |
| Inline critical paths | `#[inline(always)]` on hot code |
### 3. Cancel-Correct by Default
Every request handler runs in an asupersync **region**. Client disconnects, timeouts, and shutdowns trigger graceful cancellation:
```
Connection Accepted
|
v
+-----------------------------------------------+
| Request Region (owns all request work) |
| +-------------------------------------------+|
| | Handler Task ||
| | +-- Dependency Task (DB query) ||
| | +-- Dependency Task (cache lookup) ||
| | +-- Background Task (logging) ||
| +-------------------------------------------+|
| |
| Region close waits for ALL tasks to finish |
+-----------------------------------------------+
|
v
Response Sent (only after region quiescent)
```
**No orphaned tasks.** Client disconnect -> cancel region -> all tasks cleaned up.
### 4. Dependency Discipline
| Crate | Purpose | Why |
|-------|---------|-----|
| `asupersync` | Async runtime | Our own - cancel-correct, capability-secure |
| `serde` | Serialization traits | Zero-cost, industry standard |
| `serde_json` | JSON parsing | Fast, well-optimized |
**Explicitly avoided:** Tokio, Hyper, Axum, Tower, and runtime-reflection/schema-building crates.
Note: Some workspace crates currently use additional small utility/test/proc-macro dependencies
where it meaningfully improves safety or developer experience. Shrinking this further is an active
goal tracked in Beads (see `bd-uz2s`).
---
## How fastapi_rust Compares
| Feature | fastapi_rust | Axum | Actix-web | Rocket |
|---------|--------------|------|-----------|--------|
| Zero-copy HTTP parsing | **Custom** | Hyper | Partial | No |
| Compile-time routes | **Proc macros** | Runtime | Runtime | Macros |
| Structured concurrency | **asupersync** | Tokio spawn | Actix-rt | Tokio |
| Cancel-correct shutdown | **Native** | Manual | Manual | Manual |
| Dependency injection | **Native + cache** | State only | Data only | Managed |
| OpenAPI generation | **Compile-time** | External | External | External |
| Deterministic testing | **Lab runtime** | No | No | No |
| Direct deps (core runtime crates) | **Low (single digits)** | ~80+ | ~60+ | ~50+ |
| FastAPI-style errors | **Yes (422 format)** | No | No | No |
### When to Use fastapi_rust
- You need cancel-correct request handling (graceful shutdown, timeouts)
- You want compile-time route validation
- You're building with asupersync for structured concurrency
- You want deterministic tests for concurrent code
- You're familiar with FastAPI and want similar ergonomics in Rust
### When to Consider Alternatives
- You need production-proven stability today (fastapi_rust is v0.1.2)
- You require production-hardened WebSocket support (basic support exists; full parity tracked in `bd-z09e`)
- You have existing Tokio-based infrastructure
- You need the massive ecosystem of Tower middleware
---
## Installation
### Add to Cargo.toml
```toml
[dependencies]
fastapi_rust = { package = "fastapi-rust", version = "0.1.2" }
asupersync = "0.1.1"
serde = { version = "1", features = ["derive"] }
```
**Note**: The crates.io package is `fastapi-rust`, and the crate name is `fastapi_rust`.
### From Source
```bash
git clone https://github.com/Dicklesworthstone/fastapi_rust.git
cd fastapi_rust
cargo build --release
```
### Requirements
- **Rust 1.85+** (2024 edition)
- **[asupersync](https://github.com/Dicklesworthstone/asupersync)** (co-developed runtime)
---
## Architecture
```
+-------------------------------------------------------------------+
| fastapi_rust (facade) |
| Re-exports all public types, prelude module |
+-------------------------------------------------------------------+
| | | | |
v v v v v
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
| core | | http | | router | | macros | | openapi |
| | | | | | | | | |
| - Request | | - Parser | | - Trie | | - #[get] | | - Schema |
| - Response| | - Body | | - Match | | - #[post] | | - Builder |
| - Context | | - Query | | - Registry| | - Derive | | - Spec |
| - Extract | | - Headers | | | | | | |
| - Depends | | - Writer | | | | | | |
| - Error | | - Server | | | | | | |
| - Middle. | | - Stream | | | | | | |
| - Logging | | | | | | | | |
| - Testing | | | | | | | | |
| - Shutdown| | | | | | | | |
+-----------+ +-----------+ +-----------+ +-----------+ +-----------+
|
v
+-------------------------------------------------------------------+
| asupersync |
| Structured concurrency - Cx - Regions - Budgets - Lab |
+-------------------------------------------------------------------+
```
### Crate Overview
| Crate | ~Lines | Purpose |
|-------|--------|---------|
| `fastapi_rust` | 100 | Facade: re-exports, prelude |
| `fastapi-core` | 6,000 | Request, Response, extractors, DI, middleware, logging, testing, shutdown |
| `fastapi-http` | 2,500 | Zero-copy HTTP/1.1 parser, body handling, query parsing, streaming |
| `fastapi-router` | 600 | Radix trie routing, path matching, conflict detection |
| `fastapi-macros` | 400 | `#[get]`, `#[post]`, `#[derive(Validate)]`, `#[derive(JsonSchema)]` |
| `fastapi-openapi` | 500 | OpenAPI 3.1 types, schema builder, spec generation |
---
## Extractors
Extract typed data from requests declaratively:
```rust
use fastapi_rust::prelude::*;
#[get("/users/{id}")]
async fn get_user(
cx: &Cx, // Capability context (required)
id: Path<i64>, // Path parameter: /users/123
q: Query<SearchParams>, // Query string: ?q=...&limit=...
auth: Header<Authorization>, // Required header
accept: Header<Option<Accept>>, // Optional header
) -> Result<Json<User>, HttpError> {
// Types declare what you need - framework handles extraction
// Wrong types -> compile error
// Missing required -> 422 with FastAPI-compatible error
}
#[post("/items")]
async fn create(
cx: &Cx,
item: Json<CreateItem>, // JSON body
) -> Result<Response, HttpError> {
// 415 if wrong Content-Type
// 413 if payload too large (configurable)
// 422 if parse error (with location path)
}
```
### Available Extractors
| Extractor | Description | Error Response |
|-----------|-------------|----------------|
| `Path<T>` | URL path parameters | 422 if missing/wrong type |
| `Query<T>` | Query string | 422 if missing/invalid |
| `Json<T>` | JSON request body | 415/413/422 |
| `Header<T>` | Single header value | 422 if missing/invalid |
| `HeaderValues<T>` | All values for header | 422 if invalid |
| `State<T>` | Application state | 500 if not configured |
| `Depends<T>` | Dependency injection | Depends on factory |
| `Option<T>` | Any extractor, optional | Never fails |
---
## Middleware
Composable middleware with onion model execution:
```rust
use fastapi_rust::prelude::*;
// Built-in middleware
let app = App::builder()
.middleware(RequestIdMiddleware::new()) // Add X-Request-Id
.middleware(RequestResponseLogger::default()) // Log all requests
.middleware(Cors::permissive()) // CORS handling
.middleware(RequireHeader::new("X-API-Key")) // Require header
.middleware(AddResponseHeader::new("X-Powered-By", b"fastapi_rust"))
.build();
// Custom middleware
struct Timing;
impl Middleware for Timing {
async fn before(&self, ctx: &RequestContext, req: &mut Request) -> ControlFlow {
// Store start time in context
ControlFlow::Continue
}
async fn after(&self, ctx: &RequestContext, req: &Request, mut resp: Response) -> Response {
// Add X-Response-Time header
resp
}
}
```
### Execution Order
```
Request -> MW1.before -> MW2.before -> MW3.before -> Handler
|
Response <- MW1.after <- MW2.after <- MW3.after <- Response
```
First registered runs first on the way in, last on the way out (onion model).
---
## Dependency Injection
Request-scoped dependencies with caching:
```rust
use fastapi_rust::prelude::*;
// Define a dependency
#[derive(Clone)]
struct DatabasePool { /* ... */ }
impl FromDependency for DatabasePool {
type Error = HttpError;
async fn from_dependency(ctx: &RequestContext, req: &mut Request) -> Result<Self, HttpError> {
// Resolved once per request, cached for subsequent uses
Ok(DatabasePool::connect().await?)
}
}
// Use in handler
#[get("/users/{id}")]
async fn get_user(
cx: &Cx,
id: Path<i64>,
db: Depends<DatabasePool>, // Automatically resolved and cached
) -> Result<Json<User>, HttpError> {
let user = db.fetch_user(id.0).await?;
Ok(Json(user))
}
// Override for testing
let overrides = DependencyOverrides::new()
.with::<DatabasePool>(MockDatabase::new());
```
### Dependency Scopes
| Scope | Behavior |
|-------|----------|
| `Request` (default) | Resolve once, cache for request lifetime |
| `Function` | Resolve on every extraction |
| `NoCache` | Explicit opt-out of caching |
---
## Testing
In-process testing without network I/O:
```rust
use fastapi_rust::testing::*;
#[test]
fn test_get_item() {
let client = TestClient::new(app);
let resp = client.get("/items/42")
.header("Authorization", "Bearer token")
.send();
assert_eq!(resp.status(), 200);
let item: Item = resp.json();
assert_eq!(item.id, 42);
}
#[test]
fn test_deterministic() {
// Same seed = same execution order for concurrent operations
let client = TestClient::with_seed(app, 12345);
// Reproducible even with concurrent handlers
let resp = client.post("/items")
.json(&new_item)
.send();
assert_eq!(resp.status(), 201);
}
#[test]
fn test_with_overrides() {
let overrides = DependencyOverrides::new()
.with::<Database>(MockDatabase::new());
let client = TestClient::new(app)
.with_overrides(overrides);
// Handler receives MockDatabase instead of real one
}
```
### Assertion Helpers
```rust
use fastapi_core::{assert_status, assert_header, assert_json};
assert_status!(resp, 200);
assert_header!(resp, "Content-Type", "application/json");
assert_json!(resp, {"id": 42, "name": "Widget"});
```
---
## Error Handling
FastAPI-compatible validation errors:
```rust
// Handler returns HttpError
#[get("/items/{id}")]
async fn get_item(id: Path<i64>) -> Result<Json<Item>, HttpError> {
if id.0 < 0 {
return Err(HttpError::unprocessable_entity()
.detail("ID must be positive")
.loc(["path", "id"]));
}
// ...
}
```
**Error response format (FastAPI-compatible):**
```json
{
"detail": [
{
"type": "value_error",
"loc": ["path", "id"],
"msg": "ID must be positive",
"input": -1
}
]
}
```
### Built-in Error Types
| Status | Constructor | Use Case |
|--------|-------------|----------|
| 400 | `HttpError::bad_request()` | Malformed request |
| 401 | `HttpError::unauthorized()` | Missing/invalid auth |
| 403 | `HttpError::forbidden()` | Permission denied |
| 404 | `HttpError::not_found()` | Resource not found |
| 413 | `HttpError::payload_too_large()` | Body exceeds limit |
| 415 | `HttpError::unsupported_media_type()` | Wrong Content-Type |
| 422 | `HttpError::unprocessable_entity()` | Validation failed |
| 500 | `HttpError::internal()` | Server error |
---
## Graceful Shutdown
Cancel-correct shutdown with configurable grace periods:
```rust
use fastapi_rust::prelude::*;
use fastapi_core::shutdown::*;
let app = App::builder()
.graceful_shutdown(GracefulConfig {
grace_period: Duration::from_secs(30),
force_timeout: Duration::from_secs(5),
})
.on_shutdown(|phase| async move {
match phase {
ShutdownPhase::GracePeriod => {
// Stop accepting new connections
// Wait for in-flight requests
}
ShutdownPhase::ForceClose => {
// Cancel remaining requests
}
}
Ok(())
})
.build();
```
Shutdown propagates through asupersync regions - no orphaned tasks.
---
## Configuration
```rust
use fastapi_rust::prelude::*;
let app = App::builder()
// Metadata
.title("My API")
.version("1.0.0")
.description("A sample API built with fastapi_rust")
// Routes
.route_entry(get_item_route())
.route_entry(create_item_route())
.route_entry(delete_item_route())
// Middleware (order matters)
.middleware(RequestIdMiddleware::new())
.middleware(Cors::new(CorsConfig {
allow_origins: vec!["https://example.com".into()],
allow_methods: vec![Method::Get, Method::Post],
allow_headers: vec!["Authorization".into()],
max_age: Some(3600),
}))
// Shared state
.state(DatabasePool::new())
.state(CacheClient::new())
// Exception handlers
.exception_handler(|err: DatabaseError| {
HttpError::internal().detail(err.to_string())
})
// Lifecycle hooks
.on_startup(|| async {
println!("Starting up...");
Ok(())
})
.on_shutdown(|_| async {
println!("Shutting down...");
Ok(())
})
// Build
.build();
```
---
## Troubleshooting
### Common Issues
| Problem | Cause | Solution |
|---------|-------|----------|
| `asupersync not found` | Missing dependency | Add `asupersync` to Cargo.toml |
| `Cx lifetime error` | Holding Cx across await | Use `cx.checkpoint()` pattern |
| Route conflicts | Overlapping path patterns | Check for `{param}` vs literal conflicts |
| 422 on valid JSON | Missing `#[derive(Deserialize)]` | Add serde derive to your types |
| Middleware not running | Wrong registration order | Check middleware ordering |
### Debugging Tips
```rust
// Enable request logging
.middleware(RequestResponseLogger::new(LogConfig {
log_bodies: true,
log_headers: true,
}))
// Check route registration
app.routes().for_each(|r| println!("{} {}", r.method, r.path));
// Deterministic test reproduction
TestClient::with_seed(app, failing_seed)
```
---
## Parity Status (Goal: 100% FastAPI Coverage)
This project is intended to reach **100% feature-for-feature, behavior-for-behavior parity** with
the legacy Python FastAPI library.
Current parity status and the concrete gap list are tracked in:
- `PROPOSED_RUST_ARCHITECTURE.md` (Section 0: Parity Matrix)
- Beads (`br ready`, `br show <id>`) with the top-level epic `bd-uz2s`
### Known Gaps (As Of 2026-02-11)
- **OpenAPI generation**: currently minimal; needs real operation/schema mapping from route metadata.
- **TCP server integration/hardening**: `fastapi-http` has a server implementation, but the end-to-end
surface is still evolving.
- **WebSockets**: partial (handshake + basic frames). Full FastAPI/Starlette parity is tracked in `bd-z09e`.
- **Multipart/form-data + file uploads**: parser + `MultipartForm` extractor + incremental streamed-body parsing +
streamed-part incremental flushing + spool-backed file parts + `UploadFile` async API (`read`/`write`/`seek`/`close`) are implemented.
Remaining parity work in `bd-3ess` is mainly API-surface refinement around fully streamed consumption paths and broader behavior parity edge cases.
- **HTTP/2**: missing (`bd-2c9t`).
### Non-Negotiables / Constraints
- **Requires asupersync**: no Tokio support (by design).
- **Rust 1.85+**: uses Rust 2024 edition features.
- **Early development**: API will change before v1.0.
---
## FAQ
### Why "fastapi_rust"?
It's a Rust web framework inspired by Python's [FastAPI](https://fastapi.tiangolo.com/), preserving the type-driven API design while achieving native performance and cancel-correctness.
### Why not use Tokio/Axum?
Tokio's spawn model makes cancel-correctness difficult - tasks can outlive their scope, leading to resource leaks and subtle bugs. asupersync's structured concurrency ensures all request-related work completes or cancels together.
### Can I use this in production?
Not production-ready yet. This is v0.1.2 in active development; the TCP server exists (built on `asupersync::net`),
but parity and production hardening are tracked under `bd-uz2s`.
### How fast is it?
We haven't published end-to-end benchmarks yet, but the architecture is designed for:
- Zero allocations on the fast path
- Zero-copy request parsing
- No runtime reflection
- Pre-allocated buffers (4KB default)
### Does it support async/await?
Yes, fully. All handlers, middleware, and extractors are async-native, built on asupersync's structured concurrency model.
### Why the minimal dependency approach?
Each dependency is a maintenance burden, security surface, and compile-time cost. By keeping dependencies small and intentional, we:
- Reduce build times significantly
- Have full control over behavior
- Avoid dependency conflicts
- Make auditing practical
### How do validation errors compare to FastAPI?
Identical format. The same client code that handles FastAPI 422 responses will work with fastapi_rust:
```json
{
"detail": [
{"type": "missing", "loc": ["body", "email"], "msg": "Field required"}
]
}
```
---
## About Contributions
Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via `gh` and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.
---
## License
MIT license. See [LICENSE](LICENSE).
---
## Related Projects
| Project | Description |
|---------|-------------|
| [asupersync](https://github.com/Dicklesworthstone/asupersync) | Structured concurrency async runtime (co-developed) |
| [FastAPI](https://fastapi.tiangolo.com/) | The Python framework that inspired this project |