Skip to main content
  1. Posts/

Robyn Extensions: Rust-Powered Validation, Auth, and Rate Limiting for Python APIs

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
#

ExtensionWhat It DoesPerformance
Request ValidationPydantic v2-compatible models with type coercion, nested models, field constraintsSub-microsecond per field
OpenAPI DocsAuto-generated Swagger UI at /docs and ReDoc at /redocZero runtime overhead
Rate LimitingToken bucket algorithm with per-IP, per-user, and preset profiles~100ns per check
AuthenticationJWT validation with JWKS caching, OAuth2/OIDC for Auth0, Google, Okta, Azure AD~500μs with cached JWKS
REST API GeneratorCRUD boilerplate with filtering, pagination, and sortingN/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.py

A 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   → ReDoc

Every @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).

FastAPIRobyn + Extensions
RuntimePython (Starlette/uvicorn)Rust (Actix)
ValidationPydantic (Python)Pydantic API, Rust execution
Rate limitingThird-party middlewareBuilt-in, ~100ns/check
JWT authThird-party libraryBuilt-in, JWKS cached
OpenAPIBuilt-in (excellent)Auto-generated
EcosystemMassiveGrowing

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 --all

19 Python tests + 12 Rust tests covering validation, rate limiting, auth, and OpenAPI generation.

Related#

Kevin Keller
Author
Kevin Keller
Personal blog about AI, Observability & Data Sovereignty. Snowflake-related articles explore the art of the possible and are not official Snowflake solutions or endorsed by Snowflake unless explicitly stated. Opinions are my own. Content is meant as educational inspiration, not production guidance.
Share this article

Related