Robyn is a Python web framework with a Rust runtime — it gives you Python’s developer experience with Rust’s performance. Think FastAPI’s ergonomics with actual compiled-code speed under the hood.
But out of the box, Robyn is intentionally minimal. No request validation. No built-in auth. No rate limiting. No auto-generated API docs. You build what you need.
Robyn Extensions fills that gap with five Rust-backed features that bring the FastAPI developer experience to Robyn — without sacrificing the performance that made you choose Robyn in the first place.
GitHub: KellerKev/robyn-extensions
What You Get#
| Extension | What It Does | Performance |
|---|---|---|
| Request Validation | Pydantic v2-compatible models with type coercion, nested models, field constraints | Sub-microsecond per field |
| OpenAPI Docs | Auto-generated Swagger UI at /docs and ReDoc at /redoc | Zero runtime overhead |
| Rate Limiting | Token bucket algorithm with per-IP, per-user, and preset profiles | ~100ns per check |
| Authentication | JWT validation with JWKS caching, OAuth2/OIDC for Auth0, Google, Okta, Azure AD | ~500μs with cached JWKS |
| REST API Generator | CRUD boilerplate with filtering, pagination, and sorting | N/A (code generation) |
The key: all heavy lifting happens in Rust via PyO3 bindings. The Python layer is just decorators and Pydantic models — the actual validation, token parsing, and rate counting runs in compiled Rust.
Quick Start#
git clone https://github.com/KellerKev/robyn-extensions.git
cd robyn-extensions
pixi install
pixi run maturin develop # Build Rust extensions
pixi run python examples/quickstart.pyA complete API in 20 lines:
from robyn import Robyn, Request
from robyn_extensions import BaseModel, Field, body_v2, rate_limit
app = Robyn(__file__)
class User(BaseModel):
name: str = Field(min_length=2, max_length=50)
email: str
age: int = Field(ge=0, le=150)
@app.post("/users")
@body_v2(User)
@rate_limit(requests=10, per_seconds=60)
def create_user(request: Request, user: User):
return {"message": f"Created user {user.name}", "data": user.model_dump()}
if __name__ == "__main__":
app.start(port=8080)That’s Pydantic validation + rate limiting + a typed endpoint — all backed by Rust.
Request Validation#
The validation layer is Pydantic v2-compatible. You write models the same way you would in FastAPI, but the field-level validation runs in Rust:
from robyn_extensions import (
BaseModel, Field, computed_field,
field_validator, model_validator,
body_v2, query, returns
)
class UserCreate(BaseModel):
username: str = Field(min_length=3, max_length=20)
email: str = Field(regex=r"^[\w\.-]+@[\w\.-]+\.\w+$")
age: int = Field(ge=18, le=120)
bio: Optional[str] = Field(default=None, max_length=500)
@field_validator('username')
@classmethod
def no_reserved_names(cls, v):
if v.lower() in ('admin', 'root', 'system'):
raise ValueError('Reserved username')
return v
class PasswordChange(BaseModel):
old_password: str = Field(min_length=8)
new_password: str = Field(min_length=8)
confirm_password: str = Field(min_length=8)
@model_validator(mode='after')
def passwords_match(self):
if self.new_password != self.confirm_password:
raise ValueError('Passwords do not match')
return self
@app.post("/users")
@body_v2(UserCreate)
@returns(UserResponse)
def create_user(request: Request, user: UserCreate):
# user is already validated — types coerced, constraints checked
return UserResponse(id=1, **user.model_dump())Computed fields, field validators, model validators — all the Pydantic patterns you know, with Rust doing the actual constraint checking.
Rate Limiting#
Token bucket algorithm with presets for common scenarios:
from robyn_extensions import rate_limit, strict, moderate
# Custom: 100 requests per 60 seconds
@rate_limit(requests=100, per_seconds=60)
def api_endpoint(request):
...
# Preset: strict (10 req/min)
@strict()
def login_endpoint(request):
...
# Preset: moderate (60 req/min)
@moderate()
def search_endpoint(request):
...Rate limiting tracks by IP address by default, with optional per-user tracking when combined with authentication. The token bucket implementation runs entirely in Rust — approximately 100 nanoseconds per check.
Authentication#
JWT validation with automatic JWKS discovery and caching. Supports Auth0, Google, Okta, Azure AD out of the box, or any custom JWKS endpoint:
from robyn_extensions import (
setup_auth, AuthConfig,
require_auth, optional_auth,
require_scope, admin_required
)
# Choose your provider
setup_auth(AuthConfig.auth0(
domain="your-app.auth0.com",
audience="https://your-api.example.com"
))
# Or Google
# setup_auth(AuthConfig.google(client_id="your-client-id"))
# Or custom JWKS
# setup_auth(AuthConfig.from_jwks(
# jwks_url="https://your-idp.com/.well-known/jwks.json",
# audience="your-api"
# ))
# Protected route
@app.get("/profile")
@require_auth
def get_profile(request: Request):
user = request.identity # Decoded JWT claims
return {"sub": user["sub"], "email": user.get("email")}
# Scope-based access control
@app.delete("/users/:id")
@admin_required
def delete_user(request: Request):
...
# Optional auth — works for both logged-in and anonymous users
@app.get("/posts")
@optional_auth
def list_posts(request: Request):
user = getattr(request, 'identity', None)
...The JWKS keys are fetched once and cached — subsequent JWT validations run in ~500μs. Key rotation is handled automatically (JWKS is refreshed when a kid isn’t found in cache).
OpenAPI Documentation#
Auto-generated from your route decorators and Pydantic models:
from robyn_extensions import AutoDocs
docs = AutoDocs(
app,
title="My API",
version="1.0.0",
description="Rust-powered Python API"
)
# That's it. Visit:
# /docs → Swagger UI
# /redoc → ReDocEvery @body_v2, @query, and @returns decorator automatically registers the schema. No manual OpenAPI spec maintenance.
Architecture#
┌─────────────────────────────────────────────────┐
│ Python Layer │
│ │
│ @body_v2(User) @rate_limit(10, 60) │
│ @require_auth AutoDocs(app) │
│ │
│ Decorators + Pydantic models + Route config │
└──────────────────────┬──────────────────────────┘
│ PyO3 bindings
┌──────────────────────▼──────────────────────────┐
│ Rust Layer │
│ │
│ robyn_validation robyn_ratelimit │
│ (field checks, (token bucket, │
│ type coercion) atomic counters) │
│ │
│ robyn_auth robyn_openapi │
│ (JWT parsing, (schema generation, │
│ JWKS caching, Swagger/ReDoc serving) │
│ ECDSA/RSA verify) │
└─────────────────────────────────────────────────┘Four separate Rust crates, each focused on one concern. The Python package (robyn_python) re-exports everything as a clean decorator API.
Why Robyn Over FastAPI?#
FastAPI is excellent. But it runs on Starlette (Python ASGI), which means your request handling, validation, and middleware all run in Python. Robyn’s runtime is Rust — the event loop, HTTP parsing, and routing are all compiled code.
Robyn Extensions bridges the gap: you get FastAPI’s DX (Pydantic models, OpenAPI docs, JWT auth) with Robyn’s performance (Rust event loop, sub-microsecond validation).
| FastAPI | Robyn + Extensions | |
|---|---|---|
| Runtime | Python (Starlette/uvicorn) | Rust (Actix) |
| Validation | Pydantic (Python) | Pydantic API, Rust execution |
| Rate limiting | Third-party middleware | Built-in, ~100ns/check |
| JWT auth | Third-party library | Built-in, JWKS cached |
| OpenAPI | Built-in (excellent) | Auto-generated |
| Ecosystem | Massive | Growing |
Robyn isn’t a FastAPI replacement for every project — FastAPI’s ecosystem is unmatched. But for performance-critical APIs where you want Python’s ergonomics with Rust’s speed, Robyn + Extensions is a compelling option.
Testing#
# Run Python tests
pixi run pytest tests/ -v
# Run Rust tests
pixi run cargo test --all19 Python tests + 12 Rust tests covering validation, rate limiting, auth, and OpenAPI generation.
Related#
- SMCP: Secure Model Context Protocol — security for AI agent communication
- Cortex Proxy — another Rust project for AI infrastructure
- Postgres Analyst — Python + Ollama for natural language SQL
