Oct 23, 20265 min read

Django vs Flask

How Django and Flask compare on scope, data access, async support and project structure, with the same endpoint written in each.

Django and Flask have been the two default answers for Python web apps for over a decade. Django decides most things for you, and Flask decides very little. The trade shows up as the app grows, when a Django team is working inside conventions it did not write and a Flask team is maintaining structure it wrote by hand.

The main differences:

  • Django ships its own ORM, migrations, auth, sessions and admin, while Flask provides routing and request handling and leaves the rest to extensions.
  • Django organizes code into a project with apps and a settings module, while Flask lets you structure the code however you like.

Django vs Flask at a glance

CapabilityEncoreDjangoFlask
RoleInfrastructure SDK added to either frameworkFull application frameworkMicroframework
Server interfaceFrom the frameworkWSGI or ASGIWSGI
Data accessPostgres declared in code, provisioned locally and in your cloudBuilt-in ORM and migrationsExtensions such as Flask-SQLAlchemy
AdminFrom the frameworkGenerated from modelsExtensions such as Flask-Admin
Async viewsFrom the frameworkSupported, with a sync-first ecosystemSupported, run in a thread per request
Project layoutFrom the frameworkProject, apps and settingsUp to you

Django: conventions included

A Django model describes a table, and the models documentation covers how fields, relations and managers map to SQL. Migrations come from makemigrations, and the admin and auth system read the same models.

import json from django.db import models from django.http import HttpRequest, JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST class User(models.Model): email = models.EmailField(unique=True) @csrf_exempt @require_POST def create_user(request: HttpRequest) -> JsonResponse: email = json.loads(request.body)["email"] user = User.objects.create(email=email) return JsonResponse({"id": user.pk, "email": user.email}, status=201)

The cost is that Django's way is the expected way, so swapping out the ORM or the settings module is possible but works against much of the ecosystem.

Flask: a small core

Flask's design decisions page explains why the core stays small and why it uses context locals such as request. The same endpoint is a route function that opens a connection with psycopg:

import os import psycopg from flask import Flask, request app = Flask(__name__) @app.post("/users") def create_user(): email = request.get_json()["email"] with psycopg.connect(os.environ["DATABASE_URL"]) as conn: conn.execute("INSERT INTO users (email) VALUES (%s)", (email,)) return {"email": email}, 201

Larger Flask apps usually adopt Flask-SQLAlchemy for models and blueprints for structure. Each of those is a choice the team makes and documents for itself.

Performance and deployment

Both run as WSGI apps behind servers such as Gunicorn, following the interface that PEP 3333 defines. Each request holds a worker until it finishes, so throughput scales with worker count. Django can also run under ASGI for async views. For most apps the database and external calls cost more time than either framework does.

Adding infrastructure to either framework with Encore

Encore combines the framework you already use with infrastructure declared in Python. The infrastructure SDK is installed as encore-dev, and Django or Flask keeps the routes and views. Postgres databases, Pub/Sub topics, object storage buckets, cron jobs and secrets are module-level declarations that Encore discovers and provisions.

The Flask app below writes signups to a declared database and stores avatars in a declared bucket. The bucket API is async, so the synchronous view calls it through async_to_sync:

import psycopg from asgiref.sync import async_to_sync from encore.storage import objects, postgres from flask import Flask, request db = postgres.Database("users", migrations="./migrations") avatars = objects.Bucket("avatars") app = Flask(__name__) @app.post("/users") def create_user(): email = request.get_json()["email"] with psycopg.connect(db.connection_string) as conn: conn.execute("INSERT INTO users (email) VALUES (%s)", (email,)) return {"email": email}, 201 @app.put("/users/<int:user_id>/avatar") def upload_avatar(user_id: int): async_to_sync(avatars.upload)( f"{user_id}.png", request.get_data(), content_type="image/png" ) return "", 204

A Django view uses the same pattern for the bucket, and the ORM connects through db.connection_string in settings.py:

from asgiref.sync import async_to_sync from django.http import HttpRequest, HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from encore.storage import objects avatars = objects.Bucket("avatars") @csrf_exempt @require_http_methods(["PUT"]) def upload_avatar(request: HttpRequest, user_id: int) -> HttpResponse: async_to_sync(avatars.upload)( f"{user_id}.png", request.body, content_type="image/png" ) return HttpResponse(status=204)

encore run starts Postgres locally and serves the bucket through a local emulator, and the CLI reference lists encore test, which runs tests against Encore-managed resources.

A deploy provisions the database and bucket in your own AWS or GCP account.

When a coding agent adds a bucket for exports, the declaration and the view are one change the agent can run locally, and cloud access sits with Encore's IAM role, not the agent. See the AI integration guide for the rules files that describe these conventions to an agent.

Python support is in beta, and you can request access.

Try Encore →

How to choose

  • Django when the app centers on relational data and you want the admin and auth included.
  • Flask when the service is small or you want to choose each library yourself.

Encore works with either one when you want the database and buckets running locally and provisioned in your own cloud account.

Frequently asked questions

Should I use Django or Flask?

Use Django when the app is relational and you want the ORM, migrations, auth and admin to come with the framework. Use Flask for smaller services where you prefer a minimal core and pick each extension yourself.

Is Flask easier to learn than Django?

A first Flask app is shorter, because a single file with a route is a working app. Django asks you to learn projects, apps, settings and the ORM up front, and in return those parts are already decided as the app grows.

Does Flask have an ORM?

No. Most Flask apps use SQLAlchemy through the Flask-SQLAlchemy extension, with Alembic or Flask-Migrate for migrations.

How do I add Postgres and object storage to a Django or Flask app?

Install the encore-dev package and declare postgres.Database and objects.Bucket as module-level variables. Django and Flask code can connect to the database through db.connection_string, and synchronous views call the async bucket methods with asgiref's async_to_sync. encore run starts Postgres and the bucket locally, and a deploy provisions them in your own AWS or GCP account.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders