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<()>
impl AppBuilder<()>
Source§impl<S: StateRegistry> AppBuilder<S>
impl<S: StateRegistry> AppBuilder<S>
Sourcepub fn route<H, Fut>(
self,
path: impl Into<String>,
method: Method,
handler: H,
) -> Self
pub fn route<H, Fut>( self, path: impl Into<String>, method: Method, handler: H, ) -> Self
Adds a route to the application.
Routes are matched in the order they are added.
Sourcepub fn middleware<M: Middleware + 'static>(self, middleware: M) -> Self
pub fn middleware<M: Middleware + 'static>(self, middleware: M) -> Self
Adds middleware to the application.
Middleware is executed in the order it is added:
beforehooks run first-to-lastafterhooks run last-to-first
Sourcepub fn include_router(self, router: APIRouter) -> Self
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();Sourcepub fn include_router_with_config(
self,
router: APIRouter,
config: IncludeConfig,
) -> Self
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();Sourcepub fn mount(self, prefix: impl Into<String>, app: App) -> Self
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)Sourcepub fn mounted_app_count(&self) -> usize
pub fn mounted_app_count(&self) -> usize
Returns the number of mounted sub-applications.
Sourcepub fn state<T: Send + Sync + 'static>(self, value: T) -> Self
👎Deprecated since 0.2.0: Use with_state for compile-time state type verification
pub fn state<T: Send + Sync + 'static>(self, value: T) -> Self
with_state for compile-time state type verificationAdds 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.
Sourcepub fn with_state<T: Send + Sync + 'static>(
self,
value: T,
) -> AppBuilder<(T, S)>
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>) { /* ... */ }Sourcepub fn override_dependency<T, F, Fut>(self, f: F) -> Selfwhere
T: FromDependency,
F: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<T, T::Error>> + Send + 'static,
pub fn override_dependency<T, F, Fut>(self, f: F) -> Selfwhere
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).
Sourcepub fn override_dependency_value<T>(self, value: T) -> Selfwhere
T: FromDependency,
pub fn override_dependency_value<T>(self, value: T) -> Selfwhere
T: FromDependency,
Registers a fixed dependency override value.
Sourcepub fn clear_dependency_overrides(self) -> Self
pub fn clear_dependency_overrides(self) -> Self
Clears all registered dependency overrides.
Sourcepub fn exception_handler<E, H>(self, handler: H) -> Self
pub fn exception_handler<E, H>(self, handler: H) -> Self
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();Sourcepub fn exception_handlers(self, handlers: ExceptionHandlers) -> Self
pub fn exception_handlers(self, handlers: ExceptionHandlers) -> Self
Sets the exception handlers registry.
This replaces any previously registered handlers.
Sourcepub fn with_default_exception_handlers(self) -> Self
pub fn with_default_exception_handlers(self) -> Self
Uses default exception handlers for common error types.
This registers handlers for:
HttpError→ JSON response with status/detailValidationErrors→ 422 with error list
Sourcepub fn on_startup<F>(self, hook: F) -> Self
pub fn on_startup<F>(self, hook: F) -> Self
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();Sourcepub fn on_startup_async<F, Fut>(self, hook: F) -> Self
pub fn on_startup_async<F, Fut>(self, hook: F) -> Self
Sourcepub fn on_shutdown<F>(self, hook: F) -> Self
pub fn on_shutdown<F>(self, hook: F) -> Self
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();Sourcepub fn on_shutdown_async<F, Fut>(self, hook: F) -> Self
pub fn on_shutdown_async<F, Fut>(self, hook: F) -> Self
Sourcepub fn lifespan<F, Fut, T>(self, lifespan_fn: F) -> Selfwhere
F: FnOnce() -> Fut + Send + 'static,
Fut: Future<Output = Result<LifespanScope<T>, LifespanError>> + Send + 'static,
T: Send + Sync + 'static,
pub fn lifespan<F, Fut, T>(self, lifespan_fn: F) -> Selfwhere
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:
- Initialize resources (connect to database, start background tasks, etc.)
- Return a
LifespanScopecontaining:- State to be added to the application (accessible via
State<T>extractor) - An optional cleanup closure to run during shutdown
- State to be added to the application (accessible via
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();Sourcepub fn has_lifespan(&self) -> bool
pub fn has_lifespan(&self) -> bool
Returns true if a lifespan function has been registered.
Sourcepub fn startup_hook_count(&self) -> usize
pub fn startup_hook_count(&self) -> usize
Returns the number of registered startup hooks.
Sourcepub fn shutdown_hook_count(&self) -> usize
pub fn shutdown_hook_count(&self) -> usize
Returns the number of registered shutdown hooks.