Library / SDK
radiac/nanodjango avatar
radiac/nanodjango

nanodjango: a whole Django site in one Python file you can later expand

Full Django in a single file - views, models, API ,with async support. Automatically convert it to a full project.

930 stars52 forksPythonBSD-3-Clause

At a glance

What is it?
nanodjango lets you write models, views, admin and a Ninja API in a single Python file, run it with one command, and convert it into a conventional multi-file Django project when it outgrows that shape. It is a good fit for prototypes and demos that should not become throwaway code.
Who is it for?
nanodjango suits a developer building a prototype, a demo, an internal tool or a teaching example who wants a working Django site in minutes and a clean escape route into a normal project layout. It does not suit a service that will carry a team, because a single file with a global app instance has no place for the settings split, the app boundaries or the test fixtures a growing codebase needs, and nanodjango run pins you to its own defaults rather than your settings module.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 29 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One module holds the models, the views and the admin site

nanodjango is described as full Django in a single file: views, models and API, with async support, and it can convert that file into a full project automatically. The site is nanodjango.dev with documentation at docs.nanodjango.dev and a browser playground at nanodjango.dev/play.

The shape of a nanodjango app is one module and one global app object. Models are ordinary Django models registered with the admin site through a decorator, views are ordinary function or class-based views, and the API is Django Ninja mounted on the same app.

python
from django.db import models
from nanodjango import Django

app = Django()

@app.admin
class CountLog(models.Model):
    # Standard Django model, registered with the admin site
    timestamp = models.DateTimeField(auto_now_add=True)

@app.route("/")
def count(request):
    # Standard Django function view, or class-based view
    CountLog.objects.create()
    return f"<p>Number of page loads: {CountLog.objects.count()}</p>"

The comments in that sample are worth reading carefully, because they mark what has not been reinvented. A standard Django model, a standard Django function view, a standard class-based view. nanodjango is not a mini-framework that mimics Django, it is Django configured by a smaller amount of boilerplate. That is why the Django version floor in the package metadata is Django>=5.2 while the loose requirements.txt still lists django>=3.2.0, and why the framework can offer async views without a separate async story.

The API decorator follows the same idea, using Django Ninja under the hood:

python
@app.api.get("/add")
def add(request):
    # Django Ninja API support built in
    CountLog.objects.create()
    return {"count": CountLog.objects.count()}

And async views are just async functions on the same router:

python
@app.route("/slow/")
async def slow(request):
    import asyncio
    await asyncio.sleep(10)
    return "Async views supported"

nanodjango run creates the migrations and the database for you

The whole setup is one pip install and one command.

sh
pip install nanodjango
sh
nanodjango run counter.py

That run command creates migrations and a database, then starts the project in development mode. This is the point of the tool. The parts of Django that normally demand a project skeleton, a settings module, a urls module and an apps module are generated or implied, so the first thing you write is the part you care about rather than the scaffolding around it.

Management commands work the way they do in a normal project, with the app file standing in for manage.py:

sh
nanodjango manage script.py check
nanodjango manage script.py makemigrations script
nanodjango manage script.py runserver 0.0.0.0:8000

That covers the everyday commands a developer reaches for, including pointing the server at a specific address and port.

The console entry point is nanodjango.commands:invoke, and the command line is built on click, which shows up in the dependency list along with black, isort, gunicorn, uvicorn, pluggy, whitenoise and django-ninja. The presence of black and isort is not incidental: nanodjango formats and sorts your single file for you, so the file you hand to a colleague stays readable without a separate formatting step.

The package classifies itself as Development Status :: 4 - Beta and requires Python 3.8 or newer.

Converting the single file into a conventional Django project

The escape route is the command that makes nanodjango more than a toy. When the project outgrows one file, it can be converted into a full Django site.

sh
nanodjango convert counter.py path/to/site --name=counter

The target directory and the application name are both explicit, so you choose where the project lands and what the app is called inside it. This is the operation that turns the experiment into something you can keep, and it is the reason to consider nanodjango in the first place if you are the kind of developer who worries about throwaway prototypes.

The conversion is not the only path to a grown project. The documentation points to a page on converting to a full Django project, and the examples folder shows the range of shapes a nanodjango file can take, including hello_world.py, hello_api.py, hello_async.py, hello_async_server.py, hello_template.py, hello_templatetags.py and hello_distill.py, plus directories for public, static and scale examples.

Two extra packages are available as optional extras named static and full, both of which install django-nanopages and django-distill. Those point at static site generation from Django views, which is a different deployment shape from the gunicorn or uvicorn server the default path uses. The example names suggest the same progression, from a single file to a site with templates, template tags and generated pages.

What the conversion does not do is give you back the original file as your new single source of truth. Once converted, you are in a normal Django project with the normal problems and the normal tools, which is the point.

Sharing an app as a runnable script with inline dependencies

nanodjango apps are described as good for sharing examples and prototypes, and the distribution mechanism for that is PEP 723 inline script metadata rather than a package on PyPI.

