Architecture

This page explains how fastapi-lazyrouter works under the hood.

The cold-start problem

FastAPI builds an OpenAPI schema for every route at import time. When a route declares Pydantic response models, Pydantic must generate the JSON schema for those models. If your application contains hundreds of nested models, this process can take several seconds.

On serverless platforms such as AWS Lambda the runtime kills the container if it does not respond to health checks within ~10 seconds. A large FastAPI application can therefore fail to cold-start.

Lazy router principle

fastapi-lazyrouter splits the work into two phases:

  1. Declaration phase (import time) – route decorators such as @router.get(...) are intercepted. The arguments are stored in an internal list instead of being registered on the underlying APIRouter immediately. Because the routes are never added to the router, FastAPI has no models to scan and Pydantic does no work.

  2. Mount phase (first request) – the LazyApiRouterMiddleware inspects every incoming request. When the request path starts with a lazy router’s prefix, the middleware calls LazyApiRouter._lazy_mount(), which:

    • iterates over the stored route declarations,

    • calls the real APIRouter.add_api_route for each one,

    • calls app.include_router(self) so that FastAPI finally sees the routes,

    • marks the router as loaded so that the operation happens only once.

Global registry

Every LazyApiRouter instance appends itself to the module-level list _ALL_LAZY_ROUTERS. The middleware loops over this list on each request to find routers whose prefix matches the current path. The registry can be cleared with LazyApiRouter.reset_registry(), which is useful in test suites that create many application instances.

Auto-configuration

autoconfigure() makes the library almost transparent:

  1. It adds LazyApiRouterMiddleware to the FastAPI app if it is not already present.

  2. It monkey-patches app.include_router with a wrapper that converts plain APIRouter instances into LazyApiRouter instances before delegating to the original method.

After this patch you can keep using APIRouter in your code and still benefit from lazy loading.

Limitations and trade-offs

  • First-request latency – the very first request that hits a lazy router pays the cost of schema generation. In practice this is usually acceptable because only the router that is actually used is loaded.

  • OpenAPI completeness/docs and /openapi.json force FastAPI to enumerate all routes. The first request to those endpoints therefore triggers the mount of every lazy router, negating the cold-start benefit for that specific request.

  • Middleware orderingLazyApiRouterMiddleware should be the outermost middleware so that routing-dependent middlewares (e.g. authentication, CORS) operate on fully-mounted routes.