Skip to content

Routing

This guide explains how routing works in Django-Bolt and covers all the ways you can define API endpoints.

Basic routing

Routes are defined using decorator methods on a BoltAPI instance:

from django_bolt import BoltAPI

api = BoltAPI()

@api.get("/users")
async def list_users():
    return {"users": []}

@api.post("/users")
async def create_user():
    return {"created": True}

HTTP methods

Django-Bolt supports all common HTTP methods:

Decorator HTTP Method Typical use
@api.get() GET Retrieve resources
@api.post() POST Create resources
@api.put() PUT Replace resources
@api.patch() PATCH Partial updates
@api.delete() DELETE Remove resources
@api.head() HEAD Get headers only
@api.options() OPTIONS Get allowed methods

Example with all methods:

@api.get("/items")
async def list_items():
    return {"items": []}

@api.post("/items")
async def create_item():
    return {"created": True}

@api.put("/items/{item_id}")
async def replace_item(item_id: int):
    return {"replaced": True}

@api.patch("/items/{item_id}")
async def update_item(item_id: int):
    return {"updated": True}

@api.delete("/items/{item_id}")
async def delete_item(item_id: int):
    return {"deleted": True}

@api.head("/items")
async def head_items():
    return {}  # Body not sent for HEAD

@api.options("/items")
async def options_items():
    from django_bolt import Response
    return Response({}, headers={"Allow": "GET, POST, PUT, PATCH, DELETE"})

Path parameters

Capture dynamic segments of the URL using curly braces:

@api.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

@api.get("/posts/{post_id}/comments/{comment_id}")
async def get_comment(post_id: int, comment_id: int):
    return {"post_id": post_id, "comment_id": comment_id}

See Request Handling for details on path parameters, query parameters, headers, cookies, form data, and file uploads.

Route options

The route decorator accepts additional options:

@api.get(
    "/users/{user_id}",
    status_code=200,           # Default response status code
    summary="Get user",        # Short description for OpenAPI
    description="Get a user by ID",  # Detailed description
    tags=["users"],            # OpenAPI tags for grouping
    response_model=UserSchema, # Response validation schema
)
async def get_user(user_id: int):
    """This docstring also appears in OpenAPI docs."""
    return {"user_id": user_id}

Sync handlers

While async handlers are recommended, you can also use synchronous functions:

@api.get("/sync")
def sync_handler():
    return {"sync": True}

Sync handlers are automatically wrapped to run in a thread pool.

WebSocket routes

Define WebSocket endpoints using @api.websocket():

from django_bolt import WebSocket

@api.websocket("/ws/echo")
async def echo(websocket: WebSocket):
    await websocket.accept()
    async for message in websocket.iter_text():
        await websocket.send_text(f"Echo: {message}")

See the WebSocket guide for more details.

Auto-discovery

Django-Bolt automatically discovers api.py files in:

  1. Your project directory (where settings.py is)
  2. Each installed Django app

All routes from discovered files are combined into a single router. This lets you organize routes per app:

myproject/
    myproject/
        settings.py
        api.py              # /health, /docs
    users/
        api.py              # /users, /users/{id}
    products/
        api.py              # /products, /products/{id}

Multiple API instances

You can create multiple BoltAPI instances and mount them:

# users/api.py
from django_bolt import BoltAPI

api = BoltAPI()

@api.get("/users")
async def list_users():
    return {"users": []}
# myproject/api.py
from django_bolt import BoltAPI
from users.api import api as users_api

api = BoltAPI()

# Mount users API under /api/v1
api.mount("/api/v1", users_api)

Routes from users_api are now available at /api/v1/users.

Trailing slash handling

Django-Bolt normalizes trailing slashes at route registration time and uses Starlette-style redirects at runtime. By default, trailing slashes are stripped from paths:

api = BoltAPI()

@api.get("/users/")  # Registered as /users
async def list_users():
    return []

Trailing slash modes

You can control this behavior with the trailing_slash parameter:

Mode Description Example
"strip" Remove trailing slashes (default) /users//users
"append" Add trailing slashes /users/users/
"keep" No normalization /users//users/

Runtime redirect behavior

When a request URL doesn't exactly match a registered route, Django-Bolt checks if the alternate path (with or without trailing slash) exists. If it does, a 308 Permanent Redirect is returned to the canonical URL:

api = BoltAPI(trailing_slash="append")

@api.get("/users")  # Registered as /users/
async def list_users():
    return []

# GET /users   → 308 Redirect to /users/
# GET /users/  → 200 OK (canonical URL)

This Starlette-style approach means: - Both URLs work - users can access either, but one redirects - SEO-friendly - search engines see the redirect and index the canonical URL - Minimal overhead - redirect check only happens when route doesn't match

Strip mode (default)

The default mode removes trailing slashes, which produces clean URLs:

api = BoltAPI()  # trailing_slash="strip" is the default

@api.get("/users/")   # Registered as /users
@api.get("/items")    # Registered as /items

# GET /users/  → 308 Redirect to /users
# GET /users   → 200 OK

Append mode

Use append mode to follow Django's URL convention where paths end with slashes:

api = BoltAPI(trailing_slash="append")

@api.get("/users")    # Registered as /users/
@api.get("/items/")   # Registered as /items/

# GET /users   → 308 Redirect to /users/
# GET /users/  → 200 OK

Keep mode

Use keep mode when you need explicit control over each path:

api = BoltAPI(trailing_slash="keep")

@api.get("/users")    # Registered as /users
@api.get("/items/")   # Registered as /items/

# GET /users/  → 308 Redirect to /users (if /users exists)
# GET /items   → 308 Redirect to /items/ (if /items/ exists)

Multiple APIs with different settings

When Django-Bolt auto-discovers and merges multiple api.py files, each API's routes keep their own trailing slash format. This allows different apps to use different conventions:

# myproject/api.py
api = BoltAPI(trailing_slash="append")

@api.get("/items")  # Registered as /items/
async def list_items():
    return []

# users/api.py
api = BoltAPI()  # Default: trailing_slash="strip"

@api.get("/users")  # Registered as /users
async def list_users():
    return []

After merging: - GET /items → 308 Redirect to /items/ - GET /items/ → 200 OK - GET /users/ → 308 Redirect to /users - GET /users → 200 OK

Each route respects its original API's trailing_slash setting because routes are normalized at registration time, before the merge happens.

Mounted APIs

When mounting APIs with api.mount(), the parent's trailing_slash setting is applied to all child routes:

parent = BoltAPI(trailing_slash="append")
child = BoltAPI(trailing_slash="strip")  # Child's setting is IGNORED when mounted

@child.get("/users")  # Child normalizes to /users
async def list_users():
    return []

parent.mount("/api", child)
# Route is /api/users/ (parent's "append" mode is applied)

This differs from auto-discovery, where each API keeps its own setting.

URL prefix

Apply a prefix to all routes using the prefix parameter:

api = BoltAPI(prefix="/api/v1")

@api.get("/users")  # Accessible at /api/v1/users
async def list_users():
    return []

This is useful for versioning your API or grouping routes under a common path.