The dependency block goes at the top of the file:

python
# /// script
# dependencies = ["nanodjango"]
# ///

And the file needs a main guard that calls app.run:

python
if __name__ == "__main__":
    app.run()

With both in place, the script runs without installing anything, using uv or pipx:

sh
# Run with uv
uv run ./script.py
# or with pipx
pipx run ./script.py

That is a genuinely nice property for teaching examples and conference talks. A reader who has uv installed gets a working Django site from one file, with no virtual environment setup and no requirements file, and the dependency graph travels inside the script.

The traditional path still works if you prefer it, with pip install nanodjango followed by python script.py.

Note what the sharing mode gives up. The inline metadata lists dependencies only, so the app still relies on the surrounding environment for anything not in that list, and there is no custom settings file travelling with the script. It is a sharing convenience rather than a self-contained distribution format.

Production mode picks gunicorn or uvicorn depending on your views

For running beyond a development machine, nanodjango serve switches to production defaults.

sh
nanodjango serve counter.py

The behaviour depends on what the file contains. It uses gunicorn, or uvicorn if the app has async views, so an app with async endpoints is served by an ASGI capable server without you choosing. It also turns off settings.DEBUG and will not serve media files.

That last point deserves attention. Disabling DEBUG removes Django's development error pages, which is correct for production, but the side effect is that you lose the static file serving you get in development, and nanodjango deliberately does not step in to replace it. The whitenoise dependency in the package list is the intended answer for static files in production, and the documentation on the command usage page covers the options.

If you would rather wire the server yourself, the app object is importable, so it can be handed straight to a WSGI or ASGI server:

sh
gunicorn -w 4 counter:app
uvicorn counter:app

The module path is counter:app, which is the same global app object the decorators attached to. Handing that to your own server is the cleanest integration point in the whole tool, because it drops you into ordinary Django deployment with no nanodjango-specific wrapper in the way.

The documentation does not describe a container image, a reverse proxy configuration or a process manager recipe for nanodjango, so if you are deploying to a container platform you are assembling that yourself from the gunicorn or uvicorn entry points.

Licence, versioning and the absence of GitHub releases

The licence field on the repository says BSD-3-Clause, a permissive licence with a no-endorsement clause and a requirement that the notice travel with copies of the source. The package metadata classifies it as an OSI approved BSD licence and as Framework :: Django. This is not legal advice, but for a tool whose main purpose is to generate code you will keep, the terms are straightforward.

The version is dynamic, read from nanodjango.__version__ in the package metadata, and the project publishes to PyPI with a version badge pointing at the package index. The repository has no GitHub releases, so there is no tag history to consult and no changelog on the releases page. The changelog lives in the documentation instead, at docs.nanodjango.dev, and the README links it alongside the getting started guide, the tutorial and the full documentation.

The last push was on 2026-09-24, so the tree is still being worked on. The project also carries a .readthedocs.yaml for the documentation build, a .pre-commit-config.yaml, and a pytest configuration with coverage reporting enabled by default, where the test paths cover the tests directory, the nanodjango package itself and the examples directory. Testing the examples as part of the suite is a reasonable way to keep the documented shapes working.

Editorial conclusion

nanodjango suits a developer building a prototype, a demo, an internal tool or a teaching example who wants a working Django site in minutes and a clean escape route into a normal project layout. It does not suit a service that will carry a team, because a single file with a global app instance has no place for the settings split, the app boundaries or the test fixtures a growing codebase needs, and nanodjango run pins you to its own defaults rather than your settings module. Before adopting it, check that Django 5.2 or newer is acceptable to you since that is the pinned floor, and run nanodjango convert counter.py path/to/site --name=counter early to confirm the converted project matches your expectations before you have built on top of the single file.

Frequently asked questions

How do I create and run a nanodjango app?

Install it with pip install nanodjango, write your app in a single Python file using the Django app object, then run nanodjango run counter.py. That command creates the migrations and database and starts the project in development mode.

Can I convert a nanodjango file into a normal Django project?

Yes. Run nanodjango convert counter.py path/to/site --name=counter to generate a full Django site from the single file, with the target directory and application name chosen by you. This is the documented path when a project outgrows the single file.

How do I run nanodjango in production?

Use nanodjango serve counter.py, which selects gunicorn or uvicorn depending on whether the app has async views, turns off settings.DEBUG and does not serve media files. You can also pass the app object directly to a server, for example gunicorn -w 4 counter:app or uvicorn counter:app.

Can I share a nanodjango app as a single runnable script?

Yes. Add PEP 723 inline script metadata listing the nanodjango dependency at the top of the file and call app.run() under a __main__ guard, then run it with uv run ./script.py or pipx run ./script.py without installing anything first.

Official sources

  1. Issues
  2. License: BSD-3-Clause
  3. Project website
  4. radiac/nanodjango on GitHub
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/radiac-nanodjango.svg)](https://hysenlabs.com/projects/radiac-nanodjango)

Was this useful?