Skip to content

Building Your First API

This tutorial builds a small blog API from scratch: articles with a title, body, and published flag. By the end you will have a fully working CRUD API with filtering, pagination, and JWT authentication.

Prerequisites

uv add lightapi
# or: pip install lightapi

Step 1 — Define the endpoint

Create blog/endpoints.py:

from typing import Optional
from lightapi import (
    RestEndpoint, Field,
    Authentication, JWTAuthentication, IsAuthenticated, AllowAny,
    Filtering, Pagination, Serializer,
    FieldFilter, SearchFilter, OrderingFilter,
)

class ArticleEndpoint(RestEndpoint):
    title: str = Field(min_length=1, max_length=200)
    body: str
    published: bool = Field(default=False)

    class Meta:
        authentication = Authentication(
            backend=JWTAuthentication,
            permission={
                "GET": AllowAny,
                "POST": IsAuthenticated,
                "PUT": IsAuthenticated,
                "PATCH": IsAuthenticated,
                "DELETE": IsAuthenticated,
            },
        )
        filtering = Filtering(
            backends=[FieldFilter, SearchFilter, OrderingFilter],
            fields=["published"],
            search=["title", "body"],
            ordering=["title", "created_at"],
        )
        pagination = Pagination(style="page_number", page_size=10)
        serializer = Serializer(
            read=["id", "title", "published", "created_at"],
            write=["title", "body", "published"],
        )

Step 2 — Wire up the application

Create blog/main.py:

import os
from sqlalchemy import create_engine
from lightapi import LightApi
from blog.endpoints import ArticleEndpoint

os.environ.setdefault("LIGHTAPI_JWT_SECRET", "dev-secret-change-me-at-least-32-bytes-long")


def login_validator(username: str, password: str):
    """Validate credentials and return the JWT payload."""
    if username == "admin" and password == "secret":
        return {"sub": "1", "username": "admin", "is_admin": True}
    return None


engine = create_engine(os.environ.get("DATABASE_URL", "sqlite:///blog.db"))

app = LightApi(engine=engine, login_validator=login_validator)
app.register({"/articles": ArticleEndpoint})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

Step 3 — Run the server

python blog/main.py

LightAPI creates the articleendpoints table on startup. Because the endpoint declares JWTAuthentication, the framework also auto-registers POST /auth/login.

Step 4 — Log in to get a token

curl -X POST http://localhost:8000/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "secret"}'
# → 200 {"token": "eyJhbGc...", "user": {"sub": "1", "username": "admin", "is_admin": true}}

Capture the token field for the next step.

Step 5 — Interact with the API

TOKEN="<paste-token-here>"

# Create an article (requires auth)
curl -X POST http://localhost:8000/articles \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "Hello LightAPI", "body": "This is my first article.", "published": true}'
# → 201 {"id": 1, "title": "Hello LightAPI", "published": true, "created_at": "..."}

# List articles (public)
curl http://localhost:8000/articles
# → {"count": 1, "next": null, "previous": null, "results": [...]}

# Filter by published
curl "http://localhost:8000/articles?published=true"

# Full-text search
curl "http://localhost:8000/articles?search=LightAPI"

# Order by newest first
curl "http://localhost:8000/articles?ordering=-created_at"

# Get a single article
curl http://localhost:8000/articles/1
# → {"id": 1, "title": "Hello LightAPI", "published": true, "created_at": "..."}

# Update (requires current version)
curl -X PATCH http://localhost:8000/articles/1 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"published": false, "version": 1}'
# → 200 {"id": 1, ..., "version": 2}

# Delete
curl -X DELETE http://localhost:8000/articles/1 \
  -H "Authorization: Bearer $TOKEN"
# → 204 No Content

What you built

Feature Provided by
Auto CRUD routes RestEndpoint metaclass
articles table creation app.register()
JWT auth (public reads, auth writes) Meta.authentication with per-method dict
Exact-match filter ?published= FieldFilter in Meta.filtering
Full-text search ?search= SearchFilter
Sort ?ordering= OrderingFilter
Page-number pagination Meta.pagination
Read-only serializer fields Meta.serializer
Optimistic locking via version Built-in
Pydantic v2 validation Field(min_length=...)

Next Steps