Skip to main content

AppBuilder

Struct AppBuilder 

Source
pub struct AppBuilder<S: StateRegistry = ()> { /* private fields */ }
Expand description

Builder for constructing an App.

Use this to configure routes, middleware, and shared state before building the final application.

§Example

let app = App::builder()
    .config(AppConfig::new().name("My API"))
    .state(DatabasePool::new())
    .middleware(LoggingMiddleware::new())
    .on_startup(|| {
        println!("Server starting...");
        Ok(())
    })
    .on_shutdown(|| {
        println!("Server stopping...");
    })
    .route("/", Method::Get, index_handler)
    .route("/items", Method::Get, list_items)
    .route("/items", Method::Post, create_item)
    .route("/items/{id}", Method::Get, get_item)
    .build();

§Type-Safe State

The AppBuilder uses a type-state pattern to track registered state types at compile time. The generic parameter S represents the type-level set of registered state types.

When you call .with_state::<T>(value), the builder’s type changes from AppBuilder<S> to AppBuilder<(T, S)>, recording that T is now available.

Handlers that use State<T> extractors can optionally be constrained to require S: HasState<T>, ensuring the state is registered at compile time.

// Type changes: AppBuilder<()> -> AppBuilder<(DbPool, ())> -> AppBuilder<(Config, (DbPool, ()))>
let app = App::builder()
    .with_state(DbPool::new())  // Now has DbPool
    .with_state(Config::default())  // Now has DbPool + Config
    .build();

Implementations§

Source§

impl AppBuilder<()>

Source

pub fn new() -> Self

Creates a new application builder with no registered state.

Source§

impl<S: StateRegistry> AppBuilder<S>

Source

pub fn config(self, config: AppConfig) -> Self

Sets the application configuration.

Source

pub fn route<H, Fut>( self, path: impl Into<String>, method: Method, handler: H, ) -> Self
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a route to the application.

Routes are matched in the order they are added.

Source

pub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a GET route.

Source

pub fn post<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a POST route.

Source

pub fn put<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a PUT route.

Source

pub fn delete<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a DELETE route.

Source

pub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
where H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Response> + Send + 'static,

Adds a PATCH route.

Source

pub fn middleware<M: Middleware + 'static>(self, middleware: M) -> Self

Adds middleware to the application.

Middleware is executed in the order it is added:

  • before hooks run first-to-last
  • after hooks run last-to-first
Source

pub fn include_router(self, router: APIRouter) -> Self

Includes routes from an APIRouter.

This adds all routes from the router to the application, applying the router’s prefix, tags, and dependencies.

§Example
use fastapi_core::api_router::APIRouter;

let users_router = APIRouter::new()
    .prefix("/users")
    .get("", list_users)
    .get("/{id}", get_user);

let app = App::builder()
    .include_router(users_router)
    .build();
Source

pub fn include_router_with_config( self, router: APIRouter, config: IncludeConfig, ) -> Self

Includes routes from an APIRouter with configuration.

This allows applying additional configuration when including a router, such as prepending a prefix, adding tags, or injecting dependencies.

§Example
use fastapi_core::api_router::{APIRouter, IncludeConfig};

let users_router = APIRouter::new()
    .prefix("/users")
    .get("", list_users);

let config = IncludeConfig::new()
    .prefix("/api/v1")
    .tags(vec!["api"]);

let app = App::builder()
    .include_router_with_config(users_router, config)
    .build();
Source

pub fn mount(self, prefix: impl Into<String>, app: App) -> Self

Mounts a sub-application at a path prefix.

Mounted applications are completely independent from the parent:

  • They have their own middleware stack (parent middleware does NOT apply)
  • They have their own state and configuration
  • Their OpenAPI schemas are NOT merged with the parent
  • Request paths have the prefix stripped before being passed to the sub-app

This differs from include_router, which merges routes into the parent app and applies parent middleware.

§Use Cases
  • Mount admin panels at /admin
  • Mount Swagger UI at /docs
  • Mount static file servers
  • Integrate legacy or third-party apps
§Path Stripping Behavior

When a request arrives at /admin/users, and an app is mounted at /admin, the sub-app receives the request with path /users.

§Example
use fastapi_core::app::App;

// Create an admin sub-application
let admin_app = App::builder()
    .get("/users", admin_list_users)
    .get("/settings", admin_settings)
    .middleware(AdminAuthMiddleware::new())
    .build();

// Mount it at /admin
let main_app = App::builder()
    .get("/", home_page)
    .mount("/admin", admin_app)
    .build();

// Now:
// - GET /           -> home_page
// - GET /admin/users -> admin_list_users (with AdminAuthMiddleware)
// - GET /admin/settings -> admin_settings (with AdminAuthMiddleware)
Source

pub fn mounted_app_count(&self) -> usize

Returns the number of mounted sub-applications.

Source

pub fn state<T: Send + Sync + 'static>(self, value: T) -> Self

👎Deprecated since 0.2.0: Use with_state for compile-time state type verification

Adds shared state to the application (legacy method).

State can be accessed by handlers through the State<T> extractor.

Note: This method is deprecated in favor of with_state, which provides compile-time verification that state types are registered.

Source

pub fn with_state<T: Send + Sync + 'static>( self, value: T, ) -> AppBuilder<(T, S)>

Adds typed state to the application with compile-time registration.

This method registers state using a type-state pattern, which enables compile-time verification that state types are properly registered before they are used by handlers.

The return type changes from AppBuilder<S> to AppBuilder<(T, S)>, recording that type T is now available in the state registry.

§Example
use fastapi_core::app::App;

struct DbPool { /* ... */ }
struct Config { api_key: String }

// Type evolves: () -> (DbPool, ()) -> (Config, (DbPool, ()))
let app = App::builder()
    .with_state(DbPool::new())    // Now has DbPool
    .with_state(Config::default()) // Now has DbPool + Config
    .build();
§Compile-Time Safety

When used with the RequiresState trait, handlers can declare their state dependencies and the compiler will verify they are met:

// This handler requires DbPool to be registered
fn handler_requiring_db<S: HasState<DbPool>>(app: AppBuilder<S>) { /* ... */ }
Source

pub fn override_dependency<T, F, Fut>(self, f: F) -> Self
where T: FromDependency, F: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static, Fut: Future<Output = Result<T, T::Error>> + Send + 'static,

Registers a dependency override for this application (useful in tests).

Source

pub fn override_dependency_value<T>(self, value: T) -> Self
where T: FromDependency,

Registers a fixed dependency override value.

Source

pub fn clear_dependency_overrides(self) -> Self

Clears all registered dependency overrides.

Source

pub fn exception_handler<E, H>(self, handler: H) -> Self
where E: Error + Send + Sync + 'static, H: Fn(&RequestContext, E) -> Response + Send + Sync + 'static,

Registers a custom exception handler for a specific error type.

When an error of type E occurs during request handling, the registered handler will be called to convert it into a response.

§Example
#[derive(Debug)]
struct AuthError(String);

impl std::fmt::Display for AuthError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Auth error: {}", self.0)
    }
}

impl std::error::Error for AuthError {}

let app = App::builder()
    .exception_handler(|_ctx, err: AuthError| {
        Response::with_status(StatusCode::UNAUTHORIZED)
            .header("www-authenticate", b"Bearer".to_vec())
            .body_json(&json!({"error": err.0}))
    })
    .build();
Source

pub fn exception_handlers(self, handlers: ExceptionHandlers) -> Self

Sets the exception handlers registry.

This replaces any previously registered handlers.

Source

pub fn with_default_exception_handlers(self) -> Self

Uses default exception handlers for common error types.

This registers handlers for:

Source

pub fn on_startup<F>(self, hook: F) -> Self
where F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,

Registers a synchronous startup hook.

Startup hooks run before the server starts accepting connections, in the order they are registered (FIFO).

§Example
let app = App::builder()
    .on_startup(|| {
        println!("Connecting to database...");
        Ok(())
    })
    .on_startup(|| {
        println!("Loading configuration...");
        Ok(())
    })
    .build();
Source

pub fn on_startup_async<F, Fut>(self, hook: F) -> Self
where F: FnOnce() -> Fut + Send + 'static, Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,

Registers an async startup hook.

Async startup hooks are awaited in registration order.

§Example
let app = App::builder()
    .on_startup_async(|| async {
        let pool = connect_to_database().await?;
        Ok(())
    })
    .build();
Source

pub fn on_shutdown<F>(self, hook: F) -> Self
where F: FnOnce() + Send + 'static,

Registers a synchronous shutdown hook.

Shutdown hooks run after the server stops accepting connections and all in-flight requests complete (or are cancelled).

