From Flask to FastAPI: A Comprehensive Transition Guide

leo 28/11/2025

Python stands as one of the most popular programming languages. Its footprint is found everywhere, from scripting and API development to machine learning. Python’s popularity stems from its focus on developer experience and the vast ecosystem of tools it provides. The web framework Flask is one such tool, highly favored within the machine learning community and widely used for API development. However, a new framework is on the rise: FastAPI. Unlike Flask, FastAPI is an ASGI (Asynchronous Server Gateway Interface) framework. Alongside Go and NodeJS, FastAPI ranks among the fastest Python-based web frameworks available.

This article compares and contrasts common patterns in Flask and FastAPI for those interested in transitioning from Flask to FastAPI.

FastAPI vs Flask

FastAPI was built with three primary concerns in mind:

  • Speed
  • Developer Experience
  • Open Standards

You can think of FastAPI as the glue that binds Starlette, Pydantic, OpenAPI, and JSON Schema together.

At its core, FastAPI uses Pydantic for data validation and Starlette as a toolkit, making it incredibly fast compared to Flask, delivering performance on par with high-speed web APIs in Node or Go. Starlette and Uvicorn provide the asynchronous request capabilities that Flask lacks.

With Pydantic and type hints, you get a great editor experience with autocompletion. You also benefit from data validation, serialization and deserialization (for building an API), and automated documentation (via JSON Schema and OpenAPI).

That said, Flask is more widely used, making it battle-tested and backed by a larger community. Since both frameworks are designed for extensibility, Flask is the clear winner here due to its enormous ecosystem of plugins.

Recommendation:

  • Use FastAPI if you identify with the three concerns mentioned above, are tired of the plethora of choices when extending Flask, wish to leverage asynchronous requests, or simply want to build a RESTful API.
  • Use Flask if you are uncomfortable with FastAPI’s maturity, need to build a full-stack application with server-side templates, or rely heavily on certain community-maintained Flask extensions.

Getting Started

Installation

Installation is straightforward, like any other Python package.

Flask

bash

pip install flask

# or
poetry add flask
pipenv install flask
conda install flask

FastAPI

bash

pip install fastapi uvicorn

# or
poetry add fastapi uvicorn
pipenv install fastapi uvicorn
conda install fastapi uvicorn -c conda-forge

Unlike Flask, FastAPI does not have a built-in development server, so you need an ASGI server like Uvicorn or Daphne.

“Hello World” Application

Flask

python

# flask_code.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return {"Hello": "World"}

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

FastAPI

python

# fastapi_code.py
import uvicorn
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def home():
    return {"Hello": "World"}

if __name__ == "__main__":
    uvicorn.run("fastapi_code:app")

Parameters like reload=True can be passed to uvicorn.run() for hot-reloading during development.

Alternatively, you can start the server directly from the terminal:

bash

uvicorn run fastapi_code:app

With hot reload:

bash

uvicorn run fastapi_code:app --reload

Configuration

Both Flask and FastAPI offer multiple options for handling configurations in different environments. Both support the following patterns:

  • Environment variables
  • Configuration files
  • Instance folders
  • Classes and inheritance

For more information, refer to their respective documentation:

Flask

python

import os
from flask import Flask

class Config(object):
    MESSAGE = os.environ.get("MESSAGE")

app = Flask(__name__)
app.config.from_object(Config)

@app.route("/settings")
def get_settings():
    return { "message": app.config["MESSAGE"] }

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

Before running the server, set the appropriate environment variable:

bash

export MESSAGE="hello, world"

FastAPI

python

import uvicorn
from fastapi import FastAPI
from pydantic import BaseSettings

class Settings(BaseSettings):
    message: str

settings = Settings()
app = FastAPI()

@app.get("/settings")
def get_settings():
    return { "message": settings.message }

if __name__ == "__main__":
    uvicorn.run("fastapi_code:app")

Again, set the environment variable before running the server:

bash

export MESSAGE="hello, world"

Routing, Templates, and Views

HTTP Methods

Flask

python

from flask import request

@app.route("/", methods=["GET", "POST"])
def home():
    # handle POST
    if request.method == "POST":
        return {"Hello": "POST"}
    # handle GET
    return {"Hello": "GET"}

FastAPI

python

@app.get("/")
def home():
    return {"Hello": "GET"}

