Sjango: Django-Style Structure for Sanic's Async Python Web Framework

I love Django's project layout but wanted async-native Python. So I built a minimal boilerplate that brings apps, models, views, and urls to Sanic — with Tortoise ORM and Pydantic Settings.

If you’ve worked with Django, you know the layout: apps/, models.py, views.py, urls.py. It’s opinionated, but the opinions are good. When a new developer joins a Django project, they already know roughly where everything lives.

I wanted that same predictability with an async-native Python web framework. Django’s ORM is synchronous at its core (async support exists but is a retrofit), and that matters when you’re building something IO-heavy.

Sjango is the result — a minimal boilerplate that maps Django’s structural conventions onto Sanic.

The stack

  • Sanic for the async HTTP server (Python’s fastest async web framework)
  • Tortoise ORM for async database access with a Django-like model API
  • Pydantic Settings for configuration via environment variables
  • sanic-ext for Jinja2 template rendering
  • Blueprint-based routing with path() and include() helpers that mirror Django’s urlpatterns

The project structure

app.py              # Sanic entry point
urls.py             # Root URL configuration
core/
  settings.py       # Pydantic-based settings
  routing.py        # path() / include() helpers
  middleware.py     # Middleware loader
apps/
  users/            # model, views, urls
  blog/             # model, views, urls
  payments/         # model, views, urls
templates/          # Jinja2 templates

Adding a new app is exactly what a Django developer would expect: create the directory, define urlpatterns in urls.py, register it in the root urls.py with include(), and add it to INSTALLED_APPS so Tortoise picks up its models.

Why I built it

Partly frustration: every “Sanic starter” I found online was either a single app.py file with everything in it, or a massively over-engineered template with Docker, CI/CD, authentication, caching, and seventeen other things I didn’t need.

Partly because I wanted to understand Sanic better. Building the routing helpers (path() and include()) from scratch forced me to read Sanic’s blueprint and router APIs carefully, and I came away with a much better mental model of how Sanic handles routing internally.

The routing layer

Django’s path() function returns a route object; include() mounts a list of those routes under a prefix. Reproducing this in Sanic required wrapping Sanic’s Blueprint in a thin API:

# core/routing.py
def path(url, view, name=None):
    return (url, view, name)

def include(prefix, patterns):
    bp = Blueprint(prefix, url_prefix=prefix)
    for url, view, name in patterns:
        bp.add_route(view.as_view(), url, name=name)
    return bp

Then the root urls.py just collects blueprints and attaches them to the Sanic app. It’s a thin wrapper, but it makes the intent clear.

Class-based views

Django’s class-based views are a love-it-or-hate-it feature. I included a minimal BaseView that maps HTTP methods to handler methods:

class UserListView(BaseView):
    async def get(self, request):
        users = await User.all()
        return json([u.to_dict() for u in users])

No magic beyond method dispatch — none of Django’s get_queryset, get_context_data, mixins. Just a clean base class that routes GET, POST, etc. to the right method.

Middleware

Django’s middleware is a list of class paths in settings.py. I replicated that: MIDDLEWARE is a list of dotted import paths to async functions. At startup, the middleware loader imports each one and attaches it to the Sanic app. Adding middleware to a new project is a one-line change in settings.

What’s missing (intentionally)

There’s no authentication system, no admin interface, no form library. Those are real framework features — Sjango is a boilerplate, not a framework. It gives you the structure and wires together the tools; you bring the application logic.

Try it

The project is on GitHub. Clone it, install dependencies, copy .env.example to .env, and run python app.py. You’ll have a working Sanic server with a users app, a blog app, and a payments app, all wired up via the Django-style routing layer.