djsonapi — JSON:API for Django
JSON:API is a spec that makes you stop arguing about API design and start shipping. Every decision is made ahead of time — how attributes differ from relationships, how to request sparse fields, how to paginate, how to sideload related resources. One consistent wire format means one client generator, one docs strategy, one way to think.
djsonapi gives you:
- Minimal Resource classes — pydantic-adjacent, behave like dataclasses, type annotations drive everything
- FastAPI-style decorators — JSON:API-aware, auto-validate, auto-parse query params, generate OpenAPI
- Free OpenAPI 3.0.3 + Redoc docs — at
/api/openapi.jsonand/api/docs/ - Python & TypeScript SDKs — generated from your API, sealed types, IDE autocomplete
Quickstart
Install:
Add to INSTALLED_APPS. Define a resource:
from djsonapi import Resource
from typing import ClassVar
class ArticleResource(Resource):
_type: ClassVar = "articles"
_attributes: ClassVar = ["title", "content", "created_at"]
_singular_relationships: ClassVar = [("author", "users")]
_create_fields: ClassVar = ["title", "content", "author"]
_required_create_fields: ClassVar = ["title"]
id: int
title: str
content: str
created_at: datetime
author: int
Resources behave like dataclasses. Instantiate and interact with them directly:
article = ArticleResource(id=1, title="Hello", content="...",
created_at=datetime.now(), author=42)
article.title = "Updated title"
Register endpoints:
from djsonapi import DjsonApi
api = DjsonApi()
@api.get_one("articles")
def get_article(request, article_id: int) -> ArticleResource:
return ArticleResource.from_model(Article.objects.get(id=article_id))
@api.get_many("articles")
def list_articles(request) -> list[ArticleResource]:
return [ArticleResource.from_model(a) for a in Article.objects.all()]
@api.create_one("articles")
def create_article(request, payload: ArticleResource) -> ArticleResource:
article = Article.objects.create(**payload.as_create_payload())
return ArticleResource.from_model(article)
@api.edit_one("articles")
def edit_article(request, article_id: int, payload: ArticleResource) -> ArticleResource:
article = Article.objects.get(id=article_id)
for key, value in payload.as_edit_payload().items():
setattr(article, key, value)
article.save()
return ArticleResource.from_model(article)
@api.delete_one("articles")
def delete_article(request, article_id: int) -> None:
Article.objects.get(id=article_id).delete()
Wire it up:
| Method | URL | What |
|---|---|---|
GET |
/api/articles |
List |
POST |
/api/articles |
Create |
GET |
/api/articles/{id} |
Read |
PATCH |
/api/articles/{id} |
Update |
DELETE |
/api/articles/{id} |
Delete |
GET |
/api/openapi.json |
OpenAPI spec |
GET |
/api/docs/ |
Redoc docs |
What the library did for you
- Content-Type: rejected anything not
application/vnd.api+jsonon writes - Accept: enforced
application/vnd.api+json - JSON Schema validation: request body validated before your handler saw it
- Serialization: your Resource → proper JSON:API shape with
type,id,attributes,relationships,links - Link generation:
selfand relationship links auto-populated based on registered endpoints - Error documents: 400/404/405/500 all return JSON:API error bodies
OpenAPI & Redoc
Visit /api/docs/ after starting your server:
SDK generation
One command generates a sealed, typed client:
Usage
from articles_sdk import sdk
sdk.setup(
host="http://localhost:8000/api/",
headers=lambda: {"Authorization": "Bearer token"},
)
async with sdk:
article = await sdk.articles.get(1)
print(article.title) # "Why JSON:API is great"
print(article.created_at) # datetime — auto-converted
await article.save(title="New title")
await article.delete()
Why not a global singleton?
The exported sdk is convenient for simple apps, but a single global config
is unsafe when background tasks make requests on behalf of different users
(e.g., OAuth). Instantiate separate SDKs with their own config:
IDE autocompletion
Every method, every filter, every field — generated SDKs are fully typed:
Requirements
- Python ≥ 3.13
- Django ≥ 4.2
- aiohttp ≥ 3.14 (client only)
- jsonschema ≥ 4.20