Skip to main content
FastrAPI is a hybrid framework that combines the best of both worlds: Python’s developer-friendly syntax and Rust’s blazing-fast performance. This page explains how the integration works under the hood.

Architecture overview

FastrAPI consists of three main layers:
1

Python layer

Your application code written in Python with FastrAPI decorators and Pydantic models.
2

PyO3 bridge

Bidirectional FFI (Foreign Function Interface) that allows Rust and Python to communicate seamlessly.
3

Rust core

High-performance HTTP server built on Axum, handling routing, middleware, and request processing.
4

Tokio runtime

Asynchronous task scheduler providing true multi-threaded concurrency.

PyO3: The Python-Rust bridge

PyO3 is a Rust library that enables seamless interoperability between Python and Rust. FastrAPI uses PyO3 version 0.27.0 with these key features:
[dependencies]
pyo3 = { version = "0.27.0", features = ["extension-module", "py-clone"] }
pyo3-async-runtimes = { version = "0.27.0", features = ["tokio-runtime"] }

Module definition

The main FastrAPI module is defined using PyO3’s #[pymodule] macro:
#[pymodule(gil_used = false)]
fn fastrapi(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<FastrAPI>()?;
    m.add("FastrAPI", m.getattr("FastrAPI")?)?;

    responses::register(m)?;
    exceptions::register(m)?;
    request::register(m)?;
    datastructures::register(m)?;
    background::register(m)?;
    security::register(m)?;
    pydantic::register_pydantic_integration(m)?;
    status::create_status_submodule(m)?;
    params::register(m)?;
    middlewares::register(m)?;
    websocket::register(m)?;

    Ok(())
}
The gil_used = false flag indicates that this module doesn’t require holding the Python GIL during initialization, improving startup performance.

Python class binding

The FastrAPI class is exposed to Python using the #[pyclass] macro:
#[pyclass(name = "FastrAPI")]
pub struct FastrAPI {
    #[pyo3(get, set)]
    pub debug: bool,
    #[pyo3(get, set)]
    pub title: String,
    #[pyo3(get, set)]
    pub version: String,
    // ... other fields
}

#[pymethods]
impl FastrAPI {
    #[new]
    fn new(py: Python<'_>, debug: bool, title: String, ...) -> PyResult<Self> {
        // Constructor logic
    }

    fn get<'py>(&self, path: String, py: Python<'py>) -> PyResult<Py<PyAny>> {
        self.create_decorator("GET", path, py)
    }

    fn serve(&self, py: Python, host: Option<String>, port: Option<u16>) -> PyResult<()> {
        crate::server::serve(py, host, port, self)
    }
}
This allows Python code to interact with Rust objects naturally:
from fastrapi import FastrAPI

app = FastrAPI(debug=True, title="My API")

@app.get("/hello")
def hello():
    return {"message": "world"}

app.serve("127.0.0.1", 8080)

Axum: The HTTP server

Axum is a web framework built on top of Tokio and Hyper. FastrAPI uses Axum 0.8.7 with WebSocket support:
[dependencies]
axum = { version = "0.8.7", features = ["ws"] }
tokio = { version = "1.48.0", features = ["full"] }
hyper = "1.7.0"

Router construction

FastrAPI builds an Axum router from Python route definitions:
fn build_router(
    py: Python,
    app_state: AppState,
    docs_url: Option<String>,
    openapi_url: String,
    app_config: &FastrAPI,
) -> Router {
    let mut app = Router::new();

    // Register all routes
    let guard = local_guard(&*ROUTES);
    for entry in ROUTES.iter(&guard) {
        let (route_key, _handler) = entry;
        let parts: Vec<&str> = route_key.splitn(2, ' ').collect();
        let method = parts[0];
        let path = parts[1].to_string();
        app = register_route(app, method, path, route_key.as_str().into(), app_state.clone());
    }

    // Apply middleware layers
    app.layer(Extension(app_state))
}

Request handling flow

For routes without request bodies:
let handler = move |Extension(state): Extension<AppState>,
                   ConnectInfo(_addr): ConnectInfo<SocketAddr>| {
    let route_key = Arc::clone(&route_key_clone);
    async move { 
        run_py_handler_no_args(state.rt_handle, route_key).await 
    }
};

app.route(&path, get(handler))

Data structure bridging

Route storage

FastrAPI uses lock-free concurrent data structures from the papaya crate:
use papaya::HashMap as PapayaHashMap;
use once_cell::sync::Lazy;

pub static ROUTES: Lazy<PapayaHashMap<String, RouteHandler>> =
    Lazy::new(|| PapayaHashMap::with_capacity(128));

pub struct RouteHandler {
    pub func: Py<PyAny>,           // Python function reference
    pub is_async: bool,            // Async detection
    pub is_fast_path: bool,        // Fast-path optimization flag
    pub param_validators: Vec<(String, Py<PyAny>)>,
    pub response_type: ResponseType,
    pub needs_kwargs: bool,
    pub path_param_names: Vec<String>,
    pub query_param_names: Vec<String>,
    pub body_param_names: Vec<String>,
    pub dependencies: Vec<DependencyInfo>,
}

Python to Rust conversion

FastrAPI uses serde-pyobject to convert between Python objects and Rust types:
use serde_json::Value;
use pythonize::depythonize;

fn py_any_to_json(py: Python, obj: &Bound<PyAny>) -> Value {
    match depythonize::<Value>(obj) {
        Ok(val) => val,
        Err(_) => Value::Null,
    }
}

Async execution model

Dual runtime architecture

