Closing session
Published June 13, 2025
This video features Vitaliy Kucheryaviy at DjangoCon Europe 2022 in Porto, Portugal.
Introducing Django Ninja by Vitaliy Kucheryaviy
Django Ninja is the quickest way to build REST APIs, that will be fast, secure and documented automatically.
Django Ninja is a small Django library for building REST APIs with Python type annotations. Vitaliy Kucheryaviy explains how annotations, validated by Pydantic, can define request parameters and JSON bodies, enforce types, generate OpenAPI documentation, and produce useful client tooling without manual serialization or error handling. He demonstrates schemas for input and output, query parameters, headers, cookies, file uploads, nested database objects, pagination, and model-based schemas. He also shows how routers split larger APIs into reusable application modules, how sync and async endpoints can coexist, and argues that Django Ninja offers a concise, performant, production-ready alternative for Django API development.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Hello, uh welcome to Introduction to Django Ninja My name is Vitaly Kuchirawi and today I'm gonna show you what Django Ninja is and why you should use it. So I work as a software developer for almost uh 20 years and most of the time I use Python and major part of data usually somewhere near Django code You probably noticed that I'm not at the stage at the moment. Well, that's because Russia invaded my country and I'm personally from City Kharky. which is shelled almost every day and almost every day we get casualties. But I would like to thank Jenga Khan Europe team
and everyone who listens now for this amazing opportunity to present my talk I really appreciate it. Thank you. So what is Django Ninja? So it's a relatively small library on top of Django that uses type annotations to create REST APIs. This project is heavily inspired by a framework called FASTAPI created by Sebastian Ramirez. So the secret source here is pretty simple. We take type annotations for describing APIs. These annotations are validated using Pydentic, and all this runs and integrates deeply with Django framework. Let's quickly jump into type hints or also known as type annotations.
So let's look at this function Right, so yeah, there is two arguments and the result is the plus operation between them. Uh and we cannot really know uh what are uh the a and b here. Are those numbers, are those strings, or maybe these are just some kind of remote uh data frames that happen to implement the plus operation. Uh so to help us here we can add um annotations yeah they are defined with this semicolon and the type of this argument and the result and here we can now understand yeah that the arguments are two numbers And the result of this operation will be also a number. And if you run it in interpreter, yeah, like so far so good, we added two numbers, we got the result.
But if we run a past there are not numbers but actually strings, yeah, there is no error, there is no warnings, yeah we got the result, but it's And again it's the result is not a number, it's a string. So as you can see the type annotations, they are not involved in route time at all These are just hints that can be used by linters or by editors. So for example in VS Code if I mark one of the arguments as int and other as string, you can see that now I got the warning that the plus operation is not allowed and it will eventually throw an error. So as you can see
it works on a lintel level but doesn't really work on a interpreter level So as you can see annotations are just hints and but on the other hand these hints we can use to create APIs. And yeah, let's do it. Let's create our first API. So first of all, install Django Ninja with Spip like so Then in some new module import from Ninja Ninja API class. And then we need just three lines. So first of all we initialize our API instance Then we use decorator.
In this case we use api. get to implement the get method. And inside we pass the path for that operation. In our case it's/add And finally we pass the request to our function. This is just the common Django approach, like all view functions, actually first argument request As you can see it's not even needed to mark a request with type annotations, but if you want to you always can. The race stays the same, a and b will be integers and the result will be the plus between A and B. And finally just put this API. urls
to URL patterns and that's it. You already have an ready API. And all you have to do now is to go to slash API slash docs. And there you will see the ready to use graphical interface and both documentation uh for the api which you just created. So in our case you see we have here the method slash API slash add that accepts two uh arguments a and b they will be passed in uh uh get parameters And let's try and execute it. And there you go, we get our results. So A and B are passed as
get parameters and we got our result. But unlike before our type annotations are now enforced. So if we pass, for example, not a number to our b argument. We will get a HTTP error that our B argument is not a valid integer number So basically we have now a guarantee that whatever we uh define in our functions will be always guaranteed and enforced And we don't have to worry about serialization, validation, and displaying the error messages back to the clients. And of course we can return any type of results.
So before we were outputting just a number, but we can actually return any structure that is convertible to JSON. It can be dict, it can be list, it can be any object that can be converted to JSON. And as well, like before, our arguments were passed as uh get arguments, but If we use mustache brackets in our path, we can mark them to be passed in path arguments. So here if we query the same URL with slash add slash three slash four, yeah we get our result uh as as value number seven. What other methods we can do?
It's actually pretty straightforward. So to send a post request, we use . api. post to put v write. put, delete and so on. So yeah, pretty straightforward. All popular HTTP methods are there and you can even use your own or combinations of multiple methods. To define your APIs. But where it mainly shines is when it comes to more complex JSON payloads that are submitted by clients. And the main horse here is a class called Schema. So let's create a new example where we will be creating
blog posts. with payloads. So all we have to do now is to define a new class that is inherited from schema And we can define there like as many as possible as complex as possible arguments. So it can be ins, dates, list of strings. These are just standard Python, there is nothing import here, there is nothing new. You basically define um uh with annotations. What you expect and the framework guarantees that you will get your results like you defined. And now all you have to do is in your post endpoint add an arguments uh which have annotation new post
and yeah there's a helper method dict that uh that can uh convert your object into a dictionary and yeah let's let's see how it works. Um so this is our uh request body example And what you note here is uh for example year we pass as a string and a timestamp we also pass as string, but uh as as not before we expect their uh year as a number and timestamp as a date. So now if we execute this request , this is what we get as a result. So yeah, you see the year is correctly converted to a number. And timestamp is also converted to a number, but yeah, it's actually in JSON
we only have uh dates, but uh yeah, believe me it's it's actually a date. But what's really cool about the way we use annotation is that when we use them, the editor can actually know what argument at this and as you can see here our timestamp is a date uh parameter and once we type a dot we can already use autocomplete to find uh the method that we need or the uh attribute that we need and yeah we never need to make any typos or mistakes so as you can see here vs code even suggests that yeah we made a typo here and we should better better uh to
change it so the more you the more type annotations you use the less um errors are in your code And schemes are also can be used to filter out unwanted data, like here if we pass some extra suspicious attribute, it will not be present in our validated payload By the way, Ninja automatically generates OpenAPI spec for all defined endpoints, which means you can use a lot of third-party tools like Swagger UI that I already showed. or redox that also comes as an option. Or you can even generate client-side code that will be automatically up to date with the changes that you make on the server side
And what about performance, right? So there's probably a big penalty for all this fancy validation. Well, actually no, and the opposite. Well, according to some tests in comparing with Jenga REST framework, ninja schemes can be sometimes like up to two times faster in data parsing and validation. And also you can use async views and more importantly you can mix sync and async views in one project So endpoints that are weighted like a lot of I. O. can run in one async worker and the CPU heavy task can run in multiple sync workers
Now uh in previous example we used schema in request body, but uh sometimes we sometimes we can have a ton of arguments in git in get parameters like tens of optional filters that are passed in URLs. Like in this example, we can define our filters as queryset lookups. And pass it as a function argument. And the only extra here we need to tell Ninja that it should be taken from query instead of body. To that, we can import this query marker, which will tell to get the data from git parameters instead of the body.
And yeah here to skip parameters that were not provided, you can use this exclude unset argument And yeah, finally just send all the lookups into query set filter. And that's it And yeah, the same way we also can access cookies and header values. You can define them like so. And as well get all the validation you define. Working with files is as easy. You just have to set your argument with a file marker. And a resulting value will be a standard Django file object with all the necessary attributes. Well, if you need a list of files, it is as easy.
Just set this argument with a list container. Now uh responses, right? So sometimes outputting nice results are as hard as parsing the data. Let's take an example the following database model. So yeah, this is a blog post with a bunch of fields. And now we need to output list of these posts. All we have to do is define a post schema with the same attributes as the fields And yeah, we just set the same types annotation that matches the database fields. And we set this class as a response attribute to our endpoint. Now if we check the interactive docs, yeah it
defines all the results correctly with all the attributes and when we execute it uh we are getting all the items in a way we define them. Oh, and did you notice like our endpoint just returned the query set? Yeah, there is no need to convert to any other intermediate dictionaries or lists or anything like that. Just one line of code. And also it is as easy work with nested objects. So if let's say our blog post have a foreign key to some category model , All we need to do is define category schema and just use category annotation and it will be automatically queried from the database and serialized.
And one of the simplest features is pagination. All you have to do is add this paginate decorator. By default it is limit offset paginator. If we open our interactive docs, yeah, we now see uh a pagination options And when we execute it, yeah, the results we have uh yeah, so this is our current page of items and the total number of results Next is schemes and models. Alright, so creating schemes for models is nice and intuitive, right? But What if you have like tens of fields in a model and these fields are constantly changing during development? Well, we have a special schema called model
schema. With model schema you can just configure the model and the fields that you need to sync to the schema. Well basically it's the same as Django model forms. Alright, and now whenever you change any fields in your model, the those changes will be automatically applied to corresponding schema. So every application tends to grow and yeah once you have more than five endpoints it's already a good time to start splitting your project into some logical pieces For that matter Ninja comes with a road structure. Let's take an example. So here is like a tree structure of some small project
Yeah, so there is uh um managed by project and there are three applications, some events, news and blocks. And each of these applications is supposed to have some uh API calls, yeah, at least five to ten uh uh endpoints each. So in this case, what we will do is we will create for each application a new module api. py. And inside this module, we import a router from Ninja. A router is basically a It's having the same interface as the Ninja API class. Yeah, it has all those get, posts, and all the rest
of functionality. So what you do is basically you uh inside each API module inside application you define as usual your uh endpoints And once you're done in inside your main module where you create your main ninja API instance You can include those rows from different modules using the add router method And yeah, in this case, like all the uh endpoints that were defined in uh events router. will be included to main
API and all the uh path will start with slash events. The same for news, all the news endpoints will start with slash news and same for blocks. So this is the way you can easily split your application into multiple uh independent modules and include them and uh even reuse if needed. Also keep in mind that Ninja API instances are also pretty independent. So you can for example have some public API in one Django project and in the same project you can have some private API And yeah, you can even have multiple versions of the same API and they will be all independent and will have their own
URLs and routes. I started this project about two years ago, basically as some proof of concept, well with just 400 lines of code. And as of today, please consider it a production ready library. It is 100% covered with tests, it has constant code reviews, bug fixes. and it is already used more by more than 10 companies, the names I know of, and I know it's a lot more are for sure. So yeah if you use Django Ninja, yeah please please drop me a line. And also, yeah, as of today , as I see on stats, the on a PyPy the
package has more than 50,000 downloads per month, which to me sounds like really crazy. And also I would like to take this opportunity to thank all the contributors who helped with the project. Yeah, I never thought that the open source project you spend only like one percent actually writing the code and yeah the the rest is way way more work and yeah if you have any ideas or PRs yeah please don't hesitate thank you And the easiest way you can help this project is to go and hit that star button on the GitHub page. Because yeah this will bring it to explore page
then then more people will come and more people again will click the start button and yeah they they they will will keep rolling Alright, thank you for having me and if it's technically possible, I hope I will answer your questions. Thank you.
Django Ninja is a small Django library for building REST APIs with Python type annotations. It uses those annotations for validation, serialization, and documentation while integrating closely with Django.
Discussed at 0:50Install Django Ninja, create a `NinjaAPI` instance, add an HTTP-method decorator such as `api.get`, define the endpoint function, and include the API URLs in Django’s URL configuration. Django Ninja then provides interactive documentation at `/api/docs`.
Discussed at 3:14Type annotations are enforced for API inputs, so invalid values such as a non-integer integer parameter produce an HTTP validation error. Django Ninja handles validation, serialization, and formatting the error response automatically.
Discussed at 5:30Create a class derived from `Schema`, annotate its fields with the expected Python types, and use that schema as the endpoint argument. Django Ninja converts compatible input values, rejects or filters unwanted data, and lets the handler use the validated object.
Discussed at 7:49Yes. It automatically generates an OpenAPI specification for the defined endpoints, which can power tools such as Swagger UI and Redoc, and can also be used to generate client-side code.
Discussed at 10:10The talk says Django Ninja schema parsing and validation can be up to twice as fast as Django REST Framework in some tests. It supports async views and allows synchronous and asynchronous endpoints to coexist in the same project.
Discussed at 10:56Define a response schema matching the model fields and assign it to the endpoint; Django Ninja can serialize a queryset directly, including nested related objects. Add the pagination decorator to expose limit/offset pagination and return the current items along with the total count.
Discussed at 13:17Use `ModelSchema` to configure the Django model and select the fields to expose. Changes to the model fields are then automatically reflected in the corresponding schema, similar to Django model forms.
Discussed at 15:37Create a router in each application, define that app’s endpoints there, and include the router in the main `NinjaAPI` with `add_router`. Prefixes such as `/events` or `/news` keep the modules separate, and multiple independent or versioned APIs can coexist.
Discussed at 16:24Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025
Published June 13, 2025