Skip to main content

Next.js-style file-based routing for FastAPI

Project description

fastapi-filebased-routing

Next.js-style file-based routing for FastAPI

PyPI version Python License: MIT

Define your API routes through directory structure and convention, not manual registration. Create a route.py file in a directory and it becomes an endpoint automatically. Say goodbye to router boilerplate and route registration conflicts.

Table of Contents

๐Ÿš€ Installation & Quickstart

Requires Python 3.10+ and FastAPI 0.65.0+.

pip install fastapi-filebased-routing
from fastapi import FastAPI
from fastapi_filebased_routing import create_router_from_path

app = FastAPI()
app.include_router(create_router_from_path("app"))

That's it. Every route.py file in your app/ directory is automatically discovered and registered.

๐Ÿ“ File-Based Routing Explained

Your directory structure defines your URL routes. Given create_router_from_path("app"):

app/
โ”œโ”€โ”€ _middleware.py                  # directory middleware โ†’ ALL routes
โ”œโ”€โ”€ health/
โ”‚   โ””โ”€โ”€ route.py                   # get โ†’ /health
โ”œโ”€โ”€ api/
โ”‚   โ”œโ”€โ”€ _middleware.py             # directory middleware โ†’ /api/**
โ”‚   โ”œโ”€โ”€ [[version]]/
โ”‚   โ”‚   โ””โ”€โ”€ route.py              # โ†’ /api and /api/{version}
โ”‚   โ””โ”€โ”€ users/
โ”‚       โ”œโ”€โ”€ route.py              # file-level middleware + handlers
โ”‚       โ””โ”€โ”€ [user_id]/
โ”‚           โ””โ”€โ”€ route.py          # handler-level middleware via class
โ”œโ”€โ”€ files/
โ”‚   โ””โ”€โ”€ [...path]/
โ”‚       โ””โ”€โ”€ route.py              # catch-all route
โ”œโ”€โ”€ ws/
โ”‚   โ””โ”€โ”€ chat/
โ”‚       โ””โ”€โ”€ route.py              # websocket handler
โ””โ”€โ”€ (admin)/                       # group: excluded from URL
    โ”œโ”€โ”€ _middleware.py             # directory middleware โ†’ /settings/**
    โ””โ”€โ”€ settings/
        โ””โ”€โ”€ route.py              # โ†’ /settings

Each route.py exports route handlers. Each _middleware.py defines directory middleware.

Route Conventions

Convention Route Example URL Handler Parameter
users/ app/users/route.py /users โ€”
[id]/ app/users/[id]/route.py /users/123 id: str
[[version]]/ app/api/[[version]]/route.py /api and /api/v2 version: str | None
[...path]/ app/files/[...path]/route.py /files/a/b/c path: str
(group)/ app/(admin)/settings/route.py /settings โ€”

Files: route.py contains handlers. _middleware.py contains directory middleware that cascades to all subdirectories.

๐Ÿ“ Route Handlers

Each route.py exports handlers. Supported HTTP methods: get, post, put, patch, delete, head, options, websocket. Functions prefixed with _ are private helpers and ignored. Default status codes: POST โ†’ 201, DELETE โ†’ 204, all others โ†’ 200.

# app/api/users/route.py
from fastapi_filebased_routing import route

# Module-level metadata (applies to all handlers in this file)
TAGS = ["users"]                         # auto-derived from first path segment if omitted
SUMMARY = "User management endpoints"    # OpenAPI summary
DEPRECATED = True                        # mark all handlers as deprecated

# File-level middleware (applies to all handlers in this file, does NOT cascade to subdirectories)
middleware = [rate_limit(100)]

# Simple handler โ€” just a function
async def get():
    """List users."""
    return {"users": []}

# Configured handler โ€” per-handler control over metadata and middleware
class post(route):
    status_code = 200                    # override convention-based 201
    tags = ["admin"]                     # override module-level TAGS
    summary = "Create a user"            # override module-level SUMMARY
    deprecated = True                    # override module-level DEPRECATED
    middleware = [require_role("admin")]  # or use inline: async def middleware(request, call_next): ...

    async def handler(name: str):
        return {"name": name}