FastrAPI maintains two separate runtimes:

Tokio runtime

Handles all async I/O, routing, and middleware:
// Main event loop
PYTHON_RUNTIME.block_on(async move {
    let listener = TcpListener::bind(&addr).await?;
    axum::serve(listener, router).await
});

Python thread pool

Executes Python handlers in isolation:
// Spawn blocking Python work
rt_handle.spawn_blocking(move || {
    Python::attach(|py| {
        // Execute Python code with GIL
        handler.func.call0(py)
    })
}).await

Async Python functions

For Python async def functions, FastrAPI bridges to Tokio using pyo3-async-runtimes:
let future = Python::attach(|py| -> PyResult<_> {
    let bound_func = func.bind(py);
    let bound_kwargs = py_kwargs.bind(py);
    let coro = bound_func.call((), Some(bound_kwargs))?;
    
    // Convert Python coroutine to Rust future
    pyo3_async_runtimes::tokio::into_future(coro)
})?;

let result = future.await?;  // Await in Tokio runtime

Dependency injection

FastrAPI implements dependency injection entirely in Rust:

Dependency parsing

pub struct DependencyInfo {
    pub func: Py<PyAny>,
    pub is_async: bool,
    pub param_name: Option<String>,
    pub scopes: Vec<String>,
    pub use_cache: bool,
    pub sub_dependencies: Vec<DependencyInfo>,
    pub injection_plan: Vec<(String, InjectionType)>,
}

pub enum InjectionType {
    Dependency(String),  // Another dependency
    Parameter,           // Path/query param
    Request,             // Request object
    SecurityScopes,      // Security scopes
}

Pre-computed injection plans

At decorator time, FastrAPI analyzes the dependency graph:
pub fn parse_dependencies(py: Python, func: &Bound<PyAny>) -> PyResult<Vec<DependencyInfo>> {
    let mut dependencies = Vec::new();
    let inspect = py.import("inspect")?;
    let signature = inspect.call_method1("signature", (func,))?;
    
    // Build injection plan once
    let injection_plan = build_injection_plan(py, func, &sub_deps, &scopes)?;
    
    dependencies.push(DependencyInfo {
        func: target_callable,
        is_async,
        injection_plan,  // Cached for all future requests
        // ...
    });
    
    Ok(dependencies)
}
This eliminates runtime introspection overhead on every request.

Key dependencies

FastrAPI relies on these Rust crates:
CrateVersionPurpose
pyo30.27.0Python-Rust FFI bridge
axum0.8.7HTTP server framework
tokio1.48.0Async runtime
papaya0.2.3Lock-free concurrent hashmap
serde_json1.0.145JSON serialization
tower-http0.6.6Middleware (CORS, GZip, compression)
tower-sessions0.14.0Session management
dashmap6.1.0Concurrent hashmap
hyper1.7.0Low-level HTTP primitives

Performance optimizations

Compile-time optimizations

FastrAPI’s release profile is tuned for maximum performance:
[profile.release]
codegen-units = 1        # Better inlining across crate boundaries
lto = "fat"              # Full link-time optimization
panic = "abort"          # No unwinding, smaller binary
strip = true             # Remove debug symbols
opt-level = 3            # Maximum optimization level

[profile.release.build-override]
opt-level = 3            # Optimize build scripts too

Zero-copy string handling

FastrAPI uses smartstring for small string optimizations:
use smartstring::alias::String as SmartString;

// Stores strings up to 23 bytes inline (no heap allocation)
let route_key: SmartString = format!("GET /api/users/{}", id).into();

Parking lot for faster locks

Where locks are necessary, FastrAPI uses parking_lot for lower overhead:
use parking_lot::RwLock;  // Faster than std::sync::RwLock

let data = Arc::new(RwLock::new(shared_state));

Building from source

To build FastrAPI locally:
1

Install Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
2

Clone the repository

git clone https://github.com/ppmpreetham/fastrapi.git
cd fastrapi
3

Build with maturin

pip install maturin
maturin develop --release
4

Test the build

from fastrapi import FastrAPI

app = FastrAPI()

@app.get("/test")
def test():
    return {"status": "built from source!"}

app.serve("127.0.0.1", 8080)

Extending FastrAPI

You can add custom Rust handlers for maximum performance:

Native Rust endpoints

// In src/custom_handlers.rs
use axum::{Json, response::IntoResponse};
use serde_json::json;

pub async fn ultra_fast_endpoint() -> impl IntoResponse {
    // Pure Rust, no Python overhead
    Json(json!({"message": "blazingly fast"}))
}

Register in module

#[pymodule(gil_used = false)]
fn fastrapi(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<FastrAPI>()?;
    
    // Expose Rust function to Python
    m.add_function(wrap_pyfunction!(ultra_fast_endpoint, m)?)?;
    
    Ok(())
}
Custom Rust handlers require rebuilding the extension module and won’t benefit from Python’s dynamic nature.

Debugging tips

FastrAPI uses the tracing crate for structured logging:
app = FastrAPI(debug=True)
Set the RUST_LOG environment variable:
RUST_LOG=debug python main.py
If a Rust panic occurs, enable backtraces:
RUST_BACKTRACE=1 python main.py
Use py-spy to profile Python code called from Rust:
pip install py-spy
py-spy record -o profile.svg -- python main.py

Further reading

PyO3 User Guide

Learn more about Python-Rust interoperability

Axum Documentation

Deep dive into Axum’s routing and middleware

Tokio Tutorial

Master async Rust with Tokio

FastrAPI Source

Explore the full implementation

Build docs developers (and LLMs) love