Skip to content

Dependency injection

When working on a Pavex application, you don't have to worry about wiring.
All the components in your application (request handlers, middlewares, error handlers, etc.) use their input parameters to declare the data they need to do their job. We refer to those input parameters as their dependencies. Pavex takes care of injecting (and sometimes building) those dependencies when and where they're needed.

We refer to this system as Pavex's dependency injection framework.

What is the purpose of dependency injection?

Let's look at an example: rejecting unauthenticated requests in a middleware.

The desired behavior:

  • If the user is logged in, the middleware lets the request through.
  • If the user isn't logged in, a 401 Unauthorized response is returned.
src/authentication.rs
use pavex::middleware::Processing;
use pavex::response::Response;

use crate::user::User;

pub fn reject_anonymous(user: &User) -> Processing
{
    if let User::Anonymous = user {
        let r = Response::unauthorized();
        Processing::EarlyReturn(r)
    } else {
        Processing::Continue
    }
}

The middleware logic doesn't care about how authentication is performed. It only cares about the result: is the user authenticated or not?

The contract is data-driven: as long as the outcome of the authentication process doesn't change (i.e. the User type) the middleware will work as expected and doesn't need to be modified.
You won't have to touch middleware code if, in the future, you decide to migrate to a different authentication system (e.g. from username/password authentication to an OAuth2 flow).

This is the entire purpose of Pavex's dependency injection framework: decouple the way data is computed from the way it's used. The middleware doesn't care about how the User is computed, it only cares about what it is.

This is a simple example, but the same principle applies to a vast collection of use cases: body parsing, logging, authorization, etc.

Guide structure

There are three different sources for injectable dependencies: framework primitives, constructors and prebuilt types. Check out the respective sections for guidance on how to use each source.

We recommend going through the cookbook as well. It contains a collection of reference examples for common use cases: how to inject a function as a constructor, how to inject a non-static method, how to inject a trait object, generics, etc. Use it a reference in your day-to-day Pavex development if you're not sure of the syntax for a particular use case.