Skip to content

App

The application crate is where most of your code lives. In particular, it's where you define your Blueprint.

Note

As a convention, the application crate is named app.
You can use {project_name}_app if you need to disambiguate between multiple Pavex applications in the same workspace.

Blueprint

Every Pavex project has, at its core, a Blueprint. It's the type you use to declare the structure of your API: routes, middlewares, constructors, error handlers, error observers, configuration, etc.

app/src/blueprint.rs
// [...]
pub fn blueprint() -> Blueprint {
    let mut bp = Blueprint::new();
    ApiKit::new().register(&mut bp);
    telemetry::register(&mut bp);
    configuration::register(&mut bp);

    routes::register(&mut bp);
    bp
}

Think of a Blueprint as the specification for your API, a plan for how your application should behave at runtime.

But you can't run or execute a Blueprint as-is. That's where code generation comes in.

Code generation

To convert a Blueprint into an executable toolkit, you need pavex generate. It's a CLI command that takes a Blueprint as input and outputs a Rust crate, the server SDK for your Pavex project.

cargo-px

If you went through the Quickstart tutorial, you might be wondering: I've never run pavex generate! How comes my project worked?

That's thanks to cargo-px!
If you look into the Cargo.toml manifest for the server_sdk crate in the demo project, you'll find this section:

# [...]
[package.metadata.px.generate]
generator_type = "cargo_workspace_binary"
generator_name = "bp"

It's a cargo-px configuration section.
The server_sdk crate is telling cargo-px to generate the whole crate by executing a binary called bp (short for blueprint) from the current Cargo workspace.

That binary is defined in the demo crate:

app/src/bin/bp.rs
use app::blueprint;
use cargo_px_env::generated_pkg_manifest_path;
use pavex_cli_client::Client;
use std::env::args;
use std::error::Error;

/// Generate the `server_sdk` crate using Pavex's CLI.
///
/// Pavex will automatically wire all our routes, constructors and error handlers
/// into a "server SDK" that can be used by the final API server binary to launch
/// the application.
///
/// If `--check` is passed as an argument, it only verifies that the server SDK
/// crate is up-to-date. An error is returned if it isn't.
fn main() -> Result<(), Box<dyn Error>> {
    let generated_dir = generated_pkg_manifest_path()?.parent().unwrap().into();
    let mut cmd = Client::new().generate(blueprint(), generated_dir);
    if args().any(|arg| arg == "--check") {
        cmd = cmd.check()
    };
    if let Err(e) = cmd.execute() {
        eprintln!("{e}");
        std::process::exit(1);
    }
    Ok(())
}

Client::generate takes care of serializing the Blueprint and passing it as input to pavex generate.

All this is done automatically for you when you run cargo px build or cargo px run. cargo-px examines all the crates in your workspace, generates the ones that need it, and then goes on to complete the build process.