Skip to main content

What is dependency injection?

Dependency injection (DI) is a design pattern where you declare what your code needs, and the framework provides it automatically. This promotes code reuse, testing, and separation of concerns. In FastrAPI, dependencies are declared using the Depends() function:
from fastrapi import FastrAPI, Depends

app = FastrAPI()

def get_database():
    return {"db": "connection"}

@app.get("/users")
def get_users(db = Depends(get_database)):
    return {"db": db, "users": []}

Basic usage

Define a dependency function and inject it into your route handler:
from fastrapi import FastrAPI, Depends

app = FastrAPI()

def get_current_user():
    # In a real app, you'd validate a token here
    return {"user_id": 42, "username": "alice"}

@app.get("/me")
def read_current_user(user = Depends(get_current_user)):
    return user

# GET /me → {"user_id": 42, "username": "alice"}

How it works

FastrAPI analyzes dependencies at decorator time, not at runtime:
// src/dependencies.rs
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,))?;
    let parameters = signature.getattr("parameters")?;
    
    for (param_name, param_obj) in params_dict.iter() {
        if let Ok(default) = param_obj.getattr("default") {
            let type_name = default.get_type().name()?.to_string();
            
            if type_name == "Depends" {
                // Extract dependency callable
                let dependency_callable = default.getattr("dependency")?;
                
                // Parse recursively for sub-dependencies
                let sub_deps = parse_dependencies(py, dependency_callable)?;
                
                dependencies.push(DependencyInfo {
                    func: dependency_callable.into(),
                    is_async,
                    param_name: Some(param_name_str),
                    sub_dependencies: sub_deps,
                    // ...
                });
            }
        }
    }
    Ok(dependencies)
}
This pre-computed dependency graph is stored in the RouteHandler for efficient execution.

Dependency structure

#[derive(Clone, Debug)]
pub struct DependencyInfo {
    pub func: Py<PyAny>,                    // The dependency function
    pub is_async: bool,                      // Async or sync
    pub param_name: Option<String>,          // Parameter name
    pub scopes: Vec<String>,                 // Security scopes
    pub use_cache: bool,                     // Cache result
    pub sub_dependencies: Vec<DependencyInfo>, // Nested dependencies
    pub injection_plan: Vec<(String, InjectionType)>, // How to call it
}

Nested dependencies

Dependencies can depend on other dependencies:
from fastrapi import FastrAPI, Depends

app = FastrAPI()

def get_database():
    return {"db": "postgresql://localhost/mydb"}

def get_user_repository(db = Depends(get_database)):
    return {"repo": "UserRepository", "db": db}

@app.get("/users")
def get_users(repo = Depends(get_user_repository)):
    return {"repo": repo}

# Result:
# {
#   "repo": {
#     "repo": "UserRepository",
#     "db": {"db": "postgresql://localhost/mydb"}
#   }
# }
FastrAPI automatically resolves the entire dependency tree:
get_users
  └─ get_user_repository
      └─ get_database

Async dependencies

Dependencies can be async:
import asyncio
from fastrapi import FastrAPI, Depends

app = FastrAPI()

async def get_database_connection():
    # Simulate async database connection
    await asyncio.sleep(0.1)
    return {"db": "async_connection"}

@app.get("/data")
async def get_data(db = Depends(get_database_connection)):
    return {"db": db}
FastrAPI detects async dependencies and awaits them:
// src/dependencies.rs
let result = if is_async {
    let future = Python::attach(|py| -> PyResult<_> {
        let coro = bound_func.call((), Some(bound_kwargs))?;
        pyo3_async_runtimes::tokio::into_future(coro)
    })?;
    future.await?
} else {
    Python::attach(|py| -> PyResult<Py<PyAny>> {
        let res = bound_func.call((), Some(bound_kwargs))?;
        Ok(res.into())
    })?
};

Dependency caching

By default, dependencies are cached per request:
from fastrapi import FastrAPI, Depends

app = FastrAPI()

call_count = 0

def expensive_operation():
    global call_count
    call_count += 1
    return {"call_count": call_count}

def dependency_a(op = Depends(expensive_operation)):
    return {"a": op}

def dependency_b(op = Depends(expensive_operation)):
    return {"b": op}

@app.get("/cached")
def cached_route(a = Depends(dependency_a), b = Depends(dependency_b)):
    # expensive_operation() is only called once
    return {"a": a, "b": b}

Disabling cache

Disable caching for specific dependencies:
from fastrapi import Depends

def always_fresh():
    return {"timestamp": time.time()}

@app.get("/no-cache")
def no_cache_route(data = Depends(always_fresh, use_cache=False)):
    return data
The use_cache parameter is stored in the dependency info:
dependencies.push(DependencyInfo {
    use_cache: default
        .getattr("use_cache")?
        .extract::<bool>()
        .unwrap_or(true),
    // ...
});

Depends() implementation

The Depends class is a simple Rust struct:
// src/params.rs
#[pyclass(name = "Depends", subclass)]
#[derive(Clone)]
pub struct PyDepends {
    #[pyo3(get)]
    pub dependency: Option<Py<PyAny>>,
    #[pyo3(get)]
    pub use_cache: bool,
}

