Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 30

Function-Based Dependencies with `Depends()`

Recommended resources

Hello! Welcome back.

In our last lesson, we introduced the concept of Dependency Injection (DI). We established that it's FastAPI's unified system for handling tasks that in Django and DRF would be spread across middleware, permission classes, and mixins. You learned the why—that DI promotes clean, decoupled, and testable code by separating a function's logic from the creation of its dependencies.

Today, we move from theory to practice. This lesson is all about the how. We will address the learning outcome: Create and use simple function-based dependencies with Depends(). You'll write your first dependencies and see firsthand how they streamline your path operations.

1. The Core Idea: Don't Repeat Yourself (DRY)

One of the most immediate benefits of dependencies is adhering to the "Don't Repeat Yourself" (DRY) principle. Imagine you have several endpoints that list different resources (e.g., users, items, posts), and you want to offer pagination for all of them.

Without dependencies, you would have to repeat the pagination query parameters (skip and limit) in every single path operation function:




# The "wet" (opposite of DRY) way
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 100):



    # ... logic to fetch items ...

@app.get("/users/")
async def read_users(skip: int = 0, limit: int = 100):



    # ... logic to fetch users ...

This is repetitive and error-prone. What if you want to change the default limit? You'd have to find and update it everywhere.

A dependency solves this cleanly. Let's see how.

Fast API Tutorial, Part 22: Dependencies - Intro

This video from JVP Design provides a perfect introduction to dependencies by solving this exact pagination problem.

Watch from the beginning to 04:53. The video will first show the problem of duplicated query parameters and then refactor that logic into a simple common_parameters function-based dependency.

As you saw in the video, we can create a single function to handle these common parameters:

from fastapi import Depends, FastAPI

app = FastAPI()




# This is our dependency
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}

@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return {"message": "Reading items", "params": commons}

@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
    return {"message": "Reading users", "params": commons}

2. The Depends Syntax Explained

The key to the whole system is fastapi.Depends. Let's break down the syntax from the example above:

commons: dict = Depends(common_parameters)

  • Depends(common_parameters): This is a "marker" that tells FastAPI: "Before you run this path operation, you need to execute the common_parameters function." FastAPI will then take the value returned by common_parameters and pass it to the path operation.
  • commons: dict: This is a standard Python parameter definition. The return value from the dependency will be assigned to the commons argument. The type hint (dict) helps with code completion and static analysis.

Essentially, Depends lets you declare that your path operation depends on the result of another function.


3. A Familiar Pattern: Get Object or 404

In our previous lesson, we compared dependencies to DRF's get_object method, which is commonly used in generic views to fetch a model instance or raise a 404 error. Let's implement this exact pattern in FastAPI. This is a powerful technique for abstracting away boilerplate database logic from your endpoints.

FastAPI for Django Programmers: Dependency Injections ...

The article 'FastAPI for Django Programmers' we looked at last time has an excellent, concise example of this pattern.

Please read the section under the 'Dependency Injection' heading. Focus on the code for the get_record dependency and how it's used in the read_item path operation. Note how the dependency itself raises the HTTPException.

Let's analyze the code from the article:

The Dependency:




# Dependency function
async def get_record(person_id: int):
 record = await Person.get(person_id) # Hypothetical ORM call
 if record is None:
    raise HTTPException(status_code=404, detail="Item not found")
 return record

Notice two crucial things:

  1. It has its own parameter: person_id: int. FastAPI is smart enough to see this and pass the person_id from the URL path directly to our dependency.
  2. It contains the logic: It fetches the data and handles the "not found" error case.

The Path Operation:




# FastAPI function
@app.get("/items/{person_id}")
async def read_item(person: Person = Depends(get_record)):
 return item

The read_item function is now incredibly clean. Its only job is to work with the person object. It can trust that if the code executes, person is a valid object from the database. This is a perfect example of separation of concerns.


4. Chaining Dependencies

The power of DI truly shines when you realize that dependencies can depend on other dependencies. This allows you to build up complex logic from small, reusable, and testable pieces.

A canonical example is user authentication. To get the current user, you might need to:

  1. Get a database session.
  2. Get the auth token from the request header.
  3. Decode the token to get a user ID.
  4. Use the user ID and the database session to fetch the user object.

Instead of doing all of this in one giant function, you can break it into a chain of dependencies.

Role-Based Access Control Using Dependency Injection (User Roles) - FastAPI Beyond CRUD (Part 13)

This video by Ssali Jonathan provides a fantastic, real-world example of creating a get_current_user dependency that itself relies on other dependencies for the database session and the auth token.

Watch from 02:15 to 12:42. Pay close attention to the signature of the get_current_user function. Notice how it uses Depends() multiple times to inject both the token and the database session it needs to do its job. Then, see how cleanly this new dependency is used in the /me endpoint.

The video demonstrates a common professional pattern. The get_current_user dependency might look something like this:




# Dependency to get a DB session (from our previous lessons)
async def get_session():



    # ... logic to create and yield a session ...




# Dependency to get the token from the header
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")




# The dependency that chains the others
async def get_current_user(
    session: AsyncSession = Depends(get_session),
    token: str = Depends(oauth2_scheme)
):



    # 1. Decode the token to get user identifier
    # 2. Use the 'session' to fetch the user from the DB
    # 3. Return the user object (or raise HTTPException)
    ...
    return user_from_db




# The final, clean path operation
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
    return current_user

This hierarchical structure is what makes FastAPI's DI system so composable.

FastAPI Dependency Chain for User Roles
This diagram illustrates a dependency chain for authentication. A top-level dependency like `current_user` can rely on other dependencies, which can branch out to create specific access rules (like `admin_user` or `paying_user`) for different endpoints.

Key Takeaways

  • A dependency is a regular Python function (or other callable) that FastAPI runs before your path operation.
  • You use variable_name: Type = Depends(dependency_function) in your endpoint's signature to use a dependency.
  • FastAPI inspects the dependency's signature and provides it with any path parameters, query parameters, or bodies it needs.
  • Dependencies are perfect for abstracting away repeated logic (DRY), such as handling pagination, fetching objects from a database, or validating API keys.
  • You can create powerful and modular logic by chaining dependencies, where one dependency calls another using Depends().

Preview of the Next Lesson:

Function-based dependencies are excellent for many use cases. However, what if your dependency needs some configuration? For example, an authentication dependency might need to know which user roles are allowed. Passing this configuration to a simple function can be awkward.

In the next lesson, "Build reusable, parameterized dependencies using callable classes", we will explore how to use classes as dependencies. This will give us a way to create configurable and stateful dependencies, unlocking even more advanced patterns.