Django Ninja Tutorial

Our copy of the Django Ninja tutorial, adapted for uv and your locallibrary project.

This tutorial builds one realistic API from start to finish: TaskFlow, a task tracker for teams. Users sign up, create projects, invite members with roles, and track tasks with assignees, labels, statuses, due dates, comments and file attachments. Each part adds a feature to the same codebase and shows the Django Ninja tools that make it work.

What you’ll build

By the end of Part 8, TaskFlow has these endpoints:

Method URL What it does
POST /taskflow/api/auth/signup Create an account
POST /taskflow/api/auth/token, /taskflow/api/auth/refresh Get and refresh JWT access tokens
GET, POST /taskflow/api/projects/ List your projects, create one
GET, PUT, DELETE /taskflow/api/projects/{project_id} Read, update and delete a project
POST /taskflow/api/projects/{project_id}/labels Add a label to a project
GET, POST /taskflow/api/projects/{project_id}/members List and add members
GET, POST /taskflow/api/projects/{project_id}/tasks/ Filter, search, sort and paginate tasks, create one
GET, PATCH /taskflow/api/projects/{project_id}/tasks/{task_id} Read a task, update part of it
POST /taskflow/api/projects/{project_id}/tasks/{task_id}/status Move a task through its workflow
GET, POST /taskflow/api/projects/{project_id}/tasks/{task_id}/comments List comments with a cursor, add one
PUT, DELETE /taskflow/api/projects/{project_id}/tasks/{task_id}/comments/{comment_id} Edit or delete a comment
GET, POST /taskflow/api/projects/{project_id}/tasks/{task_id}/attachments List attachments, upload a file
DELETE /taskflow/api/projects/{project_id}/tasks/{task_id}/attachments/{attachment_id} Delete an attachment

Every endpoint except the auth ones needs a token, and users only ever see the projects they’re members of. A second version of the API runs next to the first at /taskflow/api/v2/, and a pytest suite covers the rules that matter.

Before you start

  • You know Django: projects, apps, models, migrations and settings. The tutorial doesn’t explain those. It focuses on the Django Ninja parts.
  • You’ve done our Django Ninja Quickstart, so NinjaAPI, schemas and the interactive docs are familiar. That also means you’ve finished preps 01-06 and our Channels tutorial, and you have your locallibrary repository: a uv project with manage.py, pyproject.toml, and uv.lock at its root, the locallibrary_config project package, and the catalog and chat apps. Every command in this tutorial is run from that root folder.
  • You have Python 3.12 or newer. The code uses modern type hints such as int | None and list[int]. Your locallibrary uses Python 3.14.

The only packages TaskFlow needs are Django and Django Ninja, and both are already in your pyproject.toml: Django since Prep 01, and Django Ninja since the Quickstart. You can check:

uv run python -c "import django, ninja; print(django.__version__, ninja.__version__)"

Everything else, including JSON Web Tokens, is written with the standard library. Part 7 adds pytest and pytest-django as development tools. The database is the same SQLite db.sqlite3 your catalog and chat use, and all of TaskFlow’s views are synchronous.

How the parts work

  • Each part starts with a What you’ll learn box and takes about 15 minutes to read and type.
  • Each part builds on the code of the one before, so work through them in order. Changed files are shown in full, or as excerpts with ... where the rest of the file stays the same.
  • The example requests and responses are real output from the original tutorial. Your projects, labels, tasks, comments and attachments get new tables, so if you follow along exactly, their ids will match the ones on the pages. Your user ids won’t, because your database already has users. Part 2 explains what to do about that.
  • Go deeper links at the end of each section point to the Django Ninja Guide page that covers the feature in full.

The parts

  1. Project setup & layout: one router per app, schemas from models, and task URLs nested under projects.
  2. Relations & computed fields: assignees and labels as nested output, computed and annotated fields, and fewer queries.
  3. Validation & workflows: status and priority choices, field and model validators, PATCH updates and status transitions.
  4. Auth & permissions: JWT tokens from scratch, project memberships with roles, and object-level rules for comments.
  5. Filtering, search, ordering & pagination: a FilterSchema for tasks, safe sort orders, and page-number and cursor pagination.
  6. File uploads & forms: multipart uploads with form fields, file validation, media files and absolute URLs.
  7. Testing: a pytest suite with fixtures and TestClient, covering validation, permissions, filters and uploads.
  8. OpenAPI polish & versioning: descriptions, examples and deprecations in the docs, and a version 2 next to version 1.

Ready? Start with Part 1: Project setup & layout.

How this copy differs from the original

This is a copy of the official Django Ninja tutorial, changed to match the way we have worked in our fork of the MDN Django Tutorial, our Channels tutorial, and our Django Ninja Quickstart:

  • You don’t create a new project. The projects, tasks, and accounts apps go into your existing locallibrary repository, next to catalog and chat. Everywhere the original edits taskflow/settings.py or taskflow/urls.py, this copy edits locallibrary_config/settings.py or locallibrary_config/urls.py, and only adds lines.
  • taskflow is a plain package. In the original, taskflow is the Django project package. Here it’s a small package next to manage.py that holds only taskflow/api.py, so imports like from taskflow.api import api work exactly as in the original.
  • TaskFlow lives at /taskflow/api/, not /api/. Your Quickstart API already uses /api/. Every TaskFlow URL in this copy starts with /taskflow/api/, and version 2 is at /taskflow/api/v2/. Paths inside TestClient calls are relative to the API, so they’re the same as in the original.
  • Each TaskFlow NinjaAPI sets urls_namespace=. That keeps TaskFlow’s URL names apart from the Quickstart API’s. Part 8 explains why.
  • Dependencies are managed with uv. pip install becomes uv add (uv add --dev for pytest), and every command is run with uv run.
  • You never delete db.sqlite3. The original suggests it in Part 2 so the ids match. Yours holds your catalog and chat data, so this copy looks users up by username where the original uses their ids.
  • Part 6 adds media/ to your .gitignore. The original assumes it’s already there.
  • Part 7’s tests use your test_db.sqlite3 file. That’s the TEST database setting you added in Part 4 of the Channels tutorial.

This copy follows the Django Ninja documentation as of 2026-10-19 (Django Ninja 1.7.1), on Django 6.1 and Python 3.14.


The original tutorial is Copyright (c) 2025 Vitaliy Kucheryaviy, and is used under its MIT license.


Part 1: Project setup & layout

One router per app, schemas from models, and task URLs nested under projects.

Part 2: Relations & computed fields

Assignees and labels as nested output, computed and annotated fields, and fewer queries.

Part 3: Validation & workflows

Status and priority choices, field and model validators, PATCH updates and status transitions.

Part 4: Auth & permissions

JWT tokens from scratch, project memberships with roles, and object-level rules for comments.

Part 5: Filtering, search, ordering & pagination

A FilterSchema for tasks, safe sort orders, and page-number and cursor pagination.

Part 6: File uploads & forms

Multipart uploads with form fields, file validation, media files and absolute URLs.

Part 7: Testing

A pytest suite with fixtures and TestClient, covering validation, permissions, filters and uploads.

Part 8: OpenAPI polish & versioning

Descriptions, examples and deprecations in the docs, and a version 2 next to version 1.

Last modified October 22, 2026: add button label (28b88d0)