HTTP Responses Overview#

This is the 13th post in the Django Ninja tutorial series.
This article begins the third section, "HTTP Responses."
This section will introduce how Django Ninja handles HTTP responses through 4 articles:
- Article 13: Response (1) How Django Ninja Handles HTTP Responses (this article)
- Article 14: Response (2) Building Nested Structure Responses with Schema
- Article 15: Response (3) Why Not Use ModelSchema? — Why I Prefer Django Ninja Over DRF
- Article 16: Response (4) Resolver Methods — Field Data Formatting
We will cover more Schema usages. Through these techniques, you can precisely control the output format of your APIs. Whether it is a single-object response or a complex nested structure, each will be addressed in turn.
For all code changes in this article, please refer to this PR.
GitHub Example Project#
This article guides you step by step from simple to complex HTTP Responses in Django Ninja.
We will use the three existing APIs as examples, adding details where needed:
- Add Post: Demonstrating a simple response with a status code.
- Get Single Post: Demonstrating a single-object response, which requires a Schema and defining the
response=parameter. - Get Post List: Demonstrating a multi-object response.
Let's get started!
1. Simple Response: Add Post#
Let's first look at the simplest response format. This example will demonstrate how to respond with a Python dictionary and manually set the HTTP response status code.
Taking the "Add Post" API as an example (part of the code is omitted):
@router.post(path='/posts/')
def create_post(...) -> dict:
...
return {'id': post.id, 'title': post.title}
What is returned here is a Python dictionary. In fact, you can return any JSON-serializable Python data. (Therefore, Django model objects will not work because they cannot be directly serialized.)
Thus, all of the following can be returned:
- A plain string:
"Hello World!" - A Python list:
[1, 2, 3] - A nested data structure:
{"name": "Alice", "age": 30, "hobbies": ["reading", "swimming"]}
These will be automatically serialized into JSON format by Django Ninja and sent as the API response:
Adding HTTP Status Codes to Responses#
View functions handling responses often need to add an HTTP status code, especially when there are multiple response states that need to be distinguished by status codes.
The approach is simple: return the Status Code before the Response body:
By doing so, the return type of the function changes from the original dict to a tuple.
Therefore, the type hints of our function signature must also be adjusted accordingly:
If you do not add the status code number beforehand, Django Ninja will default it to 200.
It is worth noting that when your view function returns a "non-200" response, you must declare it in the router decorator:
The response={201: dict} declaration uses a Python dictionary to map each Status Code to its Response format.
Note: At the time of writing, this part of the example project code had not been added yet, so this API could not respond properly 😅, just a reminder.
The first response type above is very simple, but most API responses are not that straightforward.
Let's look at the second type of response.
2. Single Model Object Response: Get Single Post#
When developing Django APIs, a large part of the data in responses is serialized from Django model objects.
However, we usually do not send all information from the database directly to the frontend. Instead, we perform field filtering, validation, or format conversion.
This not only allows precise control over the API output but also ensures data correctness and safety.
In Django Ninja, Schema handles needs such as field selection, validation, and format conversion.
Let's design a response format for the "Get Single Post" API using Schema.
# post/schemas.py
from datetime import datetime
...
class PostResponse(Schema):
id: int
title: str
content: str
author_id: int
created_at: datetime
updated_at: datetime
This PostResponse Schema contains almost all fields of Post.
Note that the Schema definition determines the output fields. If the Schema has only the id field, the output result will only contain data for that field.
Next, we use this Schema in the view function:
@router.get(path='/posts/{int:post_id}/', response=PostResponse)
def get_post(request: HttpRequest, post_id: int) -> Post:
"""
Get Single Post
"""
post = Post.objects.get(id=post_id)
return post
Only one line changes: we add response=PostResponse to the Router Decorator.
With the response=PostResponse configuration, Django Ninja will pass the Post model object returned by the function to PostResponse for validation. Once successful, it directly converts it to JSON format and sends it back to the frontend.
Let's look at the response result:
// http://127.0.0.1:8000/posts/2/
{
"id": 2,
"title": "Alice's Django Ninja Post 1",
"content": "Alice's Django Ninja Post 1 content",
"author_id": 1,
"created_at": "2024-09-12T02:28:16.801Z",
"updated_at": "2024-09-12T02:28:16.801Z"
}
Excellent!
3. Multiple Model Objects Response: Get Post List#
"Lists" are also a common response format for APIs, containing multiple records.
We will continue to use the same PostResponse without any changes and apply it directly to the "Get Post List" API.
Similarly, only one line needs to be changed, but it is slightly different from before:
We used list[PostResponse], indicating that the response will be a list of PostResponse objects.
Django Ninja Automatically Handles Iterables#
However, in reality, you do not need to "really" return a Python list; you can directly return a QuerySet, and Django Ninja will handle the iteration and serialization of the objects on its own.
In fact, as long as what you return is an iterable and every element in the iterable can pass the PostResponse validation (matches the format), that is sufficient!
Let's see the result. Since the list is too long, I will present it using a screenshot:

Multiple Status Code Responses#
The responses mentioned above are either 200 or 201, but usually APIs also have responses like 400, 401, 403, or even 500. How do we handle the mapping relationships between them?
Exactly, by expanding the dictionary in response=! Let's look directly at the example from the official documentation:
class Token(Schema):
token: str
expires: date
class Message(Schema):
message: str
@api.post(
path='/login',
response={200: Token, 401: Message, 402: Message}
)
...
It is worth noting that while the dictionary keys cannot be duplicated, the values can! — Message appears twice.
However, I feel that this "multiple status code response" configuration is not very practical in real-world applications. Why? We will discuss this later.
Summary#
Starting with the simplest Response, this article explained how to return single and multiple records and how Django Ninja configures Responses for multiple Status Codes.
The next article will explore how to handle complex nested structures in responses, making our API increasingly robust.