@app.post("/")
def home_post():
    return {"Hello": "POST"}

FastAPI provides separate decorators for each method: @app.get()@app.post()@app.delete()@app.patch(), etc.

URL Parameters

Pass information via the URL (e.g., /employee/1) to manage state.

Flask

python

@app.route("/employee/<int:id>")
def home():
    return {"id": id}

FastAPI

python

@app.get("/employee/{id}")
def home(id: int):
    return {"id": id}

URL parameters are specified similarly to an f-string expression. Additionally, you can leverage type hints. Here, we are telling Pydantic that id is of type int. This also aids in better code completion during development.

Query Parameters

Like URL parameters, query parameters (e.g., /employee?department=sales) can be used to manage state, often for filtering or sorting.

Flask

python

from flask import request

@app.route("/employee")
def home():
    department = request.args.get("department")
    return {"department": department}

FastAPI

python

@app.get("/employee")
def home(department: str):
    return {"department": department}

Templates

Flask

python

from flask import render_template

@app.route("/")
def home():
    return render_template("index.html")

By default, Flask looks for templates in a “templates” folder.

FastAPI
You need to install Jinja:

bash

pip install jinja2

Implementation:

python

from fastapi import Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse

app = FastAPI()

templates = Jinja2Templates(directory="templates")

@app.get("/", response_class=HTMLResponse)
def home(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

In FastAPI, you must explicitly define the “templates” directory. For each response, you need to provide the request context.

Static Files

Flask
By default, Flask serves static files from a “static” folder.

FastAPI
In FastAPI, you need to mount a folder for static files:

python

from fastapi.staticfiles import StaticFiles

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

Asynchronous Tasks

Flask
Starting with Flask 2.0, you can use async/await to create asynchronous route handlers:

python

@app.route("/")
async def home():
    result = await some_async_task()
    return result

For more on asynchronous views in Flask, check out the Async in Flask 2.0 article.

Asynchrony in Flask can also be achieved using threading (concurrency) or multiprocessing (parallelism), or with tools like Celery or RQ:

FastAPI
FastAPI greatly simplifies asynchronous tasks due to its native support for asyncio. To use it, just add the async keyword to your view function:

python

@app.get("/")
async def home():
    result = await some_async_task()
    return result

FastAPI also has a Background Tasks feature, which you can use to define operations to run after a response is sent. This is useful for actions that don’t need to complete before the response is sent back.

python

from fastapi import BackgroundTasks

def process_file(filename: str):
    # process file :: takes minimum 3 secs (just an example)
    pass

@app.post("/upload/{filename}")
async def upload_and_process(filename: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(process_file, filename)
    return {"message": "processing file"}

Here, the response will be sent instantly without making the user wait for the file processing to complete.

When you need heavy background computation, or require a task queue to manage tasks and workers, you might want to use Celery instead of BackgroundTasks. For more, see Asynchronous Tasks with FastAPI and Celeryhttps://testdriven.io/blog/fastapi-and-celery/

Dependency Injection

Flask
While you can implement your own dependency injection solution, Flask doesn’t have true first-class support for it by default. Instead, you need to use an external package like flask-injector.

FastAPI
On the other hand, FastAPI has a powerful solution for handling dependency injection.

python

from databases import Database
from fastapi import Depends
from starlette.requests import Request

from db_helpers import get_all_data

def get_db(request: Request):
    return request.app.state._db

@app.get("/data")
def get_data(db: Database = Depends(get_db)):
    return get_all_data(db)

Here, get_db will obtain a reference to the database connection created in the application’s startup event handler. Depends is then used to tell FastAPI that the route “depends” on get_db. Thus, it should be executed before the code inside the route handler, and the result should be “injected” into the route itself.

Data Validation

Flask
Flask does not have any built-in data validation support. You can use the powerful Pydantic package for data validation via Flask-Pydantic.

FastAPI
One of the reasons FastAPI is so powerful is its support for Pydantic.

python

from pydantic import BaseModel

app = FastAPI()

class Request(BaseModel):
    username: str
    password: str

@app.post("/login")
async def login(req: Request):
    if req.username == "testdriven.io" and req.password == "testdriven.io":
        return {"message": "success"}
    return {"message": "Authentication Failed"}

Here, we accept input modeled by Request. The payload must contain a username and password.

Example with correct payload format:

bash

curl -X POST 'localhost:8000/login' \
    --header 'Content-Type: application/json' \
    --data-raw '{"username": "testdriven.io","password":"testdriven.io"}'

{"message":"success"}

Example with incorrect payload format (notice passwords instead of password):

bash

curl -X POST 'localhost:8000/login' \
    --header 'Content-Type: application/json' \
    --data-raw '{"username": "testdriven.io","passwords":"testdriven.io"}'

{"detail":[{"loc":["body","password"],"msg":"field required","type":"value_error.missing"}]}

The Pydantic model automatically informs the user that the password field is missing.

Serialization & Deserialization

Flask
The simplest way to serialize is to use jsonify:

python

from flask import jsonify
from data import get_data_as_dict

@app.route("/")
def send_data():
    return jsonify(get_data_as_dict)

For more complex objects, Flask developers often use Flask-Marshmallow.

FastAPI
FastAPI automatically serializes any returned dict. For more complex and structured data, use Pydantic models:

python

from pydantic import BaseModel

app = FastAPI()

class Request(BaseModel):
    username: str
    email: str
    password: str

class Response(BaseModel):
    username: str
    email: str

@app.post("/login", response_model=Response)
async def login(req: Request):
    if req.username == "testdriven.io" and req.password == "testdriven.io":
        return req
    return {"message": "Authentication Failed"}

Here, we added a Request model with three inputs: username, email, and password. We also defined a Response model containing only the username and email. The input Request model handles deserialization, while the output Response model handles object serialization. The response model is then passed to the decorator via the response_model argument.

Now, if we return the request itself as the response, Pydantic will omit the password because our defined response model does not include a password field.

Example output:

bash

curl -X POST 'localhost:8000/login' \
    --header 'Content-Type: application/json' \
    --data-raw '{"username":"testdriven.io","email":"admin@testdriven.io","password":"testdriven.io"}'

{"username":"testdriven.io","email":"admin@testdriven.io"}

Middleware

Middleware is used to apply logic before each request is processed by the view function.

Flask

python

import time

class middleware:
    def __init__(self, app) -> None:
        self.app = app

    def __call__(self, environ, start_response):
        start = time.time()
        response = self.app(environ, start_response)
        end = time.time() - start
        print(f"request processed in {end} s")
        return response

app = Flask(__name__)
app.wsgi_app = middleware(app.wsgi_app)

FastAPI

python

import time
from fastapi import Request

@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    print(f"request processed in {process_time} s")
    return response

The @app.middleware("http") decorator is essential for creating middleware in FastAPI. The above middleware calculates the time taken to process a request. After the view function processes the request, the total processing time is calculated and printed.

Example logs:

bash

# flask output(logs)
request processed in 0.0010077953338623047 s
127.0.0.1 - - [22/Sep/2020 18:56:21] "GET / HTTP/1.1" 200 -

# fastapi output(logs)
request processed in 0.0009925365447998047 s
INFO:     127.0.0.1:51123 - "GET / HTTP/1.1" 200 OK

Modularization

As your application grows, you’ll want to group similar views, templates, static files, and models to help break the app into smaller components.

Flask
In Flask, Blueprints are used for modularization:

python

# blueprints/product/views.py
from flask import Blueprint

product = Blueprint("product", __name__)

@product.route("/product1")
def product1():
    ...

# main.py
from blueprints.product.views import product

app.register_blueprint(product)

FastAPI
In FastAPI, modularization is achieved via APIRouter:

python

# routers/product/views.py
from fastapi import APIRouter

product = APIRouter()

@product.get("/product1")
def product1():
    ...

# main.py
from routers.product.views import product

app.include_router(product)

Additional Features

Automated Documentation

Flask
Flask does not automatically create API documentation out of the box. However, several extensions can handle this, like flask-swagger and Flask RESTX, but they require additional setup.

FastAPI
FastAPI natively supports OpenAPI along with Swagger UI and ReDoc. This means every endpoint is automatically documented from metadata associated with the endpoint.

Admin Application

Flask
Flask has a widely used third-party admin package called Flask-Admin for quickly performing CRUD operations on your models.

FastAPI
As of now, two popular FastAPI extensions for this are:

  • FastAPI Admin – A functional admin panel that provides a user interface for performing CRUD operations on data.
  • SQLAlchemy Admin – An admin panel for FastAPI/Starlette that works with SQLAlchemy models.

Authentication

Flask
While Flask has no native solution, many third-party extensions can be used.

FastAPI
FastAPI natively supports many security and authentication tools through the fastapi.security package. With a few lines of code, you can add basic HTTP authentication to your app:

python

import secrets

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI()

security = HTTPBasic()

def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
    correct_username = secrets.compare_digest(credentials.username, "stanleyjobson")
    correct_password = secrets.compare_digest(credentials.password, "swordfish")
    if not (correct_username and correct_password):
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
    return credentials.username

@app.get("/whoami")
def who_am_i(username: str = Depends(get_current_username)):
    return {"username": username}

FastAPI also facilitates implementing OAuth2 and OpenID Connect via the OpenAPI standard.

Check the following resources in the official documentation for more information:

Other resources:

CORS

CORS (Cross-Origin Resource Sharing) middleware checks if a request comes from an allowed origin. If it does, the request is passed to the next middleware or view function. If not, it rejects the request and sends an error response back to the caller.

Flask
Flask requires an external package called Flask-CORS to support CORS.

bash

pip install flask-cors

Basic implementation:

python

from flask_cors import CORS

app = Flask(__name__)

CORS(app)

FastAPI
FastAPI has native support for CORS:

python

from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = ["*"]

app.add_middleware(CORSMiddleware, allow_origins=origins)

Testing

Flask

python

import pytest
from flask import Flask

app = Flask(__name__)

@app.route("/")
def home():
    return {"message": "OK"}

def test_hello():
    res = app.test_client().get("/")

    assert res.status_code == 200
    assert res.data == b'{"message":"OK"}\n'

FastAPI

python

from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/")
async def home():
    return {"message": "OK"}

client = TestClient(app)

def test_home():
    res = client.get("/")

    assert res.status_code == 200
    assert res.json() == {"message": "OK"}

FastAPI provides a TestClient. With it, you can run pytest directly with FastAPI. For more info, check the Testing guide in the official documentation.

Deployment

Production Servers

Flask
Flask, by default, runs a development WSGI (Web Server Gateway Interface) application server. For production, you need to use a production-grade WSGI application server like Gunicorn, uWSGI, or mod_wsgi.

Install Gunicorn:

bash

pip install gunicorn

Start the service:

bash

# assuming main.py and app = Flask(__name__)
gunicorn main:app

FastAPI
Since FastAPI doesn’t have a development server, you’ll use Uvicorn (or Daphne) for both development and production.

Install Uvicorn:

bash

pip install uvicorn

Start the service:

bash

# assuming main.py and app = FastAPI()
uvicorn main:app

You might want to use Gunicorn to manage Uvicorn to leverage both concurrency (via Uvicorn) and parallelism (via Gunicorn workers):

bash

# assuming main.py and app = FastAPI()
gunicorn -w 3 -k uvicorn.workers.UvicornWorker main:app

Docker

Flask

dockerfile

FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["gunicorn", "main:app"]

This is one of the simplest Dockerfiles for Flask. For a full production configuration, check out the Dockerizing Flask with Postgres, Gunicorn, and Nginx tutorial.

FastAPI

dockerfile

FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .

RUN pip install -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app"]

Again, this is a very simple configuration. The FastAPI author provides several production-ready Dockerfile examples. For more information, review the official FastAPI documentation and the Dockerizing FastAPI with Postgres, Uvicorn, and Traefik tutorial.

Conclusion

Taking a step back, Django and Flask are the two most popular Python-based web frameworks (with FastAPI being the third). However, their philosophies are quite different. Flask’s advantage over Django is that it’s a micro-framework. The program structure is decided by the programmers themselves; it’s not enforced. Developers can add third-party extensions to improve their code as they see fit. That said, as a codebase grows, there’s typically a need for common features required by almost every web application. Having these features tightly integrated with the framework significantly reduces the amount of code that end developers need to create and maintain themselves.

The code examples in this article convey the same idea. In other words, FastAPI includes many of the necessary features out of the box. It also follows strict standards, making your code production-ready and easier to maintain. The documentation for FastAPI is also very well done.

While FastAPI might not be as battle-tested as Flask, an increasing number of developers are moving to it for serving machine learning models or developing RESTful APIs. Making the switch to FastAPI is an excellent choice.

Official Documentation

Additional Resources