Both styles coexist freely. Directory bracket names (e.g., [user_id]) become path parameters automatically injected into handler signatures. See examples/ for complete working projects.

๐Ÿ”— Middleware

Three-layer middleware system that lets you scope cross-cutting concerns (auth, logging, rate limiting) to directories, files, or individual handlers. Middleware is validated at startup and assembled into chains with zero per-request overhead.

All middleware functions use the same signature:

async def my_middleware(request, call_next):
    # Before handler
    response = await call_next(request)
    # After handler
    return response

Middleware must be async. Sync middleware raises a validation error at startup.

Directory-Level Middleware

Create a _middleware.py file in any directory. Its middleware applies to all routes in that directory and all subdirectories. Use one of two forms:

List form โ€” multiple middleware functions:

# app/api/_middleware.py โ€” applies to all routes under /api/**
from app.auth import auth_required
from app.logging import request_logger

middleware = [auth_required, request_logger]

Single function form โ€” one inline middleware:

# app/_middleware.py โ€” root-level timing middleware
import time

async def middleware(request, call_next):
    start = time.monotonic()
    response = await call_next(request)
    response.headers["X-Response-Time"] = f"{time.monotonic() - start:.4f}"
    return response

Pick one form per file. If both are defined, standard Python name resolution applies โ€” the last assignment to middleware wins.

Directory middleware cascades: a _middleware.py in app/ applies to every route, while one in app/api/ only applies to routes under /api/. Parent middleware always runs before child middleware.

File-Level Middleware

Set middleware = [...] at the top of any route.py to apply middleware to all handlers in that file. Unlike directory middleware, file-level middleware does not cascade to subdirectories.

# app/api/users/route.py
middleware = [rate_limit(100)]  # applies to get and post below, not to /api/users/[user_id]

async def get():
    """List users. Rate limited."""
    return {"users": []}

async def post(name: str):
    """Create user. Rate limited."""
    return {"name": name}

Handler-Level Middleware

class handler(route): blocks support a middleware attribute โ€” as a list or a single inline function:

# app/api/orders/route.py
from fastapi_filebased_routing import route

class post(route):
    async def middleware(request, call_next):
        if not request.headers.get("X-Idempotency-Key"):
            from fastapi.responses import JSONResponse
            return JSONResponse(
                {"error": "X-Idempotency-Key header required"},
                status_code=400,
            )
        return await call_next(request)

    async def handler(order: dict):
        """Create order. Requires idempotency key."""
        return {"order_id": "abc-123", **order}

Execution Order

When a request hits a route with middleware at multiple levels, they execute in this order:

Directory middleware (root โ†’ leaf)
  โ†’ File-level middleware
    โ†’ Handler-level middleware
      โ†’ Handler function
    โ† Handler-level middleware
  โ† File-level middleware
โ† Directory middleware

Each middleware can modify the request before calling call_next, and modify the response after. Middleware can also short-circuit by returning a response without calling call_next:

async def auth_guard(request, call_next):
    if not request.headers.get("Authorization"):
        return JSONResponse({"error": "unauthorized"}, status_code=401)
    return await call_next(request)

๐Ÿ“ฆ Examples

See the examples/ directory for runnable projects:

  • basic/ โ€” Routing fundamentals: static, dynamic, CRUD
  • middleware/ โ€” All three middleware layers in action
  • advanced/ โ€” Optional params, catch-all, route groups, WebSockets

๐Ÿ“– API Reference

create_router_from_path

def create_router_from_path(
    base_path: str | Path,
    *,
    prefix: str = "",
) -> APIRouter

Create a FastAPI APIRouter from a directory of route.py files.

Parameters:

  • base_path (str | Path): Root directory containing route.py files
  • prefix (str, optional): URL prefix for all discovered routes (default: "")

