Oct 23, 20265 min read

FastAPI vs Flask

How FastAPI and Flask compare on async, validation, API docs, extensions and migration effort, with the same endpoint written in each.

FastAPI and Flask look alike at first glance, with a decorator per route and a small amount of code for a working app. Underneath, Flask is a synchronous WSGI framework from 2010, and FastAPI is an async ASGI framework built around type hints. The difference decides how requests are validated, how the app handles concurrent I/O and what a migration between the two costs.

The main differences:

  • FastAPI validates and documents requests from type hints, while Flask reads the body with request.get_json() and leaves validation to you.
  • FastAPI handlers can be async on an event loop, while Flask runs each request on a worker.
  • FastAPI generates an OpenAPI schema and interactive docs, while Flask needs an extension for that.
  • Flask has a larger set of mature extensions, including ones for forms and admin screens.

FastAPI vs Flask at a glance

CapabilityEncoreFastAPIFlask
RoleInfrastructure SDK added to either frameworkAPI frameworkMicroframework
Server interfaceFrom the frameworkASGIWSGI
ValidationFrom the frameworkPydantic models from type hintsManual or through extensions
API docsFrom the frameworkOpenAPI and Swagger UI built inThrough extensions
AsyncAsync SDK, callable from sync viewsNativeAsync views run in a thread

FastAPI: types as the contract

FastAPI turns a Pydantic model in the signature into both validation and documentation. The request body tutorial shows how a model becomes a JSON body with field-level errors, and Pydantic models does the validation work.

import os from contextlib import asynccontextmanager import asyncpg from fastapi import FastAPI, Request from pydantic import BaseModel class Signup(BaseModel): email: str @asynccontextmanager async def lifespan(app: FastAPI): app.state.pool = await asyncpg.create_pool(os.environ["DATABASE_URL"]) yield await app.state.pool.close() app = FastAPI(lifespan=lifespan) @app.post("/users", status_code=201) async def create_user(req: Signup, request: Request) -> dict: await request.app.state.pool.execute( "INSERT INTO users (email) VALUES ($1)", req.email ) return {"email": req.email}

The generated schema follows the OpenAPI specification, so clients and gateways that read OpenAPI can use it directly. An AI coding agent benefits from the same types, because a wrong field name fails validation in a test instead of passing silently.

Flask: request objects and extensions

Flask exposes the incoming request as a context-local request object, and the Flask quickstart covers routing, request data and responses in one page. The same endpoint:

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

Nothing checks that email exists or is a string. Flask apps add that through marshmallow, Pydantic or an extension, and each project picks its own.

Performance

Under load with many slow upstream calls, FastAPI on Uvicorn can keep more requests in flight per process because handlers yield while waiting. Flask scales by running more workers, which costs memory. For CPU-bound handlers or a fast local database, the gap is small.

Adding infrastructure to either framework with Encore

Encore combines the framework you chose with infrastructure declared in Python. It uses an infrastructure SDK installed as encore-dev, and FastAPI or Flask keeps routing and request handling. Postgres databases, Pub/Sub topics, object storage buckets, cron jobs and secrets are module-level declarations that Encore discovers and provisions.

A nightly cleanup in FastAPI is a database declaration, a cron job and the route the job calls:

from encore import cron from encore.storage import postgres from fastapi import FastAPI db = postgres.Database("users", migrations="./migrations") app = FastAPI() _ = cron.Job( "purge-unverified", path="/internal/purge-unverified", title="Delete unverified signups", schedule="0 3 * * *", ) @app.post("/internal/purge-unverified", status_code=204) async def purge_unverified() -> None: await db.exec( "DELETE FROM users WHERE verified = false" " AND created_at < now() - interval '7 days'" )

In Flask the declarations are the same, and the synchronous route connects with psycopg through the declared database's connection string:

import psycopg from encore import cron from encore.storage import postgres from flask import Flask db = postgres.Database("users", migrations="./migrations") app = Flask(__name__) _ = cron.Job( "purge-unverified", path="/internal/purge-unverified", title="Delete unverified signups", schedule="0 3 * * *", ) @app.post("/internal/purge-unverified") def purge_unverified(): with psycopg.connect(db.connection_string) as conn: conn.execute( "DELETE FROM users WHERE verified = false" " AND created_at < now() - interval '7 days'" ) return "", 204

encore run starts Postgres locally and applies the migrations. Cron jobs do not run locally or in preview environments, so call the route directly to test it, and the CLI reference covers encore test for tests against Encore-managed resources.

On deploy, Encore provisions the database in your own AWS or GCP account and calls the route on schedule.

An agent asked for a nightly job adds the cron.Job next to the route it calls, so a reviewer reads the schedule and the code in one diff. Cloud access sits with Encore's IAM role, not the agent. The AI integration guide covers how to hand an agent these conventions.

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

Try Encore →

How to choose

  • FastAPI for new JSON APIs where validation from types and async I/O pay off.
  • Flask for server-rendered apps and codebases that depend on specific Flask extensions.

Encore works with either when you want the database and scheduled jobs provisioned in your own cloud account.

Frequently asked questions

Should I use FastAPI or Flask?

Use FastAPI for new JSON APIs that benefit from async handlers and validation from type hints. Use Flask when the app renders templates, relies on mature Flask extensions or the team already knows it well.

Is FastAPI faster than Flask?

For I/O-bound work, usually yes. FastAPI handlers can await database and network calls on an ASGI server, while Flask holds a worker for each request, including async views.

How do I migrate from Flask to FastAPI?

Route by route. Each route trades request.get_json() for a Pydantic model in the signature, and Flask-only extensions such as Flask-SQLAlchemy need replacements that work outside Flask. Mounting the Flask app inside FastAPI with WSGIMiddleware lets both run while you move routes.

How do I add a database and a cron job to a FastAPI or Flask app?

Install the encore-dev package, declare postgres.Database and cron.Job at module scope, and add a POST route at the cron job's path. encore run starts Postgres locally, and on deploy Encore calls the route on schedule and provisions the database 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