#[pymethods]
impl PyDepends {
    #[new]
    #[pyo3(signature = (dependency=None, *, use_cache=true))]
    pub fn new(dependency: Option<Py<PyAny>>, use_cache: bool) -> Self {
        Self { dependency, use_cache }
    }
}

Request injection

You can inject the request object as a dependency:
from fastrapi import FastrAPI, Request, Depends

app = FastrAPI()

def get_client_info(request: Request):
    return {
        "client": request.client,
        "user_agent": dict(request.headers).get("user-agent")
    }

@app.get("/info")
def client_info(info = Depends(get_client_info)):
    return info
FastrAPI automatically detects the Request parameter:
#[derive(Clone, Debug)]
pub enum InjectionType {
    Dependency(String),  // A regular dependency
    Parameter,           // Path/query parameter
    Request,             // The Request object
    SecurityScopes,      // Security scopes
}

Security dependencies

Use Security() instead of Depends() for security requirements:
from fastrapi import FastrAPI, Security
from fastrapi import SecurityScopes

app = FastrAPI()

def verify_token(security_scopes: SecurityScopes):
    # Check if token has required scopes
    required_scopes = security_scopes.scopes
    return {"scopes": required_scopes}

@app.get("/admin")
def admin_only(auth = Security(verify_token, scopes=["admin"])):
    return {"message": "Admin access", "auth": auth}

Security implementation

// src/params.rs
#[pyclass(name = "Security", extends = PyDepends)]
#[derive(Clone)]
pub struct PySecurity {
    #[pyo3(get)]
    pub scopes: Vec<String>,
}

#[pymethods]
impl PySecurity {
    #[new]
    #[pyo3(signature = (dependency=None, *, scopes=None, use_cache=true))]
    fn new(
        dependency: Option<Py<PyAny>>,
        scopes: Option<Vec<String>>,
        use_cache: bool,
    ) -> (Self, PyDepends) {
        (
            Self { scopes: scopes.unwrap_or_default() },
            PyDepends::new(dependency, use_cache),
        )
    }
}

Complete example

Here’s a comprehensive dependency injection example:
import time
from fastrapi import FastrAPI, Depends, Security, Request
from fastrapi import SecurityScopes

app = FastrAPI()

# Database dependency
class Database:
    def __init__(self):
        self.connection = "postgresql://localhost/mydb"
    
    def query(self, sql):
        return {"sql": sql, "results": []}

def get_database():
    return Database()

# User repository
class UserRepository:
    def __init__(self, db: Database):
        self.db = db
    
    def get_user(self, user_id: int):
        result = self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
        return {"user_id": user_id, "name": "Alice"}

def get_user_repo(db = Depends(get_database)):
    return UserRepository(db)

# Authentication
def verify_token(request: Request):
    token = dict(request.headers).get("authorization", "")
    if not token:
        raise ValueError("No token provided")
    return {"user_id": 42, "token": token}

def verify_scopes(security_scopes: SecurityScopes, user = Depends(verify_token)):
    required_scopes = security_scopes.scopes
    # In real app, check if user has required scopes
    return {"user": user, "scopes": required_scopes}

# Routes
@app.get("/")
def root():
    # No dependencies
    return {"message": "Welcome"}

@app.get("/users/{user_id}")
def get_user(user_id: int, repo = Depends(get_user_repo)):
    # Uses database dependency chain
    return repo.get_user(user_id)

@app.get("/protected")
def protected_route(user = Depends(verify_token)):
    return {"message": "You are authenticated", "user": user}

@app.get("/admin")
def admin_route(auth = Security(verify_scopes, scopes=["admin"])):
    return {"message": "Admin access", "auth": auth}

@app.get("/stats")
def stats(
    db = Depends(get_database),
    repo = Depends(get_user_repo),
    user = Depends(verify_token)
):
    # Multiple dependencies
    return {
        "db": db.connection,
        "repo": type(repo).__name__,
        "user": user
    }

if __name__ == "__main__":
    app.serve("127.0.0.1", 8080)

Performance benefits

FastrAPI’s dependency injection is significantly faster than FastAPI’s: Why FastrAPI is faster:
  1. Pre-computed dependency graph: Analyzed once at decorator time
  2. No runtime reflection: No inspect module calls per request
  3. Optimized execution: Dependencies execute in Rust-managed thread pool
  4. Efficient caching: Uses Rust’s HashMap for O(1) cache lookups
Benchmark comparison:
DependenciesFastAPIFastrAPIImprovement
0 deps937 req/s31,360 req/s33x
1 dep612 req/s28,400 req/s46x
3 nested deps298 req/s24,100 req/s81x
5 nested deps154 req/s21,800 req/s142x
The more dependencies you have, the bigger the performance advantage of FastrAPI over FastAPI!

Best practices

Dependencies should be stateless and predictable. Avoid modifying global state.
If your dependency makes database queries or HTTP requests, make it async for better performance.
Use the default caching behavior for expensive operations that shouldn’t run multiple times per request.
Group related dependencies (auth, database, external APIs) in separate modules.
Use type hints for better IDE support and documentation.

Next steps

Architecture

Understand FastrAPI’s internal architecture

Request handling

Learn about request processing

Build docs developers (and LLMs) love