Returns:

  • APIRouter: A FastAPI router with all discovered routes registered

Raises:

  • RouteDiscoveryError: If base_path doesn't exist or isn't a directory
  • RouteValidationError: If a route file has invalid exports or parameters
  • DuplicateRouteError: If two route files resolve to the same path+method
  • PathParseError: If a directory name has invalid syntax
  • MiddlewareValidationError: If a _middleware.py file fails to import or contains invalid middleware

Example:

from fastapi import FastAPI
from fastapi_filebased_routing import create_router_from_path

app = FastAPI()

# Basic usage
app.include_router(create_router_from_path("app"))

# With prefix
app.include_router(create_router_from_path("app", prefix="/api/v1"))

# Multiple routers
app.include_router(create_router_from_path("app/public"))
app.include_router(create_router_from_path("app/admin", prefix="/admin"))

route

Base class for handler-level middleware and metadata configuration. Uses a metaclass that returns a RouteConfig instead of a class.

from fastapi_filebased_routing import route

class get(route):
    middleware = [auth_required]
    tags = ["users"]
    summary = "Get user details"

    async def handler(user_id: str):
        return {"user_id": user_id}

# `get` is now a RouteConfig, not a class
# `get(user_id="123")` calls the handler directly

๐Ÿ’ก Why This Plugin?

FastAPI developers building medium-to-large APIs face these problems:

  1. Manual route registration is tedious. Every endpoint requires updating a centralized router file.
  2. Route discoverability degrades. Finding the handler for /api/v1/users/{id} requires searching across files.
  3. Middleware wiring is repetitive. Applying auth to 20 admin endpoints means 20 copies of Depends(require_admin).
  4. Full-stack developers experience friction. Next.js has file-based routing, FastAPI requires manual wiring.

This plugin solves all four with:

  • Zero-configuration route discovery from directory structure
  • Three-layer middleware system (directory, file, handler) with cascading inheritance
  • Next.js feature parity: dynamic params, optional params, catch-all, route groups
  • Convention over configuration for status codes, tags, and metadata
  • Battle-tested security (path traversal protection, symlink validation)
  • WebSocket support, sync and async handlers
  • Hot reload compatible (uvicorn --reload works out of the box)
  • Full mypy strict mode support, coexists with manual FastAPI routing

๐Ÿค Contributing

This plugin is extracted from a production codebase and is actively maintained. Issues, feature requests, and pull requests are welcome.

GitHub: https://github.com/rsmdt/fastapi-filebased-routing.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_filebased_routing-1.0.2.tar.gz (20.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_filebased_routing-1.0.2-py3-none-any.whl (24.9 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_filebased_routing-1.0.2.tar.gz.

File metadata

  • Download URL: fastapi_filebased_routing-1.0.2.tar.gz
  • Upload date:
  • Size: 20.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fastapi_filebased_routing-1.0.2.tar.gz
Algorithm Hash digest
SHA256 e3c9f4ab5142c973e5027236d9c5df50bd382b96b0089f9e8c141f1f629da997
MD5 e61e999ea9c88e482d7974c5ab8e0768
BLAKE2b-256 86f2ea88ce74b5c1d447831e5065f446a2532ce303352d2290a7d57311b45e5c

See more details on using hashes here.

File details

Details for the file fastapi_filebased_routing-1.0.2-py3-none-any.whl.

File metadata

  • Download URL: fastapi_filebased_routing-1.0.2-py3-none-any.whl
  • Upload date:
  • Size: 24.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.0 {"installer":{"name":"uv","version":"0.10.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fastapi_filebased_routing-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 bff0e52d7c29f910e0b5ca273d1c2fff31d89644019e1d6ff569c104bfb7218d
MD5 52d228976053bc7e5ce5a43bffedba9e
BLAKE2b-256 e484e5eac91d417e370accc31743436e806161eb852492395429e1d962a0a2ca

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page