Next.js-style file-based routing for FastAPI
Project description
fastapi-filebased-routing
Next.js-style file-based routing for FastAPI
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
- ๐ File-Based Routing Explained
- ๐ Route Handlers
- ๐ Middleware
- ๐ฆ Examples
- ๐ API Reference
- ๐ก Why This Plugin?
- ๐ค Contributing
๐ Installation & Quickstart
Requires Python 3.13+ and FastAPI 0.115.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, CRUDmiddleware/โ All three middleware layers in actionadvanced/โ 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 filesprefix(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 directoryRouteValidationError: If a route file has invalid exports or parametersDuplicateRouteError: If two route files resolve to the same path+methodPathParseError: If a directory name has invalid syntaxMiddlewareValidationError: If a_middleware.pyfile 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:
- Manual route registration is tedious. Every endpoint requires updating a centralized router file.
- Route discoverability degrades. Finding the handler for
/api/v1/users/{id}requires searching across files. - Middleware wiring is repetitive. Applying auth to 20 admin endpoints means 20 copies of
Depends(require_admin). - 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 --reloadworks 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastapi_filebased_routing-1.0.0.tar.gz.
File metadata
- Download URL: fastapi_filebased_routing-1.0.0.tar.gz
- Upload date:
- Size: 104.8 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d1350635fc91113de9c441a04263231c9b523675ec8279c2d52f5ac6878141a
|
|
| MD5 |
63e15b7d942fbc13c3baa786b32bf9de
|
|
| BLAKE2b-256 |
200c9c05a3277739312b426cb314777a13ac0d196c59cd5923704a4d2d275141
|
File details
Details for the file fastapi_filebased_routing-1.0.0-py3-none-any.whl.
File metadata
- Download URL: fastapi_filebased_routing-1.0.0-py3-none-any.whl
- Upload date:
- Size: 25.1 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
875348e7fb3085083f55a504c3d0184829af8113457f6a1c841f41cac9cc2b61
|
|
| MD5 |
0eeb12a3eec48758fe1ed95282cf55cc
|
|
| BLAKE2b-256 |
85ee06aed7ca9b21924465cd4679450852e6ae9cd8a8e664d405f3797364fd09
|