Skip to content

Path Parameters#

2024 iThome Ironman

This is the 10th post in the Django Ninja tutorial series.

In the previous article, we introduced how Django Ninja handles HTTP requests and highlighted its tight integration with Python type hints.

This article will explore the application and details of path parameters in Django Ninja, which are extremely common when handling HTTP requests, especially in RESTful APIs.

The code changes made to the example project in this article are concentrated in this PR.

GitHub Example Project#

👉 Django-Ninja-Tutorial


1. What Are Path Parameters?#

Path Parameters occupy specific positions in a URL path. Their values dynamically identify which resource the Request targets.

Let's first understand the position of the path in the overall URL (image source: Wikipedia):

Click to enlarge

As seen in the diagram, the path is a part of the URL—and a necessary part.

However, please note that path parameters are just a "feature" provided by frameworks like Django and Django Ninja. As far as the URL itself is concerned, a path is just a path—simply a string.

Path Parameter Examples#

Let's look at a simple example to better understand the concept of path parameters.

@router.get(path='/posts/{post_id}/')  # {post_id} is the path parameter

In an actual request, 123 is passed via the path parameter, representing the ID of a specific post:

GET /posts/123

As you can imagine, if it were 456 or 789, you would get different results.

This makes the API flexible, allowing operations on different resources without having to create different routes for each resource—their endpoints and routes are all the same, only the "parameters" differ.


2. Changes in the Example Project#

Next, let's explain and demonstrate the content of this article using the code from the example project.

But first, we need to make two changes.

Change 1: Remove the Root-Level Routing Prefix#

Remove the Root-Level Routing Prefixes /posts/ and /user/ so that the path in each view function's Router Decorator is complete and easier to read.

Originally, it was like this:

@router.get(path='/{post_id}/')  # The prefix route is defined in the project's first-level routing

Now it is like this:

@router.get(path='/posts/{post_id}/')  # All changed to be defined in the app's second-level routing

Note that this is to enhance the tutorial experience. In production, we usually wouldn't do this, as it would defeat the advantages of modular routing.

Change 2: Add New API#

To demonstrate path parameters, we must have an API that implements this feature.

We add a new "Get Single Post Info" API.


Alright, that's all for now. We can start learning about path parameters.

The following code snippets are all taken from the example project.

3. Using Path Parameters in Django Ninja#

Defining path parameters in Django Ninja is extremely simple. Through the router decorator and type hints, we can easily handle these parameters and automatically perform type conversion.

Defining an API With Path Parameters#

Let's see how to define an API with a path parameter in Django Ninja:

@router.get(path='/posts/{post_id}/')
def get_post(request: HttpRequest, post_id: int) -> Post:
    post = Post.objects.get(id=post_id)
    return post

In this example, {post_id} is a Path Parameter. Its value is parsed from the path and passed to the post_id parameter of the get_post function.

Django Ninja will automatically perform type conversion according to the type defined in the function signature.

For example, since we marked post_id: int in the view function, Django Ninja automatically converts the string parameter from the URL into an int.

In other words, the process of handling path parameters has two effects simultaneously:

  1. Parameter Type Validation: Preventing the view function from trying to process a post_id of the wrong type and failing later.
  2. Automatic Type Conversion Inside the View Function: Saving the effort of manual conversion within the function.

4. Compatibility With Django's Native Path Converters#

When handling URLs, Django natively provides "path converters" to let you perform "strict matching" on request paths.

Only when the match is successful will the HTTP request be "forwarded" to the specific view function.

Here, "strict" means that a value must match the pattern defined by the Path Converter for the Route to match successfully.

Common converter types include str, int, and slug, which can restrict the format of parameters in the URL:

path('posts/<int:post_id>/', views.get_post),

<int:post_id> is a path converter, which requires that post_id must be an integer.

It is worth emphasizing that the primary purpose of path converters is not for type conversion—that is just secondary. It is for the "pattern matching" of endpoint paths.

In cases where the pattern does not match, no match will succeed at all, and of course, no type conversion will take place.

Path Converters in Django Ninja#

In Django Ninja, these native path converters can still be used and are further simplified.

Simply write them directly in the path string of the router decorator:

@router.get(path='/posts/{int:post_id}/')

As mentioned earlier, with a path converter, if post_id is not a valid int, the URL pattern matching will fail directly—the request will not enter the view function, let alone perform type conversion.

If no other path is successfully matched, Django will directly return "404 Not Found."

Personally, I believe that in Django Ninja, the function of path converters has been partially replaced by type hints. If you want to use path converters at the same time, you must pay attention to the evaluation order of the two (path converters are evaluated first) and ensure that the types set for both must be identical.


5. Basic Error Handling for Requests#

When a path parameter in a request does not match the type defined by type hints, Django Ninja automatically returns an HTTP response with an error message and hint details, with a status code of 422.

For instance, if a user requests the path /posts/abc/ (where the post_id parameter is not a number), they will get the following response:

// http://127.0.0.1:8000/posts/abc/
{
    "detail": [
        {
            "type": "int_parsing",
            "loc": [
                "path",
                "post_id"
            ],
            "msg": "Input should be a valid integer, unable to parse string as an integer"
        }
    ]
}

Such an automatic error handling mechanism not only improves API stability but also simplifies the developer's error handling logic.

The built-in 422 response is extremely common in Django Ninja, saving us a lot of time.


Summary and Next Steps#

Path parameters are an important component of RESTful APIs. Django Ninja allows us to easily handle dynamic parameters in paths through type hints and automated error handling.

Additionally, it maintains good compatibility with Django's native path converters, providing a highly efficient and clean development experience.

In the next article, we will explore query parameters in depth to explain how to handle these parameters in Django Ninja, further enhancing the flexibility and functionality of the API.