Tutorial: build the blog API
The Express example in examples/blog-api shows how Tango's model, migration, serializer, model-hook, resource, and adapter layers fit together.
The shape of this tutorial is intentionally close to Django and DRF tutorials: you build something concrete first, then use the topic guides and reference pages to deepen individual parts later.
What you are building
The example application exposes:
GET /healthGET /api/healthzGET|POST /api/generic/users- CRUD endpoints for users, posts, and comments through viewsets
- list filtering, search, ordering, and offset pagination
GET /api/openapi.json
1. Run the example
pnpm --filter @danceroutine/tango-example-express-blog-api bootstrap
pnpm --filter @danceroutine/tango-example-express-blog-api devNow visit:
http://localhost:3000/healthhttp://localhost:3000/api/posts?limit=20&offset=0http://localhost:3000/api/openapi.json
2. Start at the application wiring
This file shows the full wiring sequence:
- create the Express app
- create a database client
- assert the schema is present
- construct viewsets and API views
- register them with
ExpressAdapter
Tracing that wiring sequence is the fastest way to understand how a Tango application is assembled.
3. Read one model
Notice the pattern:
- a read schema defines the shape returned from the database and the API
- a create schema defines the body accepted by POST
- an update schema defines the body accepted by PATCH or PUT
PostModelwraps the read schema with Tango metadata and model hooks
The metadata includes:
- the primary key
- the
authorIdforeign key - default values for
published,createdAt, andupdatedAt
The model hook layer is where record lifecycle rules live when they should keep applying outside one resource.
4. Read one serializer
The serializer is where the resource contract becomes concrete.
It ties together:
- the model manager used for persistence
- the create schema
- the update schema
- the outward-facing read schema
Serializer hooks are available for request-scoped normalization. Record lifecycle behavior belongs with the model so the same rule keeps running for every caller of Model.objects.
5. Read one viewset
The viewset is the point in the stack where the HTTP surface becomes explicit.
The viewset config ties the serializer to the public list contract. Filtering, ordering, and search stay explicit, which tells clients what the endpoint supports without exposing raw ORM syntax directly.
6. Inspect the other resource styles
These files show the other resource styles:
APIViewfor a fully custom endpoint- generic CRUD views for a model-backed endpoint that does not need a full viewset
The example also includes src/openapi.ts, which generates an OpenAPI document from Tango resource instances, and /api/openapi.json, which publishes that document as JSON.
7. Try the list API
Use these URLs while the server is running:
/api/posts?limit=20&offset=0/api/posts?published=true/api/posts?search=serializer/api/posts?ordering=-createdAt/api/posts?authorId=1&published=true
Each query is handled by the combination of FilterSet, searchFields, orderingFields, and OffsetPaginator.
8. Make one schema change
Add an optional summary field to the post schemas and model metadata, then generate and apply a migration.
The commands are:
pnpm --filter @danceroutine/tango-example-express-blog-api make:migrations --name add_summary
pnpm --filter @danceroutine/tango-example-express-blog-api setup:schemaAfter that, update the serializer, any model hooks affected by the new field, and any resource behavior that should expose the field, then hit the API again.