Shutdown hooks run in reverse registration order (LIFO), matching typical resource cleanup patterns (last acquired, first released).

§Example
let app = App::builder()
    .on_shutdown(|| {
        println!("Closing database connections...");
    })
    .build();
Source

pub fn on_shutdown_async<F, Fut>(self, hook: F) -> Self
where F: FnOnce() -> Fut + Send + 'static, Fut: Future<Output = ()> + Send + 'static,

Registers an async shutdown hook.

Async shutdown hooks are awaited in reverse registration order (LIFO).

§Example
let app = App::builder()
    .on_shutdown_async(|| async {
        flush_metrics().await;
    })
    .build();
Source

pub fn lifespan<F, Fut, T>(self, lifespan_fn: F) -> Self
where F: FnOnce() -> Fut + Send + 'static, Fut: Future<Output = Result<LifespanScope<T>, LifespanError>> + Send + 'static, T: Send + Sync + 'static,

Registers a lifespan context manager for async startup/shutdown.

The lifespan pattern is preferred over separate on_startup/on_shutdown hooks because it allows sharing state between the startup and shutdown phases. This is especially useful for resources like database connections, HTTP clients, or background task managers.

The lifespan function runs during application startup. It should:

  1. Initialize resources (connect to database, start background tasks, etc.)
  2. Return a LifespanScope containing:
    • State to be added to the application (accessible via State<T> extractor)
    • An optional cleanup closure to run during shutdown

If the lifespan function returns an error, application startup is aborted.

Note: When a lifespan is provided, it runs before any on_startup hooks. The lifespan cleanup runs after all on_shutdown hooks during shutdown.

§Example
use fastapi_core::app::{App, LifespanScope, LifespanError};

#[derive(Clone)]
struct DatabasePool { /* ... */ }

impl DatabasePool {
    async fn connect(url: &str) -> Result<Self, Error> { /* ... */ }
    async fn close(&self) { /* ... */ }
}

let app = App::builder()
    .lifespan(|| async {
        // Startup: connect to database
        println!("Connecting to database...");
        let pool = DatabasePool::connect("postgres://localhost/mydb")
            .await
            .map_err(|e| LifespanError::with_source("database connection failed", e))?;

        // Clone for use in cleanup
        let pool_for_cleanup = pool.clone();

        // Return state and cleanup
        Ok(LifespanScope::new(pool)
            .on_shutdown(async move {
                println!("Closing database connections...");
                pool_for_cleanup.close().await;
            }))
    })
    .get("/users", get_users)  // Handler can use State<DatabasePool>
    .build();
§Multiple State Types

To provide multiple state types from a single lifespan, use a tuple or define a struct containing all your state:

#[derive(Clone)]
struct AppState {
    db: DatabasePool,
    cache: RedisClient,
    config: AppConfig,
}

let app = App::builder()
    .lifespan(|| async {
        let db = DatabasePool::connect("...").await?;
        let cache = RedisClient::connect("...").await?;
        let config = load_config().await?;

        let state = AppState { db, cache, config };
        let state_for_cleanup = state.clone();

        Ok(LifespanScope::new(state)
            .on_shutdown(async move {
                state_for_cleanup.db.close().await;
                state_for_cleanup.cache.close().await;
            }))
    })
    .build();
Source

pub fn has_lifespan(&self) -> bool

Returns true if a lifespan function has been registered.

Source

pub fn startup_hook_count(&self) -> usize

Returns the number of registered startup hooks.

Source

pub fn shutdown_hook_count(&self) -> usize

Returns the number of registered shutdown hooks.

Source

pub fn build(self) -> App

Builds the application.

This consumes the builder and returns the configured App.

Trait Implementations§

Source§

impl<S: StateRegistry> Debug for AppBuilder<S>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for AppBuilder<()>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<S> Freeze for AppBuilder<S>

§

impl<S = ()> !RefUnwindSafe for AppBuilder<S>

§

impl<S> Send for AppBuilder<S>

§

impl<S = ()> !Sync for AppBuilder<S>

§

impl<S> Unpin for AppBuilder<S>
where S: Unpin,

§

impl<S = ()> !UnwindSafe for AppBuilder<S>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, _span: NoopSpan) -> Self

Instruments this future with a span (no-op when disabled).
Source§

fn in_current_span(self) -> Self

Instruments this future with the current span (no-op when disabled).
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ResponseProduces<T> for T