Skip to content

Quick Start

This tutorial walks you through building a simple API with Django-Bolt. By the end, you'll understand the core concepts and be ready to build your own APIs.

Your first endpoint

Open your api.py file and add:

from django_bolt import BoltAPI

api = BoltAPI()

@api.get("/hello")
async def hello():
    return {"message": "Hello, World!"}

Start the server:

python manage.py runbolt --dev

Visit http://localhost:8000/hello in your browser. You'll see:

{ "message": "Hello, World!" }

Path parameters

Extract values from the URL path using curly braces:

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

The user_id parameter is automatically converted to an integer. Try:

  • http://localhost:8000/users/42 returns {"user_id": 42}
  • http://localhost:8000/users/abc returns a 422 error (invalid integer)

Query parameters

Function parameters without path placeholders become query parameters:

@api.get("/search")
async def search(q: str, page: int = 1, limit: int = 10):
    return {"query": q, "page": page, "limit": limit}

Try: http://localhost:8000/search?q=python&page=2

Returns:

{ "query": "python", "page": 2, "limit": 10 }

Optional parameters have default values. Required parameters (like q) return a 422 error if missing.

Request body validation

Use msgspec.Struct to define and validate request bodies:

import msgspec

class Item(msgspec.Struct):
    name: str
    price: float
    description: str | None = None

@api.post("/items")
async def create_item(item: Item):
    return {"name": item.name, "price": item.price}

Test with curl:

curl -X POST http://localhost:8000/items \
  -H "Content-Type: application/json" \
  -d '{"name": "Widget", "price": 29.99}'

Returns:

{ "name": "Widget", "price": 29.99 }

Invalid data returns a detailed validation error:

curl -X POST http://localhost:8000/items \
  -H "Content-Type: application/json" \
  -d '{"name": "Widget"}'

Returns a 422 Unprocessable Entity because price is required:

{
    "detail": [
        {
            "loc": ["body", "price"],
            "msg": "Object missing required field `price`",
            "type": "missing_field"
        }
    ]
}

HTTP methods

Django-Bolt supports all common HTTP methods:

@api.get("/resource")
async def get_resource():
    return {"method": "GET"}

@api.post("/resource")
async def create_resource():
    return {"method": "POST"}

@api.put("/resource/{id}")
async def update_resource(id: int):
    return {"method": "PUT", "id": id}

@api.patch("/resource/{id}")
async def partial_update(id: int):
    return {"method": "PATCH", "id": id}

@api.delete("/resource/{id}")
async def delete_resource(id: int):
    return {"method": "DELETE", "id": id}

Headers

Extract header values using Annotated:

from typing import Annotated
from django_bolt.param_functions import Header

@api.get("/auth")
async def check_auth(
    authorization: Annotated[str, Header(alias="Authorization")]
):
    return {"token": authorization}

@api.get("/optional-header")
async def optional_header(
    x_custom: Annotated[str | None, Header(alias="X-Custom")] = None
):
    return {"custom": x_custom}

Form data

Handle form submissions:

from typing import Annotated
from django_bolt.param_functions import Form

@api.post("/login")
async def login(
    username: Annotated[str, Form()],
    password: Annotated[str, Form()]
):
    return {"username": username}

File uploads

Handle file uploads:

from typing import Annotated
from django_bolt.param_functions import File

@api.post("/upload")
async def upload(
    files: Annotated[list[dict], File(alias="file")]
):
    for f in files:
        print(f"Received: {f.get('filename')} ({f.get('size')} bytes)")
    return {"uploaded": len(files)}

Each file dict contains filename, content_type, size, and content (bytes).

Response types

By default, returning a dict creates a JSON response. You can also return other types:

from django_bolt.responses import PlainText, HTML, Redirect

@api.get("/text")
async def plain_text():
    return PlainText("Hello, plain text!")

@api.get("/html")
async def html_page():
    return HTML("<h1>Hello HTML</h1>")

@api.get("/redirect")
async def redirect():
    return Redirect("/hello")

Rendering Django templates

Use the render() function to render Django templates. It works like Django's render() but returns an HTML response:

from django_bolt import Request
from django_bolt.shortcuts import render

@api.get("/page")
async def show_page(request: Request):
    return render(request, "page.html", {
        "title": "My Page",
        "items": ["item1", "item2"],
    })

Working with Django models

Django-Bolt works seamlessly with Django's ORM. Use async methods for database queries:

from myapp.models import Article

@api.get("/articles")
async def list_articles():
    # Use async ORM methods
    articles = []
    async for article in Article.objects.all()[:10]:
        articles.append({
            "id": article.id,
            "title": article.title
        })
    return {"articles": articles}

@api.get("/articles/{article_id}")
async def get_article(article_id: int):
    try:
        article = await Article.objects.aget(id=article_id)
        return {"id": article.id, "title": article.title}
    except Article.DoesNotExist:
        from django_bolt.exceptions import HTTPException
        raise HTTPException(status_code=404, detail="Article not found")

Error handling

Raise HTTPException to return error responses:

from django_bolt.exceptions import HTTPException

@api.get("/items/{item_id}")
async def get_item(item_id: int):
    if item_id <= 0:
        raise HTTPException(status_code=400, detail="Invalid item ID")

    # Simulate item not found
    if item_id > 1000:
        raise HTTPException(status_code=404, detail="Item not found")

    return {"item_id": item_id}

Common HTTP exceptions are also available:

from django_bolt.exceptions import NotFound, BadRequest, Unauthorized

@api.get("/resource/{id}")
async def get_resource(id: int):
    if id <= 0:
        raise BadRequest(detail="ID must be positive")

    # Check if exists...
    raise NotFound(detail=f"Resource {id} not found")

API documentation

Django-Bolt automatically generates OpenAPI documentation. Visit:

  • http://localhost:8000/docs - Interactive Swagger UI

Add descriptions to your endpoints for better documentation:

@api.get(
    "/users/{user_id}",
    summary="Get a user",
    description="Retrieve a user by their unique ID",
    tags=["users"]
)
async def get_user(user_id: int):
    """
    This docstring also appears in the API documentation.
    """
    return {"user_id": user_id}

Next steps

You now know the basics of Django-Bolt. Here's where to go next: