How to build a simple API service

This example demonstrates how to build a simple CRUD (Create, Read, Update, Delete) API using nanodjango’s built-in Django Ninja support.

Lets implement a simple “Todo” app with a database model and endpoints for creating, listing, retrieving, updating, and deleting items.

Example code

from django.db import models
from nanodjango import Django

app = Django()

# Standard Django model
# Decorator registers it with the admin site
@app.admin
class Todo(models.Model):
    title = models.CharField(max_length=200)
    done = models.BooleanField(default=False)

    def __str__(self):
        return self.title

# Generate API schemas from the models
# One which has all fields for reading...
class TodoSchema(app.ninja.ModelSchema):
    class Meta:
        model = Todo
        fields = "__all__"

# ... and one which doesn't have the ID, for create and update payloads - we'll
# be getting the ID from the URL
class TodoIn(app.ninja.ModelSchema):
    class Meta:
        model = Todo
        exclude = ["id"]

# CRUD views, registered under the app's API - they all get the URL prefix /api
@app.api.post("/todos", response=TodoSchema)
def create_todo(request, payload: TodoIn):
    return Todo.objects.create(**payload.dict())

@app.api.get("/todos", response=list[TodoSchema])
def list_todos(request):
    return Todo.objects.all()

@app.api.get("/todos/{todo_id}", response=TodoSchema)
def get_todo(request, todo_id: int):
    try:
        return Todo.objects.get(id=todo_id)
    except Todo.DoesNotExist:
        return {"error": "Todo not found"}, 404

@app.api.put("/todos/{todo_id}", response=TodoSchema)
def update_todo(request, todo_id: int, payload: TodoIn):
    try:
        todo = Todo.objects.get(id=todo_id)
    except Todo.DoesNotExist:
        return {"error": "Todo not found"}, 404
    for key, value in payload.dict().items():
        setattr(todo, key, value)
    todo.save()
    return todo

@app.api.delete("/todos/{todo_id}")
def delete_todo(request, todo_id: int):
    try:
        todo = Todo.objects.get(id=todo_id)
    except Todo.DoesNotExist:
        return {"error": "Todo not found"}, 404
    todo.delete()
    return {"message": "Deleted"}

Running the app

Run the API using :

cd /examples
uvx nanodjango run hello_api.py

Endpoints

All endpoints are available under /api/:

1. Create a Todo (POST)

  • URL: /api/todos

  • Body: JSON object with title and optional done

curl -X POST http://127.0.0.1:8000/api/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Learn nanodjango", "done": false}'

Response:

{"id": 1, "title": "Learn nanodjango", "done": false}

2. List all Todos (GET)

  • URL: /api/todos

curl http://127.0.0.1:8000/api/todos

Response:

[{"id": 1, "title": "Learn nanodjango", "done": false}]

3. Get a Todo by ID (GET)

  • URL: /api/todos/{id}

curl http://127.0.0.1:8000/api/todos/1

Response:

{"id": 1, "title": "Learn nanodjango", "done": false}

4. Update a Todo (PUT)

  • URL: /api/todos/{id}

  • Body: JSON object with new done state

curl -X PUT http://127.0.0.1:8000/api/todos/1 \
  -H "Content-Type: application/json" \
  -d '{"done": true}'

Response:

{"id": 1, "title": "Learn nanodjango", "done": true}

5. Delete a Todo (DELETE)

  • URL: /api/todos/{id}

curl -X DELETE http://127.0.0.1:8000/api/todos/1

Response:

{"message": "Deleted"}