Get started
← Back to all posts

Ultimate guide to FastAPI library in Python

By Katerina Hynkova

Updated on August 22, 2025

FastAPI is a modern open-source Python web framework for building high-performance APIs, released in 2018 by Sebastián Ramírezen.

Illustrative image for blog post

It gained popularity rapidly due to its ease of use, intuitive design, and speed, leveraging standard Python type hints for data validation and documentation. FastAPI is built atop Starlette (ASGI toolkit) and Pydantic (data validation library), which enable its performance and features. In fact, FastAPI’s performance is on par with Node.js and Go for API workloads, and it’s one of the fastest Python frameworks available according to independent benchmarks.

Community and adoption: Since its inception, FastAPI has seen explosive growth. By late 2024 it surpassed older frameworks like Flask in popularity – FastAPI reached 78.9k stars on GitHub, overtaking Flask’s 68.4k. Surveys show FastAPI rising from obscurity to being used by ~25% of Python web developers by 2023. Major companies have adopted FastAPI for production systems, including Microsoft, Uber, and Netflix, thanks to its reliability and performance. FastAPI is distributed under the permissive MIT license and has an active community contributing to its ecosystem. In summary, FastAPI’s combination of modern Python features (async/await, type hints), automatic docs, and robust performance has made it a go-to choice for building APIs in Python in 2024–2025.

How FastAPI works: architecture and key components

FastAPI is designed as a lightweight layer combining several powerful components in the Python ecosystem:

  • ASGI and starlette: FastAPI is built on Starlette, an ASGI (Asynchronous Server Gateway Interface) framework. ASGI support means FastAPI can handle asynchronous requests concurrently, unlike traditional WSGI frameworks. Starlette provides the underlying routing, middleware, and async server capabilities that FastAPI extends. When you run a FastAPI app, you typically use an ASGI server like Uvicorn to serve it. Uvicorn is a lightning-fast ASGI server that executes FastAPI apps and handles I/O at scale. Together, Starlette and Uvicorn enable FastAPI’s event loop and non-blocking request handling.

  • Pydantic for data models: FastAPI heavily utilizes Pydantic for data parsing and validation. You define Pydantic BaseModel classes to represent request bodies or response schemas. FastAPI automatically converts incoming JSON into Python objects and validates fields (types, ranges, etc.) before your endpoint logic runs. This provides a robust layer of error checking with minimal code. Pydantic also serializes responses back to JSON. By using standard Python type hints and Pydantic models, FastAPI ensures that data is validated, parsed, and documented according to your models.

  • Dependency injection system: FastAPI includes a simple yet powerful dependency injection mechanism via the Depends() function. This lets you declare “dependencies” (like database connections, authentication checks, etc.) that are resolved for each request. Under the hood, FastAPI’s dependency injector handles calling these functions (which can be normal or async) and provides their results to your endpoint function. This promotes clean code architecture and resource sharing (e.g., one DB session per request).

  • Automatic documentation (OpenAPI): FastAPI automatically generates an OpenAPI schema (formerly Swagger) for your API. Thanks to using Pydantic and type hints, it documents all endpoints, parameters, and schemas without extra work. Two interactive documentation UIs are included by default: Swagger UI and ReDoc, available at /docs and /redoc URLs. These allow developers to explore and test the API in the browser. (We’ll see an example of the docs interface shortly.)

  • Integration of components: Importantly, FastAPI isn’t a large monolith – it’s a glue of optimized components. For example, when a request arrives, Uvicorn (ASGI server) hands it to Starlette, which matches a route and calls FastAPI’s endpoint function. FastAPI processes dependencies and Pydantic model validation, then your function runs (either sync or async). The result (e.g. a Pydantic model or dict) is serialized (via Pydantic/JSON) and sent back as a response. This layered architecture keeps FastAPI lean yet powerful, reusing proven libraries.

Key framework components summary:

  • FastAPI class: Creates the application instance and includes methods for routing.

  • Path operation decorators: (@app.get, @app.post, etc.) provided by FastAPI for declaring endpoints.

  • Pydantic BaseModel: Used for defining request/response data schemas with validation.

  • Starlette: Provides Request/Response classes, routing, middleware, WebSocket support, etc.

  • Uvicorn: ASGI server that runs the app (you can also use alternatives like Hypercorn, Gunicorn with Uvicorn workers, etc.).

  • OpenAPI schema: FastAPI generates a JSON schema describing the API, powering the docs UIs and allowing client code generation.

FastAPI’s design is an example of composition: it “stands on the shoulders” of Starlette and Pydantic. This means when you use FastAPI, you also benefit from improvements in those libraries (for instance, Pydantic 2.0’s speedups, Starlette’s middleware ecosystem, etc.) with minimal overhead introduced by FastAPI itself.

Benefits and use cases of FastAPI

FastAPI offers numerous benefits that make it especially appealing to new Python developers building web APIs:

  • High performance: As the name suggests, FastAPI is built for speed. It’s capable of handling very high throughput thanks to async I/O. Benchmarks (e.g., TechEmpower) show FastAPI (with Uvicorn) can handle on the order of 15k–20k requests per second for simple endpoints, making it one of the fastest Python frameworks (only behind lower-level Starlette/Uvicorn which FastAPI uses internally). This means you get production-level performance out of the box, on par with Node.js and Go APIs, which is reassuring as your project scales.

  • Developer friendly (easy to learn): FastAPI is designed to be intuitive and easy to use. If you know basic Python, you can pick up FastAPI quickly because you declare routes using standard function definitions and type annotations – there’s no new syntax to learn. Many common errors are eliminated by design: for example, if you forget to validate input, FastAPI (via Pydantic) does it for you. The framework was tested against the most popular editors to ensure great editor support and autocompletion. This means as you code, your IDE will suggest attribute names and flag type errors, reducing bugs by ~40% (as claimed by FastAPI’s author). New developers often report that FastAPI “feels like writing regular Python” with helpful guidance from the tools.

  • Less code and fewer bugs: FastAPI follows a “sensible defaults” philosophy. Many features are auto-configured or inferred, so you write minimal boilerplate. For instance, a single Pydantic model class defines both the validation schema and example in docs and doesn’t need separate serializer code. This reduces the amount of code you write and maintain (developers have noted ~30-40% less code compared to equivalent Flask apps). With less code and strong typing, there are fewer opportunities for human error. Additionally, error messages in FastAPI/Pydantic are quite descriptive (pointing out exactly which field is wrong, etc.), which helps debugging.

  • Automatic interactive documentation: A standout feature is the built-in docs UI. FastAPI automatically provides Swagger UI and ReDoc interfaces that document every endpoint, parameter, and schema. For a beginner, this is incredibly useful – you can visually explore and test your API without writing any docs yourself. It’s gratifying and educational to see your API documented as you build it. The docs UIs also make it easier to share your API with others (front-end developers, QA, etc.) who can understand and try it out live.

  • Validation and security out of the box: FastAPI provides validation of incoming data (types, ranges, formats) without any extra effort, thanks to Pydantic models and Python types. This means new developers are less likely to accidentally accept bad data – FastAPI will return clear 400 errors with details if the input is invalid. Security features like authentication, OAuth2, and CORS are also supported via utilities and dependency injection, so implementing common auth flows is straightforward (important for production-readiness even in beginner projects).

  • Asynchronous concurrency: FastAPI’s async support is a huge benefit for use cases involving I/O (database calls, external API calls, file operations). A traditional Flask app (WSGI) would block one request while waiting on I/O, but FastAPI can serve other requests during waits. This means for I/O-heavy applications (common in web services), FastAPI can handle many simultaneous connections efficiently. For example, one user on Reddit noted that FastAPI’s async nature made it ideal for their AI app, which needed to call external APIs and perform data processing concurrently. For beginners, this means you don’t have to deeply understand multi-threading – using async def functions gives performance benefits automatically.

  • Use cases especially for new developers: FastAPI is particularly well-suited for:

    • RESTful APIs and microservices: If you’re building a backend for a web or mobile app, FastAPI allows you to quickly create JSON APIs. It’s focused on API use cases, unlike heavier frameworks. You can start with a simple “Hello World” and grow to more complex endpoints easily (we’ll see an example soon).

    • Data science & machine learning applications: Many beginners in data science turn to FastAPI to deploy models or data analysis as an API. FastAPI’s performance helps serve ML models quickly, and its easy syntax lowers the barrier to deployment for those not originally software engineers. (Indeed, Uber’s Ludwig ML library uses FastAPI to serve predictions in a production setting, and many Kaggle prototypes use FastAPI for exposing models.)

    • Real-time and async services: FastAPI’s ability to handle WebSockets and server-sent events makes it viable for real-time features (e.g. chat apps, live dashboards). A beginner can use the provided WebSocket support or background tasks to, say, push updates to clients without learning a complex async library. For example, one user cited FastAPI’s support for Server-Sent Events and OAuth2 as a reason it fit their real-time requirements well.

    • Prototyping and learning: Because of the interactive docs and minimal setup, FastAPI is great for quickly prototyping an idea or learning how web APIs work. You can run a dev server and play with your API in the browser (via Swagger UI) in minutes. This tight feedback loop is excellent for newcomers.

  • Community and documentation: FastAPI has excellent documentation with lots of examples and an active community. The official tutorial is very beginner-friendly, walking through step-by-step. There are many community resources (blogs, examples, and even a dedicated subreddit r/FastAPI). Beginners have ample places to get help – e.g., over 7,000 questions on Stack Overflow are tagged with FastAPI, and an official Discord channel for FastAPI exists for real-time help. This supportive ecosystem means you’re never “stuck” for long when learning FastAPI.

In summary, FastAPI’s combination of speed, automatic validation/docs, and developer experience (great editor support, less boilerplate) yields tangible benefits: one FastAPI user at Microsoft said “it’s beautifully designed, simple to use and highly scalable”, and that sentiment is echoed by many adopting it for new projects. For newcomers, FastAPI offers a gentle learning curve while encouraging best practices (type hints, clear error handling) from the start. It’s an excellent framework to quickly go from idea to production-ready API.

Installation and setup guide

Getting FastAPI up and running in your local development environment is straightforward. In this section, we’ll cover installation and setup across various scenarios: pip, Conda, virtual environments, IDEs like VS Code/PyCharm, operating systems (Windows, macOS, Linux), using Docker, and even a generic cloud deployment setup. FastAPI is compatible with Python 3.8+ (ensure you have a recent Python installed).

1. Using pip (Python package installer): The quickest way to install FastAPI is with pip. This works on Windows, Mac, and Linux as long as Python is installed:

  1. Install FastAPI: Open a terminal and run:

    pip install fastapi

    This installs the core FastAPI library. FastAPI itself is lightweight – under the hood it will pull in Pydantic and Starlette.

  2. Install an ASGI server: FastAPI requires an ASGI server to run your app. The most common choice is Uvicorn. Install it via:

    pip install "uvicorn[standard]" 

    The "[standard]" extra installs Uvicorn with high-performance enhancements like uvloop. Alternatively, you can use Hypercorn or another ASGI server, but Uvicorn is recommended for beginners.

  3. Verify installation: You can verify by importing in Python:

    import fastapi; import uvicorn
    print(fastapi.__version__, uvicorn.__version__)

    This should output version numbers without errors. As of 2025, FastAPI’s version is around 0.95+ (still in “0.x” but stable) and Uvicorn 0.22+.

Note: If you prefer, you can install both in one command. FastAPI provides an all-in-one install option:

pip install "fastapi[all]" 

This will install FastAPI and all optional dependencies, including Uvicorn and other extras (like orjson, uvloop). The FastAPI docs recommend fastapi[standard] (which is similar to [all]) for most cases.

2. Using Conda (Anaconda/Miniconda): If you manage packages with Conda, FastAPI is available via conda-forge:

  • Ensure you have added the conda-forge channel. Then run:

    conda install -c conda-forge fastapi uvicorn

    This will install FastAPI and Uvicorn in your Conda environment. Using Conda can be convenient for managing different Python versions or isolating a data science environment. If you’re using Anaconda Navigator (the GUI), you can search for “fastapi” and “uvicorn” packages on the conda-forge channel and install them via the GUI interface.

Tip: Create a new environment for your FastAPI project to avoid dependency conflicts. For example:

conda create -n fastapi-env python=3.10 fastapi uvicorn -c conda-forge
conda activate fastapi-env

This creates and activates an env with Python and FastAPI installed.

3. Setting up a virtual environment (venv): It’s a best practice to use an isolated virtual environment for your FastAPI project, especially when using pip.

  • Create a venv: Navigate to your project folder and run:

    python3 -m venv venv

    (On Windows, use python -m venv venv). This creates a directory venv/ with a standalone Python.

  • Activate the venv:

    • On Linux/Mac: source venv/bin/activate

    • On Windows: venv\Scripts\activate

  • Once activated (prompt shows venv), install FastAPI and Uvicorn inside it using pip as shown earlier. All packages will stay local to this project’s venv.

  • When done, deactivate with deactivate. You can reactivate whenever you continue development. This approach prevents FastAPI from affecting your global Python or other projects.

4. Using Visual Studio Code (VS Code): VS Code is a popular editor for FastAPI development. Steps to set up:

  • Python extension: Install the official Python extension in VS Code if not already. It provides IntelliSense and venv support.

  • Create a project folder: Open VS Code in your project directory. If you haven’t created a virtual environment yet, VS Code can help:

  • Select interpreter/environment: Press Ctrl+Shift+P (Cmd+Shift+P on Mac) and run “Python: Create Environment”. Choose Venv when prompted and select a Python version. When it asks, select your requirements.txt (if you have one) or just proceed to create an empty env.

  • Install dependencies: VS Code will create and activate a virtualenv for you. You can then open a terminal in VS Code (it should show the venv active) and run pip install fastapi uvicorn. Alternatively, create a requirements.txt with fastapi and uvicorn listed, then run “pip install -r requirements.txt”.

  • IntelliSense and Run/Debug: VS Code will detect FastAPI and even suggests a debugger config. You can write your FastAPI app (e.g., main.py), then run Debug (F5). VS Code’s Python extension has a pre-built FastAPI launch configuration that uses Uvicorn to run your app and attaches the debugger. Select “FastAPI” in the debug configuration dropdown, and VS Code will start Uvicorn for you, typically on http://127.0.0.1:8000. You can set breakpoints in your code and debug interactively.

  • Tip: Ensure the Python interpreter selected (shown in VS Code’s status bar) is the one from your venv or conda env that has FastAPI installed. VS Code might prompt you or you can change it via the Command Palette (“Python: Select Interpreter”).

5. Using PyCharm (or IntelliJ/PyCharm Pro): PyCharm Professional has built-in support for FastAPI:

  • New project: In PyCharm’s new project dialog, you can select FastAPI as the project type if using PyCharm Pro. This will set up a new project with a virtual environment and install FastAPI for you. It may also create a sample main file.

  • Community edition: If you’re using PyCharm CE (which doesn’t have the FastAPI template), simply create a Python project, then add FastAPI and Uvicorn via pip install (PyCharm will offer to create a virtualenv when you open the project).

  • Run/debug: PyCharm Pro can auto-create a Run/Debug configuration for FastAPI. It detects the uvicorn command. You can also manually create a run config: choose Uvicorn as the run target with main:app (assuming your FastAPI app instance is named app in main.py). PyCharm’s FastAPI support also includes an Endpoints tool window to list your API routes for easy navigation.

  • Editor support: PyCharm offers excellent autocompletion and type checking for FastAPI (just like VS Code). As you type a path operation function, it will suggest parameter names from your Pydantic models, etc. This is part of what makes FastAPI intuitive to work with (80% of Python devs use editors that provide such support).

6. Operating system specific notes:

  • Windows: Make sure to use the correct Python executable (sometimes python vs python3). On Windows, after installing, you might run the app with python -m uvicorn main:app --reload. If you hit a pip is not recognized error, ensure Python and Scripts directory are in your PATH, or use the Python Launcher (py -3). Also, consider using PowerShell or the new Windows Terminal for a better experience.

  • macOS: If using the system Python, consider installing a newer Python via Homebrew (brew install python) to get 3.10+. Use python3 command explicitly. macOS might require granting permissions if you use port 80 (instead use 8000 or run as sudo).

  • Linux: Ensure you have Python 3 and pip. On Debian/Ubuntu, you might install with sudo apt update && sudo apt install python3-pip. Then use pip3 if pip is mapped to Python 2. For running the app, opening firewall ports (if testing from another machine) might be needed.

7. Using Docker for FastAPI:

Docker is a great way to containerize FastAPI applications for deployment or development. Here’s a basic setup:

  • Write your FastAPI app (e.g., main.py with app = FastAPI()).

  • Create a Dockerfile in your project:

    FROM python:3.10-slim
    WORKDIR /app
    COPY requirements.txt ./
    RUN pip install -r requirements.txt # which contains fastapi and uvicorn
    COPY . .
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

    This Dockerfile uses a slim Python base image, installs dependencies, copies your code, and sets Uvicorn to run the app on port 80 (inside container).

  • Build the image:

    docker build -t myfastapi-app .

  • Run the container:

    docker run -d -p 8000:80 myfastapi-app

    This maps container’s port 80 to your host’s 8000. Now your API is accessible at http://localhost:8000 on your machine. Docker ensures a consistent environment (useful if your local OS setup is troublesome or for deployment to servers).

  • Tips:

    • Include --reload only for development (it’s not usually needed in Docker if you rebuild often; also, --reload won’t work well if using multiple workers).

    • For even slimmer images, consider using tiangolo/uvicorn-gunicorn-fastapi Docker image (an official image with FastAPI optimizations). But starting with a Python base as shown is simpler.

    • If your app needs environment variables (e.g., secrets), you can pass them with -e flags to docker run or use a .env file.

8. Generic Cloud setup: Deploying FastAPI on a cloud VM or service involves similar steps with a few considerations:

  • Choose a host: You can use a VPS or VM (AWS EC2, DigitalOcean, Azure, etc.) or container platforms (AWS ECS/Fargate, Google Cloud Run, etc.). Ensure the host has Python 3.8+ or use Docker.

  • Install on server: SSH into your Linux VM, install Python (and pip), then install FastAPI and Uvicorn in a virtualenv as described. You might also install a process manager (like Gunicorn with Uvicorn workers, or even use PM2 for Node – but Gunicorn is common for Python) to run multiple workers.

  • Running for production: For example, on an Ubuntu server:

    pip install fastapi uvicorn[standard]
    pip install "uvicorn[standard]" gunicorn
    gunicorn -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:80 main:app

    This command uses Gunicorn to start 4 Uvicorn worker processes listening on port 80 (HTTP). Replace main:app with your module and app instance. This setup can handle higher load by utilizing multiple CPU cores.

  • Firewalls and ports: Be sure to open the appropriate port (80 or 8000) in your cloud provider’s firewall settings. Using port 80/443 (with TLS) is ideal for production. You might put a reverse proxy like Nginx or Traefik in front, but for a beginner cloud setup you can directly expose Uvicorn/Gunicorn on port 80.

  • Environment configuration: Cloud environments often use environment variables for configs. FastAPI can easily read env vars (you can use pydantic.BaseSettings or the built-in Settings in FastAPI to manage config). Ensure to set --host 0.0.0.0 when running on a cloud VM so that the server listens on the public network interface, not just localhost.

  • Scaling: For scaling beyond one VM, you’d use load balancers, container orchestration (Kubernetes), or serverless platforms. FastAPI works in those contexts too (e.g., you can deploy FastAPI on AWS Lambda using an ASGI adapter like Mangum, turning your app into a serverless function). For generic setups, start simple and then explore these advanced deployments as needed.

9. Anaconda Navigator UI: If you prefer not to use the command line, you can install FastAPI via Anaconda’s GUI:

  • Open Anaconda Navigator, go to Environments, create a new environment (or select one).

  • Search for “fastapi” in the packages (make sure to select Channels: conda-forge in the dropdown). Tick FastAPI (and possibly Uvicorn if it appears separately) and apply. This will handle installation.

  • Then you can launch VS Code or PyCharm from within Navigator with that environment to start coding.

After following the above steps suited to your environment, you should have FastAPI installed and ready to use. Next, we will run a simple example to ensure everything works correctly.

Example: building a simple FastAPI application

Let’s walk through a complete, runnable FastAPI example that you can copy and run on your machine. This example will demonstrate core concepts: defining a FastAPI app, creating path operations (endpoints), using a Pydantic model for data, and handling errors.

Example Scenario: We’ll create a basic inventory API for managing items in a store. It will have two endpoints – one to get an item by ID and one to create a new item. We’ll include some simple validation and error handling (like returning 404 if an item is not found, and ensuring item prices are non-negative).

from typing import Optional, Dict from fastapi import FastAPI, HTTPException  # Import FastAPI class and HTTPException for error handling from pydantic import BaseModel

app = FastAPI(title="Inventory API", version="1.0")  # Create a FastAPI app with metadata # Pydantic model representing an Item in our inventory class Item(BaseModel):
name: str
price: float
quantity: int = 0
description: Optional[str] = None # In-memory "database" (dictionary) to store items
items_db: Dict[int, Item] = {}

@app.get("/items/{item_id}")
def read_item(item_id: int):
"""
Retrieve an item by its ID.
"""if item_id not in items_db:
 # If item not found, raise a 404 error with a custom message raise HTTPException(status_code=404, detail=f"Item {item_id} not found")
 return {"item_id": item_id, "item": items_db[item_id]}

@app.post("/items/", status_code=201)
def create_item(item: Item):
"""
Create a new item with auto-generated ID.
"""# Simple business rule: price must be non-negative if item.price < 0:
 raise HTTPException(status_code=400, detail="Price must be >= 0")
 # Generate a new ID (just using length of dict + 1 for simplicity)
new_id = len(items_db) + 1
items_db[new_id] = item
 return {"message": "Item created", "item_id": new_id, "item": item}

Let’s break down what’s happening in this code:

  • We import FastAPI and create an app instance. This app is what Uvicorn will run. We also set a title and version for documentation purposes (these appear in the Swagger UI).

  • We define an Item model using BaseModel from Pydantic. This model has name (string), price (float), quantity (int, default 0), and an optional description. The model enforces types and can provide default values. FastAPI will use this model to automatically validate input for the create endpoint.

  • We use a simple dictionary items_db to store items in memory. In a real app, this would be a database or persistent storage, but a dict is fine for demonstration.

  • @app.get("/items/{item_id}") defines a GET endpoint to read an item. The {item_id} in the path means FastAPI will parse it as an integer (because our function parameter item_id is annotated as int). In the function:

    • We check if the item exists in items_db. If not, we raise an HTTPException(status_code=404). This exception will cause FastAPI to return an HTTP 404 response with the given detail message in JSON. This is how you handle errors gracefully – you don’t have to return error responses manually, raising an HTTPException is enough.

    • If found, we return a dictionary with the item data. Notice we can return Pydantic models or Python dicts; FastAPI will automatically convert the Item model to a JSON (by converting to dict) in the response. The response will look like {"item_id": 2, "item": {... item fields ...} }.

  • @app.post("/items/") defines a POST endpoint to create a new item. We specify status_code=201 to indicate that on success it returns HTTP 201 Created. The function takes an item: Item parameter – FastAPI will automatically:

    • Read the request JSON body,

    • Validate it against the Item model (if a required field is missing or type is wrong, FastAPI will send a 422 Unprocessable Entity error detailing the validation issues without even entering our function),

    • Convert it into an Item object (item will be an instance of our Pydantic model inside the function, with all attributes accessible).

      Inside the function, we then:

    • Validate a business rule: if item.price < 0, we raise an HTTP 400 (Bad Request) with a message. FastAPI will return a JSON error like:

      {"detail": "Price must be >= 0"} 

      to the client. (If we didn’t do this, a negative price would technically still be accepted by Pydantic because it’s a float – Pydantic can enforce bounds too, but showing manual validation here for illustration).

    • If the item is valid, we generate a new ID (trivial logic here) and save the item in our items_db. Note: the item is a Pydantic model, so you could call item.dict() to get a dictionary, but you can also store the model itself as we did.

    • We return a success message containing the new item’s ID and the item data. FastAPI will serialize the Pydantic model in the response automatically.

Running this example: Save the code to main.py. Make sure you installed FastAPI and Uvicorn as described. Then run:

uvicorn main:app --reload

The --reload flag is useful in development – it auto-restarts the server if you edit the code. You should see console output indicating Uvicorn running, e.g.:

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [...] using statreload
INFO: Started server process [...]
INFO: Waiting for application startup.
INFO: Application startup complete.

Now, open a browser and go to http://127.0.0.1:8000/docs. You will see the interactive Swagger UI documentationfastapi.tiangolo.com (see image below).

Try the API out:

  • In Swagger UI, you’ll see the two endpoints (GET /items/{item_id} and POST /items/). Click on POST /items/, then “Try it out”. You’ll see a JSON schema for Item. Enter some sample data, e.g.:

    { "name": "Apple", "price": 1.99, "quantity": 10, "description": "Fresh Red Apple" } 

    and execute. You should get a 201 response with a JSON result like:

    { "message": "Item created", "item_id": 1, "item": { "name": "Apple", "price": 1.99, "quantity": 10, "description": "Fresh Red Apple" } } 

    The “Try it out” actually performs a live request to your running API.

  • Now try GET /items/{item_id}. Put 1 as the item_id (the one we just created) and execute. It should return the item. Try with an ID that doesn’t exist, e.g. GET /items/42 – you will get a 404 response:

    {"detail": "Item 42 not found"} 

    FastAPI returns errors in a JSON with a detail field by default (you can customize this format if needed).

  • You can also view the alternative documentation at /redoc (ReDoc UI), which is a different style of docs. Both UIs are powered by the same OpenAPI schema.

This example demonstrates:

  • Defining data models for requests (the Item model) and how FastAPI automatically validates input.

  • Creating path and body parameters (path: item_id, body: item model) and how they are provided to your functions.

  • Basic error handling using exceptions (404 and 400 errors).

  • The automatic documentation that FastAPI provides (listing endpoints and schemas).

You can expand this further by adding more endpoints – for instance, an @app.put("/items/{item_id}") to update an item, or @app.delete("/items/{item_id}") to remove one. FastAPI supports all HTTP methods and features like query parameters, headers, cookies, etc., in a similar declarative way.

Now that we have a basic app running, let’s explore FastAPI’s core features in more depth and how to use them effectively.

Core features of FastAPI (beginner-friendly)

FastAPI has many features, but here we will focus on five core features that are most useful for beginners: (1) Path operation decorators (routing), (2) Data models and validation with Pydantic, (3) Dependency Injection system, (4) Asynchronous capabilities (async/await), and (5) Automatic documentation (OpenAPI). For each feature, we’ll discuss its syntax and parameters, provide examples, highlight common errors (and how to fix them), give performance tips, and suggest integration ideas.

1. Path operations and routing (declarative endpoints)

What it is: Path operations are how you define API routes (endpoints) in FastAPI. They are declared with decorators on normal Python functions. FastAPI provides a decorator for each HTTP method: @app.get, @app.post, @app.put, @app.delete, etc. The decorator’s argument is the URL path. By attaching these to functions, you create route handlers.

Syntax and parameters:

@app.get("/users/{user_id}")
def get_user(user_id: int, verbose: bool = False):
...

In this example:

  • @app.get("/users/{user_id}") defines a GET endpoint at path /users/123 for example. Path parameters are indicated by {...} in the path. Here {user_id} means the function must accept a parameter named user_id. FastAPI will convert the path segment to the type of that parameter (int in this case). If conversion fails (e.g. /users/abc when user_id is int), FastAPI will automatically return a 422 error.

  • The function get_user has a path parameter user_id: int and also a verbose: bool = False. Because verbose has a default, FastAPI treats it as an optional query parameter (i.e., /users/123?verbose=true). By default, function params not found in the path are assumed to be query parameters (unless explicitly marked otherwise). So verbose can be provided as ?verbose=true. FastAPI will convert true/false strings to boolean automatically.

  • The return type isn’t declared here, but you could add -> User (Pydantic model) or -> dict. FastAPI uses annotations for documentation but doesn’t require an explicit return type.

Examples: Let’s illustrate a few variations:

@app.get("/items/{id}")
def read_item(id: str, q: Optional[str] = None):
 # "id" is a path param (string), "q" is an optional query param. if q:
 return {"id": id, "query": q}
 return {"id": id}

  • If you call /items/XYZ?q=test, you get {"id": "XYZ", "query": "test"}.

  • If you call /items/XYZ (no query), you get {"id": "XYZ"}.

@app.get("/users")
def list_users(limit: int = 10, active: bool = True):
 # Query params example: /users?limit=10&active=false
...

Here, limit and active are query parameters with defaults (10 and True). If not provided, they default; if provided, FastAPI parses limit from string to int and active “false” to bool etc.

@app.get("/files/{file_path:path}")
def read_file(file_path: str):
 # Use :path to capture path segments return {"file_path": file_path}

This uses a special converter :path in the decorator. Normally, {param} won’t capture “/” in the value, but :path tells FastAPI to accept a path-like segment. For example, requesting /files/etc/hosts will yield file_path = "etc/hosts".

Common errors & fixes:

  • Path/function parameter mismatch: If you define a path {item_id} but your function signature doesn’t have item_id (or has it named differently), FastAPI will error on app startup. The names must match exactly. Fix: ensure your function parameters align with the path placeholders.

  • Type conversion errors: If a path param can’t convert to the specified type (e.g., /items/abc for item_id: int), FastAPI returns a 422 Unprocessable Entity with details. The fix is usually on the client side (provide correct type) or change the param type if letters should be allowed. You can also add regex constraints in the path if needed.

  • Duplicate routes or methods: If you accidentally declare two functions with the same HTTP method and path, FastAPI will raise an error at startup (duplicate routes). Fix is to ensure unique path+method or use API versioning/prefixes to differentiate.

  • Trailing slashes: FastAPI by default treats /items and /items/ as two different routes. If you only declare one, accessing the other may result in a 404. The solution is to be consistent in your routes or use the redirect_slashes option in Starlette’s routing to auto-redirect. As a beginner, just match the client exactly to how you declared.

  • Missing required query params: If a query parameter is declared without default (e.g., def func(q: str)), it becomes required. If the client doesn’t provide it, FastAPI returns a 422 error saying the query param is missing. The fix is to add = None or a default to make it optional, or ensure the client always sends it.

Performance tips:

  • Path operations themselves are very lightweight. The main overhead is parsing path and query parameters. To optimize, use proper types (int parsing is faster than str if you need numbers anyway). Avoid overly complex regex in paths as it might slow routing (though for most apps this is negligible).

  • If an endpoint is CPU-bound (e.g., heavy computation), using async def won’t make it faster (it may even be slightly slower for CPU tasks). Consider offloading CPU-bound work to a background thread or process. But for I/O-bound routes, making them async def enables concurrency (see Async feature below).

  • Use APIRouter for large apps: As your app grows, you can separate routes using fastapi.APIRouter. This has no runtime overhead but helps organize code (and you can include routers with prefixes for performance grouping, like all /admin routes can be mounted together, saving some routing lookup time). For a beginner project, performance difference is tiny, but structurally it’s beneficial.

Integration ideas:

  • FastAPI path operations integrate easily with front-end frameworks. For example, you can have a React app make AJAX calls to these endpoints. Thanks to automatic docs, you can even use tools like Swagger Codegen to generate API client libraries for your endpoints in various languages.

  • You can secure path operations with dependencies (e.g., requiring an auth token) – more on that in the Dependency Injection section. Essentially, you can attach a security dependency to any route to integrate with authentication systems.

  • If you’re migrating from Flask, you’ll find FastAPI’s routing familiar (decorators). The integration difference is that FastAPI also parses types for you. You no longer need to use request.args or manual parsing; just define parameters in the function signature. This tight integration with Python’s type system is a big win for maintainability.

2. Data models and validation (Pydantic models)

What it is: FastAPI uses Pydantic models (classes that inherit from BaseModel) to define the schema of request bodies and responses. This feature brings automatic data validation and parsing. By declaring a model, you describe the shape of the data (fields, types, optionality, constraints) and FastAPI/Pydantic handle the rest: converting incoming JSON into your model instance, validating types/constraints, and generating docs (using JSON Schema) for these models.

Syntax and parameters:

from pydantic import BaseModel, Field

class User(BaseModel):
 id: int
name: str
signup_date: Optional[datetime] = None
email: str = Field(..., regex=".+@.+\..+")
is_active: bool = True 

In this example:

  • We define a class User inheriting BaseModel. Each attribute is a field in the model with a type annotation.

  • id and name are required (no default, and not Optional). signup_date is optional (can be None) and of type datetime.

  • We use Field(..., regex=...) for email. The ... (Ellipsis) as default tells Pydantic this field is required (no default). We also add a regex constraint: the email must match a basic pattern. Field allows other validations like max_length, ge (>= constraint for numbers), lt (< constraint), etc. For example, we could do name: str = Field(..., max_length=50).

  • is_active has a default True, making it optional for input (defaults to True if not provided).

In FastAPI, you would use this model in an endpoint like:

@app.post("/users/")
def create_user(user: User):
 # user is automatically validated and is a Pydantic model instance
save_to_db(user)  # hypothetical function return user

When a request comes in, FastAPI will:

  • Read JSON body and map it into User (doing type conversions, e.g., if signup_date was a string in ISO format, it will convert to datetime).

  • Validate all fields. If any field is missing (without default) or violates a constraint (wrong type, regex fail, etc.), FastAPI sends a 422 response detailing each error.

  • Only if validation passes, create_user is called with a User object.

Examples and use cases:

  • Nested models: Pydantic models can be nested. For example:

    class Address(BaseModel):
    city: str zip: str class Customer(BaseModel):
    name: str
    address: Address

    If your POST body JSON has an "address": { "city": "...", "zip": "..." }, FastAPI will nest it into an Address instance inside Customer. This is great for representing complex JSON.

  • Response Models: You can also specify a model for the response using the response_model parameter in the decorator. E.g.:

    @app.get("/users/{id}", response_model=User)
    def get_user(id: int):
    user_obj = db.get_user(id)
     return user_obj

    If you return a dict or an ORM object, FastAPI will try to convert it to the User model for the response, ensuring the output schema matches (and filtering out any extraneous fields). This adds a slight overhead but guarantees your API responses follow the schema you declare in documentation. It’s particularly useful if you want to hide certain fields (like passwords) – they won’t appear in the response if not in the model.

  • Field aliases & serialization: Pydantic allows aliases for JSON fields. If your JSON uses camelCase but you want Python snake_case, you can declare Field(..., alias="camelCaseName"). FastAPI can read incoming alias fields and also output using alias if configured. By default, it uses the field names as is.

Common errors & fixes:

  • Missing fields: If a required field is missing in the JSON, you get a 422 error with "msg": "field required" for that field. The fix is to provide the field or give it a default/Optional in the model if it’s actually optional.

  • Type mismatch: If a field is wrong type (e.g., string for an int), 422 error "msg": "value is not a valid integer" (for example). Fix is to send correct type; FastAPI/Pydantic are quite strict. They will do some coercion (e.g. "123" string could be accepted as int 123), but for complex types like datetime, you need ISO strings or timestamps, etc.

  • Value constraints: If you set max_length=50 and send a longer string, you’ll get a validation error listing the limit. Similarly for regex. Fix is obvious: meet the constraints.

  • Pydantic validation exceptions: If something in your model’s custom validation (if you add @validator functions in Pydantic) throws, it becomes a 422 error. You should catch issues in those validators and raise ValueError with a message, which Pydantic will include.

  • Mutable default (Gotcha): If you have a field that is a mutable type (like a list) with a default [], it can behave unexpectedly (as it’s shared across instances). Pydantic handles this by making a copy, but best practice: use Field(default_factory=list) for lists/dicts default. This ensures a new empty list per instance.

Performance tips:

  • Pydantic V2 improvements: If using Pydantic v2 (FastAPI 0.100+ uses Pydantic 2 by default), enjoy significant speed boosts – model parsing is much faster (even uses Rust under the hood). Ensure you use latest FastAPI to leverage this.

  • For extremely high throughput scenarios, the JSON -> Pydantic conversion can be a hotspot. Some tips:

    • Use response_model_exclude_unset or similar if returning models with large optional fields to avoid serialization overhead of defaults.

    • Use orjson or ujson for response serialization if needed (FastAPI can integrate those via ORJSONResponse) which can be faster than default JSON. You’d install orjson and return ORJSONResponse(content=dict_or_model)

    • If input validation overhead is an issue and you trust the data (e.g., internal microservice call), you can skip validation by typing the parameter as dict instead of a model and manually constructing model if needed. But generally, the overhead is small relative to I/O.

  • Pydantic models create Python objects; if your payloads are huge (thousands of items), consider streaming responses or chunking to avoid large memory use. For example, for large query results you might want to use streaming responses rather than loading everything into Pydantic (which would allocate objects for each item).

  • Reuse models: If the same model is used in many endpoints, define it once and reuse. This not only avoids repetition but also is more efficient at runtime (Pydantic caches model schema).

Integration ideas:

  • Databases/ORMs: You can integrate Pydantic models with ORMs. For instance, you might have a SQLAlchemy model and you want to convert it to a Pydantic model for output. Pydantic provides from_orm=True capability on model config to accept ORM objects directly. You can also use libraries like SQLModel (created by FastAPI’s author) which unify Pydantic and SQLAlchemy models.

  • Form data / frontend: FastAPI can automatically handle form submissions or file uploads into Pydantic models (using Form(...) or UploadFile types), which is useful for integrating with HTML forms (though FastAPI is mostly used for JSON APIs).

  • Data science: You can integrate with pandas or numpy by converting data to standard types that Pydantic can handle (e.g., lists). Pydantic models can also have custom validators to, say, check that an image file is in a certain format, enabling integration with PIL or OpenCV for image processing inputs.

  • OpenAPI clients: Because Pydantic models define your schema, the OpenAPI docs generated allow other tools to generate clients. You could use openapi-generator to create, for example, a TypeScript client that has corresponding types for your models, making front-end integration type-safe.

3. Dependency injection with Depends

What it is: FastAPI’s Dependency Injection system allows you to declare “dependencies” for your path operation functions. A dependency is just a callable (function or class) that FastAPI will execute before your endpoint function, and inject the result into your function. This is done using the Depends() marker in function parameters. Dependencies are a powerful way to reuse logic (like auth, DB connection) across endpoints and to handle cross-cutting concerns (logging, rate limiting, etc.) in a clean manner.

Syntax and parameters:

from fastapi import Depends

def get_db():
db = DatabaseConnection(...)
 try:
 yield db
 finally:
db.close()

@app.get("/items/")
def read_items(limit: int = 100, db = Depends(get_db)):
 # FastAPI will call get_db() and inject the returned db
items = db.fetch_items(limit=limit)
 return items

In this snippet:

  • We define a function get_db that perhaps creates a database connection/session. It uses a Python generator with yield – this is a special way to define a "context-dependent" dependency (the code after yield runs after the request, for teardown). FastAPI recognizes that pattern to do setup/teardown (much like with context managers). Here, it yields a db object and ensures db.close() is called after the endpoint is done.

  • In the endpoint read_items, we have a parameter db = Depends(get_db). This tells FastAPI: “for this endpoint, before calling read_items, call get_db() and use its result as the argument db.” So db becomes an instance of our DatabaseConnection. We didn’t specify a type for db (you can, but it’s not required).

  • You can also declare dependencies that themselves have dependencies (sub-dependencies). FastAPI will resolve all in proper order. For example, get_current_user might depend on verify_token which depends on get_db, etc., and FastAPI handles the graph for you.

Examples:

  • Authentication dependency: A common example is checking a JWT token:

    def get_current_user(token: str = Depends(oauth2_scheme)):
     # oauth2_scheme is a dependency that reads an Authorization header
    user = verify_jwt_token(token)
     if not user:
     raise HTTPException(401, "Invalid auth")
     return user

    @app.get("/profile")
    def read_profile(current_user: User = Depends(get_current_user)):
     return {"username": current_user.username}

    Here, oauth2_scheme could be an instance of OAuth2PasswordBearer (provided by FastAPI) which itself is a dependency that extracts token from header. Then get_current_user depends on that to get the token, verifies it, and returns a user object or raises if invalid. The endpoint then depends on get_current_user. This means any request to /profile will automatically enforce authentication and inject the user if valid, simplifying the endpoint logic.

  • Reusable logic: Suppose multiple endpoints need to parse a common query parameter or check a condition:

    def validate_pagination(skip: int = 0, limit: int = 100):
     if limit > 1000:
     raise HTTPException(400, "Limit too large")
     return {"skip": skip, "limit": limit}

    @app.get("/users/")
    def list_users(p: dict = Depends(validate_pagination)):
     # p contains skip and limit return query_users(skip=p["skip"], limit=p["limit"])
    @app.get("/orders/")
    def list_orders(p: dict = Depends(validate_pagination)):
     return query_orders(offset=p["skip"], limit=p["limit"])

    Here, validate_pagination ensures limit is not crazy large for any endpoint that uses it. We don’t repeat that logic in each endpoint.

  • Classes as dependencies: You can use classes with __call__ or even Pydantic settings classes as dependencies, which is an advanced usage. For instance, a class that holds config values can be injected so that each endpoint has access to config without importing global variables.

Common errors & fixes:

  • Missing dependency: If FastAPI can’t resolve a dependency (maybe you forgot to include a default value or use Depends), you’ll see an error at startup or a 500 at runtime. Usually, FastAPI will catch issues at startup, e.g., if a dependency function has parameters that are not provided. Fix by ensuring each dependency’s own parameters have defaults or further Depends as needed.

  • Order of execution and context: If you accidentally create a dependency that depends on something not available, you get errors. For example, you cannot depend on request body directly in a dependency (because body is read after dependencies by default). There are ways around this (dependencies with dependant=... settings) but for beginners: dependencies are meant for things like auth, DB, etc., not to get the request body early.

  • Async vs sync dependencies: If a dependency is an async function (async def), FastAPI will run it in the event loop. If it’s regular def, FastAPI might run it in threadpool (for yield ones, it uses special handling). Usually you don’t need to worry, but if you see warnings about dependencies taking too long, ensure long IO in dependencies uses async.

  • Yield in dependencies: If you use yield in a dependency (context manager style) without the proper structure, you might get odd behavior. Always yield once. If you need to return a value and still have teardown, use yield (like shown in get_db above). Behind the scenes, FastAPI will treat the function as contextmanager. If you mistakenly put multiple yields or none at all, it won’t work as intended.

  • Dependency that returns None unexpectedly: If a dependency is supposed to return something but doesn’t (returns None), and your endpoint expects something, you might hit errors. Example: get_current_user returns None (didn’t raise) and then endpoint tries to use it assuming a user. Best practice is to always either return a valid object or raise HTTPException in a dependency if it fails; don’t return None unless None is an acceptable value in your endpoint logic.

Performance tips:

  • Dependencies add a slight overhead because FastAPI has to call these extra functions. However, they are highly optimized (using caching of dependency injection solving where possible). The overhead per request is usually microseconds to maybe a millisecond, which is trivial compared to I/O.

  • If a dependency is expensive (e.g., hitting a database every time to get settings), you can use caching. FastAPI’s Depends has dependency_cache internally for repeated sub-dependencies on the same request. Also, you can mark dependencies with @lru_cache if they should only run once per startup (like reading a file of config) – or better, do that outside of request flow.

  • Use global resources carefully: If using a global DB connection pool or something, you might not need a dependency to create it each time – instead, have a dependency that yields a session from a pool. This way you reuse connections. Dependencies should ideally manage scope – e.g., open on request, close after – rather than do heavy creation each time unless needed.

  • If you have nested dependencies, FastAPI tries to resolve them efficiently. But deep dependency trees could add overhead. Keep dependency layers as few as make sense (usually 1-3 levels at most). This is rarely an issue except in extremely performance-sensitive micro-optimizations.

Integration ideas:

  • Database sessions: We already saw an example. In larger apps, you often integrate an ORM (like SQLAlchemy or Tortoise) and use dependencies to get a session per request. Many FastAPI tutorials use Depends(get_db) to inject a session and commit/close it automatically.

  • Security schemes: FastAPI provides dependency-based tools for security (OAuth2, API keys, etc.). For instance, OAuth2PasswordBearer is used as Depends(oauth2_scheme) to get a token, and then a custom dependency to retrieve a user. This cleanly separates security logic from business logic.

  • Background tasks & external services: You can use dependencies to integrate services like caching or queues. For example, a dependency might push a message to a queue after the response is sent (though for sending after response, FastAPI’s BackgroundTasks might be more appropriate). But you could, say, have a dependency that checks a Redis cache for a value and either returns it (and your endpoint could short-circuit) or yields control to proceed to main logic. This can act like a caching layer.

  • Testing: Dependencies can be overridden during testing. FastAPI allows you to use app.dependency_overrides to substitute a different function for a dependency – very useful to inject fake data or mock services in tests. For instance, in tests you might override get_db to use a test database or override get_current_user to return a known test user without needing real auth. This makes integration testing much easier (no need to actually provide tokens, etc., if you override those dependencies).

  • Modular design: You can attach dependencies at a router level or app level as well (global dependencies) to apply to many routes. E.g., include a router with dependencies=[Depends(get_current_user)] to protect all its endpoints, achieving something akin to route grouping with shared requirements.

In summary, dependency injection in FastAPI is a declarative way to manage concerns and share functionality. It keeps endpoint functions focused on the core logic by offloading tasks like auth, connecting to resources, etc., to separate functions that can be reused and tested in isolation.

4. Asynchronous endpoints (async/await for concurrency)

What it is: FastAPI is built to fully support asynchronous Python (the asyncio library). You can declare your path operation functions with async def, allowing them to perform non-blocking I/O operations concurrently. This means FastAPI can handle multiple requests on a single thread by switching contexts during await points (when I/O happens). For I/O-heavy applications, this dramatically improves throughput and latency compared to synchronous code. FastAPI’s use of the ASGI standard and Uvicorn’s event loop is what enables this.

Syntax and usage:

To define an async endpoint, simply use async def:

import httpx

@app.get("/external-data")
async def get_external_data():
 async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com/data")  # await external call
data = resp.json()
 # We could do more awaits here (e.g., database calls) concurrently return {"external_data": data}

Important points:

  • Use await for I/O: Inside an async def function, any time you call an async operation (like an HTTP request using httpx.AsyncClient or a database query using an async driver or even asyncio.sleep), you must prefix it with await. This yields control back to the event loop so other tasks (requests) can run while waiting for I/O.

  • If you forget to await an async call, it won’t actually execute (you’ll get a warning or a runtime error).

  • Mixing async and sync: FastAPI allows both sync (def) and async endpoints. If you call blocking code (like a normal HTTP library or heavy computation) inside an async function without offloading it, you will block the event loop, harming concurrency. For blocking external calls, either use their async versions or run them in a threadpool (FastAPI provides run_in_threadpool utility behind the scenes for some standard library calls or you can use loop.run_in_executor).

  • Concurrent tasks: Within an async function, you can use asyncio features to perform tasks concurrently. For example, to fetch multiple URLs concurrently:

    @app.get("/multi-data")
    async def get_multi_data():
    urls = ["http://example.com/a", "http://example.com/b"]
     async with httpx.AsyncClient() as client:
    tasks = [client.get(url) for url in urls]
    responses = await asyncio.gather(*tasks)
     return [r.status_code for r in responses]

    Here both requests are made concurrently, and asyncio.gather awaited to get results. This can yield significant performance improvements when you have independent I/O tasks.

Common pitfalls & errors:

  • Using async where not needed: If your endpoint doesn’t do any awaitable I/O (for example, just CPU-bound work), making it async won’t improve throughput; in fact, running CPU code in the event loop can block other tasks. For CPU-bound tasks, consider running them in a separate thread or process (or use a background tasks feature).

  • Blocking inside async: Calling a regular blocking function (e.g., requests.get or heavy pandas operation) inside an async def will block the entire event loop. One common mistake is using an ORM like SQLAlchemy (synchronous) in an async endpoint. This will bottleneck. The fix is to use an async database library (like encode/databases or SQLAlchemy’s async engine in newer versions, or run the sync DB calls in threadpool via await sync_call() using starlette.concurrency.run_in_threadpool utility).

  • Awaiting non-async: If you try to await a normal function, Python will error. Conversely, forgetting to await an async function call leads to a coroutine that’s never executed (and likely a warning “coroutine was never awaited”). Always ensure every async def you call inside is awaited (or returned to be awaited by FastAPI if it’s a dependency or something – but in endpoints, you should await).

  • Thread-unsafe code: If you do mix sync code in threads, be careful with global state. The fact that FastAPI’s main loop is single-threaded (for async) is nice, but if you use threadpool for a blocking call, that code will run in a separate thread. Ensure any shared data is protected or use only thread-safe libraries. Usually not a big concern for typical DB or HTTP calls.

Performance and concurrency considerations:

  • Single vs multiple workers: By default, Uvicorn runs a single process. Async code lets that process handle many concurrent requests. However, it’s still bound by GIL for CPU tasks. For high load, you might still run multiple workers (processes) – e.g., using uvicorn --workers 4 or Gunicorn – to utilize multiple CPU cores. Each process will have its own event loop handling async tasks.

  • High I/O throughput: Async shines when you have lots of waiting (e.g., many slow external calls). For example, one FastAPI strength is calling other microservices or doing network calls concurrently. It can serve thousands of connections idling (like websockets or long polling) with minimal threads. TechEmpower benchmarks often show FastAPI (async) handling ~20k rps in simple scenarios, whereas Flask (sync) would need many threads or processes to approach that.

  • Latency: Async can also reduce latency under load. Because one request isn’t blocking the server, others don’t have to wait for it to finish if it’s waiting on I/O. This leads to more consistent response times under concurrent load.

  • Profiling async: Standard profilers might not fully capture async context. Tools like asyncio.run or using Py-Spy in async mode are helpful. Yappi is another profiler that works with multi-threaded and async code.

  • Don’t over-async: Not everything benefits from being async. For example, reading a small config file from disk at startup is fine to do sync. Database ORMs can be trickier – if an async ORM isn’t available, sometimes using sync in threadpool may be simpler. Use async primarily for networking/IO-bound tasks where library support exists.

Integration ideas:

  • Databases (async): FastAPI works great with async database libraries. E.g., databases library or Tortoise ORM allows you to await db.fetch_all() etc. If using an async DB, you can integrate it easily in async endpoints or in dependencies. The benefit is you don’t need extra threads for DB calls (unlike a sync ORM which would block).

  • External APIs: Many projects use FastAPI to create backend that gathers data from multiple external APIs (e.g., microservice aggregator or mash-up). With async, you can call all external APIs in parallel instead of sequentially, dramatically cutting down total response time.

  • WebSockets: FastAPI supports WebSocket endpoints via @app.websocket. Those must be async functions. You can await websocket.receive_text() and await websocket.send_text(). Async is essential for handling many websockets concurrently. This makes FastAPI suitable for real-time applications like chat servers, live dashboards, etc., often replacing older frameworks like Tornado in that area.

  • Background tasks: FastAPI has a BackgroundTasks feature to schedule tasks to run after sending a response. This is integrated with the async loop (tasks run in the same event loop after response). Use it to send emails or other follow-ups without delaying the response to the client. It’s not exactly multi-threading – background tasks run sequentially after response, but they can be async def as well. For heavier tasks, consider external task queues (Celery or RQ); FastAPI endpoints (async or not) can enqueue jobs to those.

  • Long-Lived connections & streaming: Async allows efficient streaming of responses or request data. For example, streaming large file responses with StreamingResponse (which can be async generator) ensures you’re not tying up a thread for the whole duration. Similarly, reading a large request body in chunks (for example, an upload) can be done asynchronously. This is useful for integrating FastAPI in scenarios like video streaming or big file uploads/downloads.

  • Third-party async libraries: Many modern Python libraries have async versions (e.g., aioredis for Redis, aiobotocore for AWS S3, etc.). Using these with FastAPI is straightforward: just await calls in your endpoints or dependencies. This way, your FastAPI app can integrate with various systems without blocking. For instance, an async Redis cache check can run concurrently with other processing.

In essence, use async def for endpoints that perform I/O-bound operations (database, HTTP calls, file I/O, sleeps) so that FastAPI can handle many such operations concurrently. For CPU-bound operations, consider alternative strategies (offload to thread/process or optimize the computation) rather than async. With proper use of async, FastAPI can scale to handle a large number of simultaneous requests efficiently on minimal threadsbetterstack.com, making it ideal for modern microservice workloads.

5. Automatic interactive documentation (OpenAPI/Swagger UI)

What it is: FastAPI automatically generates an OpenAPI specification (formerly known as Swagger) for your API, and provides interactive documentation UIs for it: Swagger UI and ReDoc. This is a core feature that greatly improves the developer experience. The docs are available by default at http://<your-host>/docs (Swagger UI) and /redoc (ReDoc). They allow you (and your API consumers) to visualize the API endpoints, models, and even execute calls from the browser.

How it works: FastAPI inspects all your path operations, their parameters, request bodies (Pydantic models), responses, and dependencies, and compiles an OpenAPI JSON schema. This schema is exposed at GET /openapi.json. The Swagger UI and ReDoc are static web apps that fetch this schema and render the documentation.

Key features of the docs UIs:

  • List of endpoints: All your routes are listed, grouped by tags if you use tags. Each endpoint shows the method (GET/POST etc.) and path, and an expandable section.

  • Parameters and schemas: When expanded, an endpoint shows its parameters (path, query, header, cookie) with descriptions, types, whether required, etc. If an endpoint expects a body (from a Pydantic model), the schema of that model is displayed (with field types, examples, any constraints like max length).

  • Try it out (Swagger UI): Swagger UI provides a “Try it out” feature where you can input parameter values and request body, then execute the request directly from the docs page. It will show you the response from the server (status code and JSON response or error). This is extremely useful for testing and exploration.

  • Example values and descriptions: You can document each field and endpoint. Pydantic models can include metadata like Field(..., description="..."), which FastAPI includes in the schema. You can also provide example payloads using example= in Field or a separate schema example. These appear in the docs to guide users.

  • Authentication in docs: If you secure some endpoints (e.g., OAuth2 or API key), Swagger UI will allow you to authenticate within the UI (for example, it shows an “Authorize” button if you use OAuth2 security scheme).

  • ReDoc: The ReDoc interface is more static (no “try it out”), but it provides a nicely formatted documentation site. It’s useful for publishing read-only docs for third-party developers.

Customization: FastAPI allows customizing the documentation in various ways:

  • You can add a title, description, version for the API (FastAPI app has those parameters). These show up on the docs page.

  • You can include your own API description in Markdown to be displayed on the docs homepage.

  • You can customize the Swagger UI or ReDoc (for instance, FastAPI allows you to serve the docs under a different URL or add your own branding if needed).

  • If certain endpoints should be hidden from docs (e.g., internal ones), you can include include_in_schema=False in the decorator to exclude them from documentation.

Common questions or issues:

  • Docs not showing or 404: By default, docs are enabled. If you set docs_url=None when creating FastAPI app, docs won’t be served. Also, ensure you are accessing the correct path (exactly /docs and not /docs/ – though a redirect is usually in place). If you mounted the app under a prefix, the docs may be at /prefix/docs.

  • ReDoc or swagger not loading: This sometimes happens if you’re behind a proxy that blocks the CDN for the UI assets. By default, FastAPI uses CDN links for Swagger UI and ReDoc. If those are blocked, you can configure FastAPI to serve those assets locally (using app = FastAPI(swagger_ui_parameters={"useLocalAssets": True}) or similar config).

  • Large schemas: If you have very large schemas, the docs might become slower or cluttered. You can manage this with tags to group endpoints, or even disabling docs in extreme cases (and providing a separate static docs).

  • Security of docs: If your API requires auth, note that the docs UI itself doesn’t enforce auth to view the schema (it’s just a webpage). If your API is private, you might want to protect the docs endpoint (e.g., behind login or disable it in production). There was a Reddit thread about protecting Swagger UI from public access. You can do so by adding an @app.get("/docs") route that requires auth and serve docs only to logged-in users, or run API behind firewall. For many cases, having docs public is fine (it doesn’t expose anything not already in the API).

  • Updating docs: If you change something in code and you’re using --reload, the docs update automatically (because they’re generated each time). If not reloading, you’d need to restart to see changes in docs (like new models or descriptions).

Performance impact: The documentation is generated at startup and served statically (except the actual try-it-out requests, which call your API). The OpenAPI JSON is usually cached the first time. So the presence of docs doesn’t slow down your API except a negligible memory overhead for storing the schema. If your schema is huge (thousands of endpoints), generating it might add a bit to startup time.

Integration ideas:

  • Client code generation: Because FastAPI gives you the OpenAPI spec, you can use tools to generate API client libraries. For instance, you can feed the spec to OpenAPI generator or Swagger Codegen to generate a TypeScript, Java, or Python client that has methods for each API endpoint. This is great for front-end teams or external API consumers. FastAPI’s adherence to OpenAPI means you have a contract for your API for free.

  • API gateways and monitoring: Many API gateway services or monitoring tools can ingest OpenAPI specs to set up routes or checks. You can point them to the /openapi.json of your FastAPI service.

  • Interactive exploration: During development, the docs UI is a wonderful integration point – you can try new endpoints as you build them. Even non-developers (QA, product managers) can hit the docs URL and play with the API if needed to understand it.

  • Documentation sites: You can export the OpenAPI spec and use it to create static documentation. Tools like ReDoc can be used as part of your documentation site. For example, you might host a static HTML that embeds ReDoc with your openapi spec, giving a clean documentation page for external developers without needing a running FastAPI instance. FastAPI’s own documentation site (for the API itself) could be generated from the spec if it were public.

  • Versioning and docs: If you version your API (say v1, v2), FastAPI can generate one schema that includes versioned paths, or you could have separate apps. It’s sometimes useful to host multiple docs (like /docs/v1, /docs/v2), which you can do by mounting sub-apps or customizing docs_url per app.

In summary, the automatic docs are a huge convenience and professionalism boost. They ensure your API is self-documenting and interactive, reducing the guesswork for anyone using your API (including yourself!). This feature often wows developers new to FastAPI, as getting a full Swagger UI with zero effort is a big time saver.

Advanced usage and optimization tips

FastAPI is suitable not just for simple apps but also for production-grade, high-performance services. In this section, we explore advanced topics and best practices to optimize your FastAPI application. This includes memory management, speed optimization, caching, profiling, testing, deployment, and production best practices.

Memory management and efficient resource usage

FastAPI itself is lightweight, but your app’s memory profile depends on how you use it:

  • Scope of objects: Be mindful of objects that persist between requests vs. those created per request. For example, Pydantic models are created per request (which is fine), but large data structures loaded globally will reside in memory permanently. If you have large datasets, consider loading on demand or using streaming.

  • Streaming responses: If you need to send large responses (files, big JSON), consider using StreamingResponse or generator functions to stream data instead of loading everything into memory. For instance, reading a big file in chunks and yielding it will use constant memory instead of reading whole file into a bytes object.

  • Request body size: Similarly, if clients upload large files, use UploadFile which streams data to disk (temporary file) instead of reading entire file into memory as bytes. UploadFile gives you a file-like object to .read() in chunks.

  • Avoiding memory leaks: Use dependencies or context managers to ensure resources are freed. For example, if you open a database connection per request, make sure it’s closed (the yield-style dependency helps here). Python generally cleans up, but be careful with global lists or caches that grow without bounds. If you implement caching, use eviction policies (LRU cache or time-based).

  • Async and memory: Each request in FastAPI (async) shares the same process memory. Ensure your code is thread-safe if you use any global mutable state (because although async is single-threaded by default, if you run multiple workers or background threads, it matters). Using locks or avoiding global state altogether is best. Also, when doing async tasks, if you accumulate a lot of data concurrently, memory can spike. For example, gathering 100k database rows concurrently might hold many results in memory at once – consider processing streams incrementally.

  • Use of yield dependencies for cleanup: As mentioned, dependencies can use yield to guarantee cleanup code runs. Use this to free any large objects or close connections promptly after each request to avoid lingering memory usage.

  • Object reuse: If you have expensive objects (like a machine learning model), load them once at startup and reuse. For example, load a ML model globally and have dependencies that provide it (not reloading it each time). This saves memory compared to reloading per request, at the cost that the global object persists (which is usually fine if it’s needed). Just be conscious of the trade-off (you don’t want dozens of copies of the same model in memory).

Speed optimization techniques

FastAPI is fast by design, but here are ways to squeeze more performance:

  • Use async effectively: As discussed, use async endpoints for I/O-bound operations so you can serve many requests concurrently. Ensure external calls (HTTP, DB) use async libraries so they don’t block. This maximizes throughput for high-latency operations.

  • Use uvicorn[standard]: This installs uvloop (a faster event loop) and other optimizations. Uvicorn with uvloop can significantly improve performance under load. Check Uvicorn logs to ensure uvloop is being used (it should if installed on Unix systems).

  • Gunicorn workers: For CPU-bound tasks or generally utilizing multi-cores, run multiple worker processes. E.g., if you have 4 CPU cores, 4 Uvicorn workers can handle CPU tasks in parallel. Use a process manager like Gunicorn to manage them. However, don’t spawn more workers than you have cores (usually).

  • Caching strategies: Introduce caching to avoid redundant computations or external calls:

    • In-memory cache: For quick wins, you can use Python’s functools.lru_cache decorator on dependency functions or any function that given the same input returns the same output. For example, caching results of a static file read or a config fetch so subsequent calls are instant. Be cautious with memory use though (limit maxsize).

    • Distributed cache: For heavier caching needs (like caching API responses or database queries), consider an external cache like Redis. FastAPI doesn’t have built-in caching, but you can easily integrate by using aioredis or similar. For example, in a dependency, check Redis for a cached result before hitting a slow backend. If found, return it, otherwise fetch and store in cache with a TTL. This can drastically reduce response times for repeated requests (e.g., caching expensive computations or rarely changing data).

    • Cache invalidation: If caching, plan how to invalidate stale data. Use TTLs or explicit cache clears on data update endpoints (e.g., after posting a new item, clear relevant cache entries). FastAPI doesn’t automate this, so design it in your logic.

    • Client-side caching: Leverage HTTP caching headers if appropriate (Cache-Control, ETag), so that clients or intermediaries can cache responses. FastAPI allows adding custom headers easily (via Response object or middleware).

  • Optimize database access: If your app uses a database, typical performance tips apply:

    • Use indexing in the database, avoid N+1 query patterns (use ORM lazy loading carefully or fetch related data in one go).

    • For heavy read loads, consider adding a caching layer or read replicas.

    • Use connection pooling (most DB libs do this). If using sync DB with multiple threads/workers, ensure pool size is configured.

    • If using async DB, use drivers like asyncpg for Postgres which are faster.

    • Profile slow queries and optimize them (this goes beyond FastAPI but is critical for speed).

  • Profile and identify bottlenecks: Utilize profiling tools to find where time is spent:

    • Use cProfile in a test client call to see where CPU time is going.

    • For async, try Py-Spy or record a run and use tools to visualize event loop waits. Often, your bottleneck might not be FastAPI itself but your own code or external services.

    • Logging middleware can measure request processing times to identify slow endpoints.

  • Compile Pydantic models (if using v1): If using Pydantic v1 (older FastAPI), there was an option to use Cython for speed. Pydantic v2 however is already quite optimized (written in Rust)v, so just use the latest versions for best performance.

  • Orjson for JSON serialization: Pydantic v2 by default uses json or orjson if installed. orjson is significantly faster for large JSON dumps. You can switch response class to ORJSONResponse globally to use it. Make sure clients can handle slight differences (orjson returns bytes, but FastAPI’s ORJSONResponse handles it).

  • Batch requests: If appropriate, allow clients to batch operations. For example, an endpoint that fetches 10 users given 10 IDs in one request is more efficient than 10 separate requests. This moves the loop overhead from network to server which can often handle it better. Design your API to support fetching or updating in bulk when it makes sense (this is an app-level design optimization).

Profiling and monitoring

Building on profiling: continuous monitoring in production is crucial:

  • Use APM tools or logging: Integrate application performance monitoring (APM) tools like NewRelic, Datadog, Sentry Performance, etc., which can instrument FastAPI (often via Starlette middlewares or OpenTelemetry). They provide metrics on response times, throughput, error rates.

  • Logging: FastAPI/Uvicorn logs every request by default (in development). In production, consider adding structured logging (JSON logs with details) and include things like request ID, user ID (if available) for tracing issues. Logging can be done via standard logging module or using dependencies/middleware to log at start and end of requests.

  • Profiling in testing: For deeper analysis, you can simulate load with tools like Locust or JMeter and observe resource usage (CPU, memory) and performance. Adjust your strategies accordingly (increase workers, add caching, etc., based on results).

  • Yappi for async profiling: As noted, Yappi can profile multithreaded or async apps effectively. You can run Yappi in a running server to collect stats and then analyze which coroutines or functions are taking the most time.

  • Identify bottlenecks: Use the data gathered to pinpoint slow spots. Is it external calls, DB queries, serialization of huge responses, etc.? Each has a targeted solution (improving that external service, adding caching, optimizing query or adding indices, splitting huge responses into paginated smaller ones, etc.).

Testing FastAPI applications

Testing is essential and FastAPI makes it straightforward:

  • Use FastAPI’s TestClient: FastAPI provides a TestClient based on Starlette’s TestClient, which allows you to call your API endpoints in tests as if from a client. Example:

    from fastapi.testclient import TestClient
    from myapp import app

    client = TestClient(app)

    def test_create_item():
    response = client.post("/items/", json={"name": "Test", "price": 5.0})
     assert response.status_code == 201
    data = response.json()
     assert data["item"]["name"] == "Test" 

    This spins up the app in test mode and you can call endpoints without actually running a server.

  • Dependency overrides in tests: As mentioned, you can override dependencies in tests to isolate components. E.g., override get_db to use a test database or override get_current_user to avoid real auth. Do:

    app.dependency_overrides[get_db] = lambda: Session(test_engine)  # example 

    And then TestClient calls will use the overridden dependency.

  • Factories and fixtures: Use pytest fixtures to set up state (like creating tables in a test DB, populating initial data, etc.). You might spin up a Docker container for a test database or use an in-memory SQLite for simplicity (if your app logic allows).

  • Test performance (if needed): You can write tests to ensure certain endpoints respond within expected time (maybe not typical unit tests, but performance tests separate from CI). Or test that caching works by calling twice and measuring time difference.

  • Stress Testing Separately: Use tools for load testing outside of unit tests to ensure your service holds up under load and that optimizations are effective.

Deployment and production best practices

When deploying a FastAPI app, consider the following:

  • Use a production server: Uvicorn is production-ready, but usually you run it behind a process manager like Gunicorn (with Uvicorn workers) or something like Uvicorn’s built-in multiple workers mode. Example using Gunicorn (with 4 workers):

    gunicorn -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:8000 myapp:app

    This combination is common. It gives you multi-process concurrency and graceful restarts, etc.

  • Reverse proxy (Nginx/Traefik): It’s often advisable to put a reverse proxy in front of your Uvicorn app. Nginx or Traefik can handle TLS termination, gzip compression, buffering, and serving static files more efficiently. They can also manage multiple services on one host.

  • Environment variables for config: Use Pydantic’s BaseSettings to define app configuration that reads from env vars (like DB URL, secrets). FastAPI can integrate this easily (there’s a Settings example in docs). This aligns with Twelve-Factor App methodology and makes switching configs in different environments easier.

  • Security Best Practices: In production:

    • Enable CORS only as needed. Use CORSMiddleware to restrict origins if your API is not public or only certain domains should call it.

    • Use HTTPS (either via proxy or directly with an ASGI server that supports TLS).

    • If applicable, implement auth on endpoints (FastAPI provides OAuth2 flows, etc.) and protect sensitive data.

    • Keep dependencies up to date for security patches (e.g., if a vulnerability is found in Starlette or Pydantic, update FastAPI accordingly).

    • Consider rate limiting (can be done via dependency or externally via API gateway) to prevent abuse.

    • If you have an open docs endpoint in production, consider whether that’s okay or if you want it behind auth to avoid giving too much info to the public (depending on your use case).

  • Connection pooling: If using Gunicorn with multiple workers, remember each worker has its own connections. Ensure your database can handle that many (e.g., if each opens pool of 5, and 4 workers, that’s 20 connections).

  • Auto-scaling: If deploying on cloud (Kubernetes, AWS ECS, etc.), containerize the app (we covered Docker earlier). Monitor CPU/memory usage, and scale out by running more instances under a load balancer if needed. FastAPI apps, being stateless, work well behind load balancers.

  • Logging and monitoring: Use logging as discussed, and set up monitoring. Uvicorn logs can be piped to logging system. You might also instrument with Prometheus (there’s Starlette middleware for metrics) to gather metrics on requests, latency, etc., for alerts.

  • Startup and shutdown events: FastAPI lets you define event handlers for app startup and shutdown (via @app.on_event("startup")). Use these to initialize things (e.g., warm up a model, connect to DB to verify, schedule recurring tasks if any). Similarly, ensure graceful shutdown closes DB connections, etc., to avoid corruption.

  • Testing in Prod-like environment: Always test your container or deployment configuration in a staging environment. Sometimes things like number of workers or environment variable wiring can cause issues that won’t appear in dev.

By following these best practices and utilizing FastAPI’s features, you can build an application that is not only fast and efficient in development, but also robust, scalable, and maintainable in production. Many of these tips are illustrated by real-world FastAPI deployments, which we will look at next.

Real-world use cases and examples

FastAPI’s capabilities have been proven in many real-world projects, from startups to large enterprises. Let’s look at five real-world use cases or open-source applications that use FastAPI, including how they leverage FastAPI and any noted performance results or benefits:

  1. Machine learning model serving – Uber’s Ludwig: Uber’s open source project Ludwig (a no-code machine learning tool) adopted FastAPI to serve trained models via a REST API. The developers noted that FastAPI made it easy to spawn a production-ready prediction server with minimal code. They benefited from FastAPI’s validation (ensuring input data to models was correct) and from its speed to handle many predictions per second. In testing, they found FastAPI’s async concurrency handled concurrent prediction requests efficiently without the overhead that something like Flask would introduce. This allowed serving high-volume predictions with lower latency. Performance data: In internal benchmarks, Uber saw that switching to FastAPI improved throughput for their model service compared to a previous Flask-based prototype, and the automatic docs made it straightforward for other teams to integrate the prediction service.

  2. High-performance microservices – microsoft: FastAPI is used within Microsoft for some of their internal AI services. For example, a Microsoft team integrated FastAPI for machine learning microservices that had to run at scale and even got integrated into Windows and Office products. The developer Kabir Khan at Microsoft praised FastAPI, saying he plans to use it for all his team’s ML services due to its simplicity and performance. This implies that in evaluation, FastAPI met Microsoft’s production criteria for scalability. They likely appreciated that FastAPI could handle heavy async I/O (common in AI services that may call other APIs or do data fetches) and the strong typing which catches bugs early. While specific numbers weren’t publicly shared, it’s telling that a major company chose FastAPI for performance-critical components and found it “highly scalable” in practice.

  3. Orchestration system – Netflix’s Dispatch: Netflix open-sourced Dispatch, an incident management orchestration framework, built with FastAPI. Dispatch coordinates multiple services during an incident (like PagerDuty, Slack, etc.). FastAPI was chosen to build a robust REST API for Dispatch because of its fast development cycle and performance. In production at Netflix, Dispatch’s FastAPI backend handles significant load (multiple teams using it, lots of integration callbacks). Netflix’s tech blog highlighted that FastAPI’s modern Python features and automatic docs accelerated their development. Performance note: Dispatch needed to respond quickly to incident events; FastAPI’s async support allows it to make outgoing calls (to Slack API, etc.) concurrently. This real-world use confirms that FastAPI scales: Netflix wouldn’t use it if it couldn’t handle their scale. Indeed, in independent benchmarks, FastAPI under Uvicorn can handle thousands of requests/sec – something Netflix likely confirmed in their own testsfastapi.tiangolo.com. Dispatch demonstrates FastAPI’s reliability under pressure and its capacity to be the backbone of an important internal tool.

  4. Open-source recipe app – Mealie: Mealie is a self-hosted recipe manager that uses FastAPI as its backend (with a Vue.js frontend). It’s an example of an open-source web application where FastAPI provides not just APIs but also serves an admin interface and handles user management. Mealie’s choice of FastAPI was due to its lightweight nature and how easy it was to integrate with a database (they use SQLAlchemy) and background job processing (for things like fetching recipe data from URLs). This app isn’t about extreme performance, but about developer productivity and maintainability. Even so, users report Mealie to be quite snappy in usage. FastAPI’s asynchronous capabilities mean Mealie can concurrently fetch images or parse websites while still serving user requests. The maintainers have noted that contributions were easier because of FastAPI’s clear structure and the interactive docs helping new contributors understand the API. This shows FastAPI’s benefit in community-driven projects: new developers can experiment with the API via docs without needing to read all the code.

  5. Analytics and data APIs – explosion’s spaCy/Prodigy and others: The team behind spaCy (Explosion AI) moved their API services to FastAPI and publicly praised it. They use FastAPI to serve APIs for their NLP products (like Prodigy annotation tool’s backend). In these scenarios, FastAPI handles moderate loads of requests that may involve heavy CPU work (NLP processing). They likely combine FastAPI with multiprocessing or job queues for the heavy lifting, but chose it for the API part because it’s fast and reliable. In terms of performance, one of their founders mentioned they “switched to FastAPI and liked it” – implicitly, it handled their throughput needs better than alternatives. Another data point: TechEmpower Benchmarks – FastAPI (with Uvicorn) ranks among the top Python frameworks. For example, in JSON serialization tests, FastAPI was only slightly behind Starlette and achieved around ~20k requests/sec on a single core. This kind of metric gives confidence when building data APIs that FastAPI won’t be the bottleneck; rather, the bottleneck will be the data processing itself.

  6. Community and others: There are many more examples: Reddit’s backend services (some newer ones are in FastAPI), internal microservices at numerous companies, and community projects. For instance:

    • An open-source project “Polar” (by Polarsource) uses FastAPI with SQLAlchemy and Celery to provide a production-grade web app for managing GitHub sponsors. They reported that FastAPI’s ability to integrate with Celery (for background jobs) and serve a high-throughput API made their stack scale. The app deals with potentially heavy load (polling GitHub APIs, handling webhooks), and FastAPI’s async nature allowed them to concurrently handle webhook requests efficiently.

    • Another example is OpenAPI generator itself being able to target FastAPI – meaning many new APIs are being built with FastAPI in mind, showing its popularity and trust in production.

In summary, real-world applications highlight FastAPI’s strengths:

  • Performance: Being among the fastest Python frameworks means even at scale (Netflix, Microsoft), FastAPI delivers the needed throughput. For I/O heavy workloads, its async concurrency shines (lower resource usage compared to spawning many threads in Flask).

  • Productivity: Teams often mention how quick it was to develop and iterate with FastAPI (thanks to its design and tooling). This translates to faster time-to-market and fewer bugs (e.g., catching issues via types).

  • Adoption and community: It’s used in production by large companies and is well-regarded, which means there is robust community support and constant improvements.

These use cases demonstrate that FastAPI is not just theoretically good – it’s battle-tested in scenarios requiring high performance, scalability, and reliability. Whether you’re building an ML inference service, an orchestration system, or a user-facing web app, FastAPI provides the needed features and has proven its worth through these examples.

Alternatives and comparisons

FastAPI is often compared to other Python web frameworks. Here, we provide a detailed comparison table of FastAPI vs Flask and Django (Django REST Framework), covering features, performance, learning curve, community support, documentation, licenses, and typical use cases. After the table, we’ll discuss a brief migration guide for those moving from these frameworks to FastAPI.

Comparison Table: FastAPI vs Flask vs Django

AspectFastAPI (Modern API framework)Flask (Microframework)Django (Full-stack framework)
Release year2018 (newer, fast-growing)2010 (proven, widely used)2005 (very mature, established)
ArchitectureASGI-based; async-first; built on Starlette & Pydantic. Highly modular.WSGI-based; synchronous (async supported in v2, but not core). Minimal core with extensions.WSGI-based; synchronous (added limited async in recent versions). Monolithic, “batteries-included” (ORM, auth, templates).
Core featuresFocused on APIs: automatic docs (Swagger UI), request validation, dependency injection, OAuth2 support. No built-in template or ORM (use external).Very minimal out-of-the-box: routing, jinja2 templating, WSGI server. Most features (auth, validation) require third-party extensions.Full-stack: ORM (Django ORM), templating system, admin interface, authentication system, forms, migrations all included. Also Django REST Framework (DRF) for APIs.
PerformanceVery High – among fastest Python frameworks. Can handle ~20k+ req/s in JSON serialization tests. Async support yields great concurrency for I/O. Comparable to Node.js/Go for many tasks.Moderate – Being synchronous, single process Flask can handle ~2k-3k req/s on one core. Scales via multi-processing. Add-ons (like gevent or waitress) needed for concurrency.Low to Moderate – Django’s overhead (middleware, ORM) makes it slower per request. Typically a few hundred to 1k req/s on one core for simple API. Meant to scale via adding more servers. Async in Django (channels or ASGI) still not as lightweight as FastAPI.
Learning curveGentle/moderate – Easy for those familiar with Python type hints. Simple syntax feels natural (just Python functions). However, beginners must learn async concepts and Pydantic models. Great editor support reduces confusion.Gentlest – Very straightforward to start: just Python functions and routes. Little enforced structure. Beginners can get a basic app running quickly. However, lack of structure can lead to learning by trial-and-error when adding extensions (learning many small libraries).Steep – A lot of concepts to understand: ORM, migrations, template engine, settings, the request/response lifecycle, etc. The framework has a strict structure (which is eventually helpful, but onboarding is significant). DRF for APIs adds another layer of concepts (Serializers, ViewSets).
Community & supportLarge and growing – FastAPI has ~80k+ GitHub stars and a very active community. Questions on StackOverflow ~7k. Frequent updates. Backed by creator (@tiangolo) and contributors, with growing ecosystem (SQLModel, Typer by same author).Huge – Flask is one of the most popular frameworks. Mature community, many extensions (Flask-Login, Flask-RESTful, etc.). Lots of tutorials and solved Q&A. Slower update rate (framework is stable/frozen, not many new features recently).Huge – Django has a massive community, used by countless companies. Django REST Framework (DRF) is also widely adopted. Tons of plugins, packages (django-allauth, etc.). Backed by the Django Software Foundation. Excellent long-term support and documentation.
DocumentationExcellent – FastAPI docs are comprehensive with lots of examples, covering both basics and advanced (dependency injection, security) clearly. Automatic API docs (Swagger/Redoc) for your app are a huge plus. Type annotations help self-document code.Good – Flask’s official docs are clean and simple. Being minimal, there’s not too much to document. Many community articles available. However, for extensions, documentation quality varies by extension.Excellent – Django’s docs are very thorough (one of the best in open source). Covers all components with examples. DRF docs are also good. Lots of books available due to framework’s age.
LicenseMIT License (permissive). You can use it freely in commercial projects.BSD-3-Clause (permissive). Very business-friendly.BSD-3-Clause (permissive). Django is also very business-friendly in terms of license.
Typical Use casesModern APIs & microservices: FastAPI is ideal for building RESTful or RPC APIs, especially for microservices, data science backends, and async tasks (websockets, streaming). Great for ML model serving, IoT backends, mobile app APIs, or any scenario needing high throughput and low latency. Also chosen for new projects where developer speed is crucial. Not designed for server-rendered HTML (though can serve via Jinja if needed).Simple web apps and services: Flask is great for quick prototypes, small to medium APIs, or adding a lightweight API layer to an existing app. Often used for simple websites or microservices that don’t need the full power of Django. Good when you want full control and to choose components à la carte. If project grows, you may need to manage many extensions.Full-stack websites and large projects: Django shines in content-heavy sites, dashboards, admin interfaces, or applications that require an integrated solution (database, forms, authentication out-of-the-box). E.g., CMS, e-commerce sites, etc. With DRF, it can also create robust APIs, though heavier than FastAPI. Use Django when you want convention and don’t mind the learning curve, or need the admin GUI and ORM.

As seen, FastAPI offers a blend of high performance, modern features, and an easier learning curve compared to heavy frameworks, making it a top choice for APIs in 2025. Flask remains a go-to for small apps or as a beginner’s introduction, but it lacks FastAPI’s async and built-in validation. Django is unparalleled for complete web applications with a lot of moving parts (if you need an ORM or templating engine out of the box), but for pure API development it can be overkill and slower. Tornado served its purpose for async needs in the past, but now frameworks like FastAPI (built on asyncio) typically offer better performance and ergonomics, so Tornado is less common unless working in a legacy Tornado codebase or specific edge cases.

Migration guide: moving to FastAPI

If you have an existing application in Flask, Django, or Tornado, here are guidelines for migrating to FastAPI:

Migrating from Flask to FastAPI:

  • Routing: In Flask you use @app.route with methods; in FastAPI you’ll use @app.get, @app.post, etc. The conversion is straightforward. For example, a Flask route:

    @app.route('/users/<int:user_id>', methods=['GET'])
    def get_user(user_id): ...

    becomes FastAPI:

    @app.get('/users/{user_id}')
    def get_user(user_id: int): ...

    (Add type hints for parameters for better validation).

  • Request data: Flask uses request.form or request.get_json(). In FastAPI, define function args for query params or body. E.g., instead of request.args['name'], use def endpoint(name: str). For JSON body, define a Pydantic model and use it as a parameter. This is a shift: you'll create classes for your data (which yields more robust code).

  • Globals/context: Flask’s g and app.context_processor things can be replaced by dependencies in FastAPI. E.g., if you had g.db for a database, create a get_db() dependency that yields a session and use Depends(get_db). Similarly, Flask’s before_request functions can be translated to dependencies that run for each request or middleware.

  • Extensions: Find FastAPI equivalents or alternative approaches:

    • Flask-Login (user session management) doesn’t directly port because FastAPI is stateless by default. You’d implement auth using OAuth2 (JWT or OAuth flows) or use Starlette’s sessions if needed. If session cookies are needed, Starlette’s middleware can handle server-side sessions similar to Flask’s.

    • Flask-WTF (forms) -> you would handle forms either by reading form data via Form(...) dependencies or move logic to the frontend. FastAPI can parse form fields with Form(...) in the signature.

    • Flask-Mail, Flask-Anything – these extensions typically just wrap other libs. In FastAPI, you’d use the underlying library (like an SMTP client) directly, possibly in a background task for sending mail.

  • Templates: If you used Flask’s Jinja2 templating, you can use Starlette’s Jinja2Templates. FastAPI can render templates by returning templates.TemplateResponse("template.html", {"request": request, "data": data}). So you can still serve HTML if needed.

  • Deployment: If you were using Flask’s built-in dev server, switch to Uvicorn for dev, and something like Gunicorn+Uvicorn for production. The concept of running behind WSGI (Flask) changes to ASGI, but if deploying via containers or similar, it’s just a different command (uvicorn main:app). Many hosting services now support ASGI out-of-the-box.

Flask to FastAPI is usually straightforward; you mostly need to introduce Pydantic models for structured data and use dependency injection for things like database connections or auth instead of global Flask app context.

Migrating from Django/Django REST framework to FastAPI:

  • Project structure: Django projects have apps, urls.py, models.py, etc. FastAPI is much more flexible. You can organize by modules similarly (maybe have a routers folder for different endpoints, analogous to urls include). Use APIRouter in FastAPI to mirror Django's app url includes for modular design.

  • Models: If you have Django ORM models, you can continue to use them in FastAPI (Django’s ORM can be used outside of Django if you configure settings). Or you might migrate to an alternative like SQLAlchemy or Tortoise. If keeping Django ORM, you’ll need to initialize Django in FastAPI (set up settings and call django.setup() before running app). A simpler path might be to gradually replace ORM usage with something like SQLAlchemy which integrates more naturally with FastAPI.

  • Serializers -> Pydantic: In DRF, serializers validate and convert data. In FastAPI, Pydantic models fulfill this role. Translate your DRF Serializer classes to Pydantic models (fields with types and validators). Many field options (max_length, etc.) have equivalents in Pydantic Field.

  • Views -> endpoint functions: DRF’s APIView or ViewSet classes would become simpler function definitions (or you can use classes if desired, but not required). For example, a DRF ViewSet action retrieve becomes a FastAPI function on a router with GET.

  • Auth: Django’s auth (session or token) could be replaced with FastAPI’s OAuth2 JWT approach. Alternatively, if you still have a Django app, you could even mount the Django app inside FastAPI using WSGIMiddleware to reuse Django features (FastAPI docs demonstrate mounting a Flask or Django app under a certain path). This could help migrate incrementally: for example, keep the Django admin and some pages, but serve new API endpoints via FastAPI in the same server.

  • Middlewares: Django middleware like CSRF or security middleware will not apply in FastAPI. You need to implement or use Starlette Middleware for similar functions (e.g., Starlette has a CORSMiddleware, etc.). Many concerns like CSRF may not apply if you’re using JWT auth for APIs. Be aware of differences (Django autoescapes templates, etc., whereas Jinja in FastAPI will too by default – that’s similar).

  • WebSockets: Something that might excite Django developers: FastAPI handles websockets easily. Django (outside of Channels) doesn’t natively. If your app needs realtime features, migrating to FastAPI will simplify that.

Migrating a full Django app is a bigger effort because of the integrated components. Often, teams choose to create new microservices in FastAPI for new functionality while leaving the Django monolith for what it does well, gradually shifting features over. FastAPI’s mounting capability (mounting WSGI apps) can facilitate a transition where both run side by side under one domain while you migrate routes one at a time.

Migrating from Tornado to FastAPI:

  • Async Code: Tornado’s async code can be refactored to async def easily since Tornado had its own coroutine patterns or used gen.coroutine. The logic mostly carries over. Instead of Tornado’s self.write() in a RequestHandler, you return data from an endpoint function. WebSocket handlers in Tornado (which use on_message callbacks) become simpler in FastAPI by using @app.websocket and await websocket.receive_text(), etc.

  • Remove Tornado-specifics: Tornado isn’t compatible with ASGI, so you’ll fully replace the web server part. But any library usage (like motor for MongoDB async, etc.) can remain since it’s async and will work in FastAPI.

  • Routing: Tornado’s routing table (which might be in code adding Handlers) will be replaced by FastAPI’s decorator routes.

  • Template and static: Tornado has its template system and static file serving. In FastAPI you’d use Jinja2 (or even reuse Tornado’s template engine if desired by calling it manually, but typically Jinja2 is used) and StaticFiles middleware for serving static.

  • Performance Gains: Expect better request throughput after migrating to FastAPI/Uvicorn, as Tornado’s performance for pure HTTP isn’t as optimized. Also, the developer experience improves with automatic docs and simpler syntax.

General migration tips:

  • You can incrementally migrate by running FastAPI alongside the old framework. For example, you can mount a Flask app inside FastAPI (or vice versa) temporarily, or run them on different ports and use a reverse proxy to route certain paths to the new service. This allows gradual transition rather than a big bang.

  • Understand that FastAPI’s philosophy is more explicit: you’ll declare schema and dependency up front. This might initially seem like more work than a quick Flask route, but it pays off in maintainability and error reduction.

  • Plan and test thoroughly. Write tests in the old app (if not already) and ensure the new FastAPI app passes the same tests (especially for a behavior-centric migration).

  • Consider training the team on FastAPI basics (especially Pydantic and dependency injection) so they can embrace the new patterns. Often, after an initial learning curve, developers appreciate the clarity and power.

Migrating to FastAPI can modernize your stack, improve performance and developer efficiency, and align your project with current and future Python web development trends. Many developers report that after moving to FastAPI, the speed to add new features increased by 2-3x due to the reduction in boilerplate and helpful tooling. Given its growing ecosystem and support, it’s a solid step for projects that need the advantages discussed throughout this guide.

Resources and further reading

To continue learning FastAPI and get help when needed, check out the following resources:

  • Official documentation: The FastAPI docs (https://fastapi.tiangolo.com) are the best starting point – they include a tutorial, advanced topics, and recipes. The Tutorial - user guide section is highly recommended for step-by-step learning, and the advanced user guide covers deeper features.

  • Official GitHub repository: The source code and issue tracker are on GitHub at https://github.com/tiangolo/fastapi. It’s a good place to see examples, report issues, or read discussions. (FastAPI’s GitHub is very active, reflecting its vibrant community).

  • FastAPI people and external articles: The FastAPI site lists external links and community articles. This includes blog posts, videos, and tutorials by the community – a great way to see how others are using FastAPI. For instance, the FastAPI tag on Medium and Dev.to often has updated tutorials (search for “FastAPI 2024” etc. for latest).

  • Stack Overflow (Q&A): There are thousands of questions on Stack Overflow tagged [fastapi]. Many common issues have been asked and answered. If you encounter an error, odds are someone asked about it – search there. Examples: “FastAPI CORS issue” or “FastAPI background tasks not running” – likely solutions exist on SO.

  • FastAPI Discord & forums: FastAPI has an official Discord server for live discussion. You can join to ask quick questions or discuss best practices. There’s also a discussions tab on the GitHub repo where the community and maintainers answer conceptual questions.

  • GitHub examples and boilerplates: Check out the FastAPI GitHub organization and other repositories:

    • Full Stack FastAPI template by the creator (Sebastián Ramírez) is a full-stack project generator with FastAPI (including React front-end, Docker, etc.). It’s a great reference for structuring larger projects.

    • Awesome FastAPI (an “awesome-list” on GitHub) – a curated list of FastAPI resources, including open-source projects using FastAPI, extensions, and libraries.

  • Books and courses: As FastAPI grows, so do formal learning materials:

    • “High Performance Web Apps with FastAPI” by Malhar Lathkar (2023) – covers using FastAPI and Pydantic for building APIsreddit.com.

    • “FastAPI: Modern Python Web Development” by Bill Lubanovic (O’Reilly, 2022) – provides a comprehensive guide through FastAPI featuresoreilly.com.

    • Online courses on Udemy or others: e.g., “FastAPI Complete Course” (Udemy, 2025) covering building and deploying FastAPI appsudemy.com.

      Ensure books or courses cover FastAPI with Python 3.9+ and Pydantic v2 updates (if they are recent).

  • YouTube tutorials and talks: Many conference talks and YouTube tutorials are available:

    • “Introduction to FastAPI” videos – often walking through building a simple API.

    • PyCon and other conference talks by Sebastián Ramírez (creator) – e.g., “FastAPI from the ground up”.

    • TestDriven.io has a great Moving from Flask to FastAPI guidetestdriven.io, and other blog series that can be very insightful.

  • Community forums/Reddit: The subreddit r/FastAPI is small but growing, where people share tips or ask questionsreddit.com. Also, broader communities like r/Python or r/learnpython often discuss FastAPI. Keep an eye on those for interesting use cases or problems and solutions.

  • Starlette & Pydantic docs: Since FastAPI builds on Starlette and Pydantic, it can help to reference those docs for advanced use. For example, Starlette’s middleware and test client docsfastapi.tiangolo.com, or Pydantic’s docs for complex model validation (like exotic types, custom validators, etc.)en.wikipedia.org.

  • Deployment guides: Many cloud providers have guides for FastAPI:

    • e.g., Deploying FastAPI on AWS Lambda with API Gateway (using Mangum adapter),

    • Deploying on Google Cloud Run, Heroku, etc. A quick web search often yields step-by-step tutorials as FastAPI has become a common choice.

  • Performance benchmarking: If interested in performance details, see the Benchmarks page on FastAPI’s site which links to TechEmpower benchmarks, and the GitHub issue discussions like “FastAPI performance vs X” where community discusses tuning.

By leveraging these resources, you’ll be well-equipped to develop, troubleshoot, and optimize FastAPI applications. The community is very welcoming – don’t hesitate to ask questions on Discord or Stack Overflow when you encounter something new. FastAPI is evolving quickly (with upcoming features and improvements), so staying updated via the official Twitter (@fastapi) or the release notes on GitHub is also a good idea.

Happy coding with FastAPI, and enjoy the journey of building fast, efficient, and fun APIs with Python!

Sources:

FastAPI official docs and benchmarks, insights from community discussions, and real-world usage reports from Microsoft, Uber, Netflix, and others as cited throughout the guide.astAPI: Comprehensive Beginner’s Guide (2025 Edition)

Introduction and background of FastAPI

FastAPI is a modern open-source Python web framework for building high-performance APIs, released in 2018 by Sebastián Ramírez. It gained popularity rapidly due to its ease of use, intuitive design, and speed, leveraging standard Python type hints for data validation and documentation. FastAPI is built atop Starlette (ASGI toolkit) and Pydantic (data validation library), which enable its performance and features. In fact, FastAPI’s performance is on par with Node.js and Go for API workloads, and it’s one of the fastest Python frameworks available according to independent benchmarks.

Community and adoption: Since its inception, FastAPI has seen explosive growth. By late 2024 it surpassed older frameworks like Flask in popularity – FastAPI reached 78.9k stars on GitHub, overtaking Flask’s 68.4k. Surveys show FastAPI rising from obscurity to being used by ~25% of Python web developers by 2023. Major companies have adopted FastAPI for production systems, including Microsoft, Uber, and Netflix, thanks to its reliability and performance. FastAPI is distributed under the permissive MIT license and has an active community contributing to its ecosystem. In summary, FastAPI’s combination of modern Python features (async/await, type hints), automatic docs, and robust performance has made it a go-to choice for building APIs in Python in 2024–2025.

How FastAPI works: architecture and key components

FastAPI is designed as a lightweight layer combining several powerful components in the Python ecosystem:

  • ASGI and starlette: FastAPI is built on Starlette, an ASGI (Asynchronous Server Gateway Interface) framework. ASGI support means FastAPI can handle asynchronous requests concurrently, unlike traditional WSGI frameworks. Starlette provides the underlying routing, middleware, and async server capabilities that FastAPI extends. When you run a FastAPI app, you typically use an ASGI server like Uvicorn to serve it. Uvicorn is a lightning-fast ASGI server that executes FastAPI apps and handles I/O at scale. Together, Starlette and Uvicorn enable FastAPI’s event loop and non-blocking request handling.

  • Pydantic for data models: FastAPI heavily utilizes Pydantic for data parsing and validation. You define Pydantic BaseModel classes to represent request bodies or response schemas. FastAPI automatically converts incoming JSON into Python objects and validates fields (types, ranges, etc.) before your endpoint logic runs. This provides a robust layer of error checking with minimal code. Pydantic also serializes responses back to JSON. By using standard Python type hints and Pydantic models, FastAPI ensures that data is validated, parsed, and documented according to your models.

  • Dependency injection system: FastAPI includes a simple yet powerful dependency injection mechanism via the Depends() function. This lets you declare “dependencies” (like database connections, authentication checks, etc.) that are resolved for each request. Under the hood, FastAPI’s dependency injector handles calling these functions (which can be normal or async) and provides their results to your endpoint function. This promotes clean code architecture and resource sharing (e.g., one DB session per request).

  • Automatic documentation (OpenAPI): FastAPI automatically generates an OpenAPI schema (formerly Swagger) for your API. Thanks to using Pydantic and type hints, it documents all endpoints, parameters, and schemas without extra work. Two interactive documentation UIs are included by default: Swagger UI and ReDoc, available at /docs and /redoc URLs. These allow developers to explore and test the API in the browser. (We’ll see an example of the docs interface shortly.)

  • Integration of components: Importantly, FastAPI isn’t a large monolith – it’s a glue of optimized components. For example, when a request arrives, Uvicorn (ASGI server) hands it to Starlette, which matches a route and calls FastAPI’s endpoint function. FastAPI processes dependencies and Pydantic model validation, then your function runs (either sync or async). The result (e.g. a Pydantic model or dict) is serialized (via Pydantic/JSON) and sent back as a response. This layered architecture keeps FastAPI lean yet powerful, reusing proven libraries.

Key framework components summary:

  • FastAPI Class: Creates the application instance and includes methods for routing.

  • Path Operation Decorators: (@app.get, @app.post, etc.) provided by FastAPI for declaring endpoints.

  • Pydantic BaseModel: Used for defining request/response data schemas with validation.

  • Starlette: Provides Request/Response classes, routing, middleware, WebSocket support, etc.

  • Uvicorn: ASGI server that runs the app (you can also use alternatives like Hypercorn, Gunicorn with Uvicorn workers, etc.).

  • OpenAPI Schema: FastAPI generates a JSON schema describing the API, powering the docs UIs and allowing client code generation.

FastAPI’s design is an example of composition: it “stands on the shoulders” of Starlette and Pydantic. This means when you use FastAPI, you also benefit from improvements in those libraries (for instance, Pydantic 2.0’s speedups, Starlette’s middleware ecosystem, etc.) with minimal overhead introduced by FastAPI itself.

Benefits and use cases of FastAPI

FastAPI offers numerous benefits that make it especially appealing to new Python developers building web APIs:

  • High Performance: As the name suggests, FastAPI is built for speed. It’s capable of handling very high throughput thanks to async I/O. Benchmarks (e.g., TechEmpower) show FastAPI (with Uvicorn) can handle on the order of 15k–20k requests per second for simple endpoints, making it one of the fastest Python frameworks (only behind lower-level Starlette/Uvicorn which FastAPI uses internally). This means you get production-level performance out of the box, on par with Node.js and Go APIs, which is reassuring as your project scales.

  • Developer friendly (easy to learn): FastAPI is designed to be intuitive and easy to use. If you know basic Python, you can pick up FastAPI quickly because you declare routes using standard function definitions and type annotations – there’s no new syntax to learn. Many common errors are eliminated by design: for example, if you forget to validate input, FastAPI (via Pydantic) does it for you. The framework was tested against the most popular editors to ensure great editor support and autocompletion. This means as you code, your IDE will suggest attribute names and flag type errors, reducing bugs by ~40% (as claimed by FastAPI’s author). New developers often report that FastAPI “feels like writing regular Python” with helpful guidance from the tools.

  • Less code and fewer bugs: FastAPI follows a “sensible defaults” philosophy. Many features are auto-configured or inferred, so you write minimal boilerplate. For instance, a single Pydantic model class defines both the validation schema and example in docs and doesn’t need separate serializer code. This reduces the amount of code you write and maintain (developers have noted ~30-40% less code compared to equivalent Flask apps). With less code and strong typing, there are fewer opportunities for human error. Additionally, error messages in FastAPI/Pydantic are quite descriptive (pointing out exactly which field is wrong, etc.), which helps debugging.

  • Automatic interactive documentation: A standout feature is the built-in docs UI. FastAPI automatically provides Swagger UI and ReDoc interfaces that document every endpoint, parameter, and schema. For a beginner, this is incredibly useful – you can visually explore and test your API without writing any docs yourself. It’s gratifying and educational to see your API documented as you build it. The docs UIs also make it easier to share your API with others (front-end developers, QA, etc.) who can understand and try it out live.

  • Validation and security out of the box: FastAPI provides validation of incoming data (types, ranges, formats) without any extra effort, thanks to Pydantic models and Python types. This means new developers are less likely to accidentally accept bad data – FastAPI will return clear 400 errors with details if the input is invalid. Security features like authentication, OAuth2, and CORS are also supported via utilities and dependency injection, so implementing common auth flows is straightforward (important for production-readiness even in beginner projects).

  • Asynchronous concurrency: FastAPI’s async support is a huge benefit for use cases involving I/O (database calls, external API calls, file operations). A traditional Flask app (WSGI) would block one request while waiting on I/O, but FastAPI can serve other requests during waits. This means for I/O-heavy applications (common in web services), FastAPI can handle many simultaneous connections efficiently. For example, one user on Reddit noted that FastAPI’s async nature made it ideal for their AI app, which needed to call external APIs and perform data processing concurrently. For beginners, this means you don’t have to deeply understand multi-threading – using async def functions gives performance benefits automatically.

  • Use cases especially for new developers: FastAPI is particularly well-suited for:

    • RESTful APIs and microservices: If you’re building a backend for a web or mobile app, FastAPI allows you to quickly create JSON APIs. It’s focused on API use cases, unlike heavier frameworks. You can start with a simple “Hello World” and grow to more complex endpoints easily (we’ll see an example soon).

    • Data science & machine learning applications: Many beginners in data science turn to FastAPI to deploy models or data analysis as an API. FastAPI’s performance helps serve ML models quickly, and its easy syntax lowers the barrier to deployment for those not originally software engineers. (Indeed, Uber’s Ludwig ML library uses FastAPI to serve predictions in a production setting, and many Kaggle prototypes use FastAPI for exposing models.)

    • Real-time and async services: FastAPI’s ability to handle WebSockets and server-sent events makes it viable for real-time features (e.g. chat apps, live dashboards). A beginner can use the provided WebSocket support or background tasks to, say, push updates to clients without learning a complex async library. For example, one user cited FastAPI’s support for Server-Sent Events and OAuth2 as a reason it fit their real-time requirements well.

    • Prototyping and Learning: Because of the interactive docs and minimal setup, FastAPI is great for quickly prototyping an idea or learning how web APIs work. You can run a dev server and play with your API in the browser (via Swagger UI) in minutes. This tight feedback loop is excellent for newcomers.

  • Community and documentation: FastAPI has excellent documentation with lots of examples and an active community. The official tutorial is very beginner-friendly, walking through step-by-step. There are many community resources (blogs, examples, and even a dedicated subreddit r/FastAPI). Beginners have ample places to get help – e.g., over 7,000 questions on Stack Overflow are tagged with FastAPI, and an official Discord channel for FastAPI exists for real-time help. This supportive ecosystem means you’re never “stuck” for long when learning FastAPI.

In summary, FastAPI’s combination of speed, automatic validation/docs, and developer experience (great editor support, less boilerplate) yields tangible benefits: one FastAPI user at Microsoft said “it’s beautifully designed, simple to use and highly scalable”, and that sentiment is echoed by many adopting it for new projects. For newcomers, FastAPI offers a gentle learning curve while encouraging best practices (type hints, clear error handling) from the start. It’s an excellent framework to quickly go from idea to production-ready API.

Installation and setup guide

Getting FastAPI up and running in your local development environment is straightforward. In this section, we’ll cover installation and setup across various scenarios: pip, Conda, virtual environments, IDEs like VS Code/PyCharm, operating systems (Windows, macOS, Linux), using Docker, and even a generic cloud deployment setup. FastAPI is compatible with Python 3.8+ (ensure you have a recent Python installed).

1. Using pip (Python package installer): The quickest way to install FastAPI is with pip. This works on Windows, Mac, and Linux as long as Python is installed:

  1. Install FastAPI: Open a terminal and run:

    pip install fastapi

    This installs the core FastAPI library. FastAPI itself is lightweight – under the hood it will pull in Pydantic and Starlette.

  2. Install an ASGI server: FastAPI requires an ASGI server to run your app. The most common choice is Uvicorn. Install it via:

    pip install "uvicorn[standard]" 

    The "[standard]" extra installs Uvicorn with high-performance enhancements like uvloop. Alternatively, you can use Hypercorn or another ASGI server, but Uvicorn is recommended for beginners.

  3. Verify installation: You can verify by importing in Python:

    import fastapi; import uvicorn
    print(fastapi.__version__, uvicorn.__version__)

    This should output version numbers without errors. As of 2025, FastAPI’s version is around 0.95+ (still in “0.x” but stable) and Uvicorn 0.22+.

Note: If you prefer, you can install both in one command. FastAPI provides an all-in-one install option:

pip install "fastapi[all]" 

This will install FastAPI and all optional dependencies, including Uvicorn and other extras (like orjson, uvloop). The FastAPI docs recommend fastapi[standard] (which is similar to [all]) for most cases.

2. Using Conda (Anaconda/Miniconda): If you manage packages with Conda, FastAPI is available via conda-forge:

  • Ensure you have added the conda-forge channel. Then run:

    conda install -c conda-forge fastapi uvicorn

    This will install FastAPI and Uvicorn in your Conda environment. Using Conda can be convenient for managing different Python versions or isolating a data science environment. If you’re using Anaconda Navigator (the GUI), you can search for “fastapi” and “uvicorn” packages on the conda-forge channel and install them via the GUI interface.

Tip: Create a new environment for your FastAPI project to avoid dependency conflicts. For example:

conda create -n fastapi-env python=3.10 fastapi uvicorn -c conda-forge
conda activate fastapi-env

This creates and activates an env with Python and FastAPI installed.

3. Setting up a virtual environment (venv): It’s a best practice to use an isolated virtual environment for your FastAPI project, especially when using pip.

  • Create a venv: Navigate to your project folder and run:

    python3 -m venv venv

    (On Windows, use python -m venv venv). This creates a directory venv/ with a standalone Python.

  • Activate the venv:

    • On Linux/Mac: source venv/bin/activate

    • On Windows: venv\Scripts\activate

  • Once activated (prompt shows venv), install FastAPI and Uvicorn inside it using pip as shown earlier. All packages will stay local to this project’s venv.

  • When done, deactivate with deactivate. You can reactivate whenever you continue development. This approach prevents FastAPI from affecting your global Python or other projects.

4. Using Visual Studio Code (VS Code): VS Code is a popular editor for FastAPI development. Steps to set up:

  • Python extension: Install the official Python extension in VS Code if not already. It provides IntelliSense and venv support.

  • Create a project folder: Open VS Code in your project directory. If you haven’t created a virtual environment yet, VS Code can help:

  • Select Interpreter/Environment: Press Ctrl+Shift+P (Cmd+Shift+P on Mac) and run “Python: Create Environment”. Choose Venv when prompted and select a Python version. When it asks, select your requirements.txt (if you have one) or just proceed to create an empty env.

  • Install dependencies: VS Code will create and activate a virtualenv for you. You can then open a terminal in VS Code (it should show the venv active) and run pip install fastapi uvicorn. Alternatively, create a requirements.txt with fastapi and uvicorn listed, then run “pip install -r requirements.txt”.

  • IntelliSense and Run/Debug: VS Code will detect FastAPI and even suggests a debugger config. You can write your FastAPI app (e.g., main.py), then run Debug (F5). VS Code’s Python extension has a pre-built FastAPI launch configuration that uses Uvicorn to run your app and attaches the debugger. Select “FastAPI” in the debug configuration dropdown, and VS Code will start Uvicorn for you, typically on http://127.0.0.1:8000. You can set breakpoints in your code and debug interactively.

  • Tip: Ensure the Python interpreter selected (shown in VS Code’s status bar) is the one from your venv or conda env that has FastAPI installed. VS Code might prompt you or you can change it via the Command Palette (“Python: Select Interpreter”).

5. Using PyCharm (or IntelliJ/PyCharm Pro): PyCharm Professional has built-in support for FastAPI:

  • New project: In PyCharm’s new project dialog, you can select FastAPI as the project type if using PyCharm Pro. This will set up a new project with a virtual environment and install FastAPI for you. It may also create a sample main file.

  • Community edition: If you’re using PyCharm CE (which doesn’t have the FastAPI template), simply create a Python project, then add FastAPI and Uvicorn via pip install (PyCharm will offer to create a virtualenv when you open the project).

  • Run/debug: PyCharm Pro can auto-create a Run/debug configuration for FastAPI. It detects the uvicorn command. You can also manually create a run config: choose Uvicorn as the run target with main:app (assuming your FastAPI app instance is named app in main.py). PyCharm’s FastAPI support also includes an endpoints tool window to list your API routes for easy navigation.

  • Editor support: PyCharm offers excellent autocompletion and type checking for FastAPI (just like VS Code). As you type a path operation function, it will suggest parameter names from your Pydantic models, etc. This is part of what makes FastAPI intuitive to work with (80% of Python devs use editors that provide such support).

6. Operating system specific notes:

  • Windows: Make sure to use the correct Python executable (sometimes python vs python3). On Windows, after installing, you might run the app with python -m uvicorn main:app --reload. If you hit a pip is not recognized error, ensure Python and Scripts directory are in your PATH, or use the Python Launcher (py -3). Also, consider using PowerShell or the new Windows Terminal for a better experience.

  • macOS: If using the system Python, consider installing a newer Python via Homebrew (brew install python) to get 3.10+. Use python3 command explicitly. macOS might require granting permissions if you use port 80 (instead use 8000 or run as sudo).

  • Linux: Ensure you have Python 3 and pip. On Debian/Ubuntu, you might install with sudo apt update && sudo apt install python3-pip. Then use pip3 if pip is mapped to Python 2. For running the app, opening firewall ports (if testing from another machine) might be needed.

7. Using Docker for FastAPI:

Docker is a great way to containerize FastAPI applications for deployment or development. Here’s a basic setup:

  • Write your FastAPI app (e.g., main.py with app = FastAPI()).

  • Create a Dockerfile in your project:

    FROM python:3.10-slim
    WORKDIR /app
    COPY requirements.txt ./
    RUN pip install -r requirements.txt # which contains fastapi and uvicorn
    COPY . .
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

    This Dockerfile uses a slim Python base image, installs dependencies, copies your code, and sets Uvicorn to run the app on port 80 (inside container).

  • Build the image:

    docker build -t myfastapi-app .

  • Run the container:

    docker run -d -p 8000:80 myfastapi-app

    This maps container’s port 80 to your host’s 8000. Now your API is accessible at http://localhost:8000 on your machine. Docker ensures a consistent environment (useful if your local OS setup is troublesome or for deployment to servers).

  • Tips:

    • Include --reload only for development (it’s not usually needed in Docker if you rebuild often; also, --reload won’t work well if using multiple workers).

    • For even slimmer images, consider using tiangolo/uvicorn-gunicorn-fastapi Docker image (an official image with FastAPI optimizations). But starting with a Python base as shown is simpler.

    • If your app needs environment variables (e.g., secrets), you can pass them with -e flags to docker run or use a .env file.

8. Generic Cloud setup: Deploying FastAPI on a cloud VM or service involves similar steps with a few considerations:

  • Choose a host: You can use a VPS or VM (AWS EC2, DigitalOcean, Azure, etc.) or container platforms (AWS ECS/Fargate, Google Cloud Run, etc.). Ensure the host has Python 3.8+ or use Docker.

  • Install on server: SSH into your Linux VM, install Python (and pip), then install FastAPI and Uvicorn in a virtualenv as described. You might also install a process manager (like Gunicorn with Uvicorn workers, or even use PM2 for Node – but Gunicorn is common for Python) to run multiple workers.

  • Running for production: For example, on an Ubuntu server:

    pip install fastapi uvicorn[standard]
    pip install "uvicorn[standard]" gunicorn
    gunicorn -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:80 main:app

    This command uses Gunicorn to start 4 Uvicorn worker processes listening on port 80 (HTTP). Replace main:app with your module and app instance. This setup can handle higher load by utilizing multiple CPU cores.

  • Firewalls and ports: Be sure to open the appropriate port (80 or 8000) in your cloud provider’s firewall settings. Using port 80/443 (with TLS) is ideal for production. You might put a reverse proxy like Nginx or Traefik in front, but for a beginner cloud setup you can directly expose Uvicorn/Gunicorn on port 80.

  • Environment configuration: Cloud environments often use environment variables for configs. FastAPI can easily read env vars (you can use pydantic.BaseSettings or the built-in Settings in FastAPI to manage config). Ensure to set --host 0.0.0.0 when running on a cloud VM so that the server listens on the public network interface, not just localhost.

  • Scaling: For scaling beyond one VM, you’d use load balancers, container orchestration (Kubernetes), or serverless platforms. FastAPI works in those contexts too (e.g., you can deploy FastAPI on AWS Lambda using an ASGI adapter like Mangum, turning your app into a serverless function). For generic setups, start simple and then explore these advanced deployments as needed.

9. Anaconda navigator UI: If you prefer not to use the command line, you can install FastAPI via Anaconda’s GUI:

  • Open Anaconda Navigator, go to Environments, create a new environment (or select one).

  • Search for “fastapi” in the packages (make sure to select Channels: conda-forge in the dropdown). Tick FastAPI (and possibly Uvicorn if it appears separately) and apply. This will handle installation.

  • Then you can launch VS Code or PyCharm from within Navigator with that environment to start coding.

After following the above steps suited to your environment, you should have FastAPI installed and ready to use. Next, we will run a simple example to ensure everything works correctly.

Example: building a simple FastAPI application

Let’s walk through a complete, runnable FastAPI example that you can copy and run on your machine. This example will demonstrate core concepts: defining a FastAPI app, creating path operations (endpoints), using a Pydantic model for data, and handling errors.

Example scenario: We’ll create a basic inventory API for managing items in a store. It will have two endpoints – one to get an item by ID and one to create a new item. We’ll include some simple validation and error handling (like returning 404 if an item is not found, and ensuring item prices are non-negative).

from typing import Optional, Dict from fastapi import FastAPI, HTTPException  # Import FastAPI class and HTTPException for error handling from pydantic import BaseModel

app = FastAPI(title="Inventory API", version="1.0")  # Create a FastAPI app with metadata # Pydantic model representing an Item in our inventory class Item(BaseModel):
name: str
price: float
quantity: int = 0
description: Optional[str] = None # In-memory "database" (dictionary) to store items
items_db: Dict[int, Item] = {}

@app.get("/items/{item_id}")
def read_item(item_id: int):
"""
Retrieve an item by its ID.
"""if item_id not in items_db:
 # If item not found, raise a 404 error with a custom message raise HTTPException(status_code=404, detail=f"Item {item_id} not found")
 return {"item_id": item_id, "item": items_db[item_id]}

@app.post("/items/", status_code=201)
def create_item(item: Item):
"""
Create a new item with auto-generated ID.
"""# Simple business rule: price must be non-negative if item.price < 0:
 raise HTTPException(status_code=400, detail="Price must be >= 0")
 # Generate a new ID (just using length of dict + 1 for simplicity)
new_id = len(items_db) + 1
items_db[new_id] = item
 return {"message": "Item created", "item_id": new_id, "item": item}

Let’s break down what’s happening in this code:

  • We import FastAPI and create an app instance. This app is what Uvicorn will run. We also set a title and version for documentation purposes (these appear in the Swagger UI).

  • We define an Item model using BaseModel from Pydantic. This model has name (string), price (float), quantity (int, default 0), and an optional description. The model enforces types and can provide default values. FastAPI will use this model to automatically validate input for the create endpoint.

  • We use a simple dictionary items_db to store items in memory. In a real app, this would be a database or persistent storage, but a dict is fine for demonstration.

  • @app.get("/items/{item_id}") defines a GET endpoint to read an item. The {item_id} in the path means FastAPI will parse it as an integer (because our function parameter item_id is annotated as int)betterstack.com. In the function:

    • We check if the item exists in items_db. If not, we raise an HTTPException(status_code=404). This exception will cause FastAPI to return an HTTP 404 response with the given detail message in JSONfastapi.tiangolo.com. This is how you handle errors gracefully – you don’t have to return error responses manually, raising an HTTPException is enough.

    • If found, we return a dictionary with the item data. Notice we can return Pydantic models or Python dicts; FastAPI will automatically convert the Item model to a JSON (by converting to dict) in the response. The response will look like {"item_id": 2, "item": {... item fields ...} }.

  • @app.post("/items/") defines a POST endpoint to create a new item. We specify status_code=201 to indicate that on success it returns HTTP 201 Created. The function takes an item: Item parameter – FastAPI will automatically:

    • Read the request JSON body,

    • Validate it against the Item model (if a required field is missing or type is wrong, FastAPI will send a 422 Unprocessable Entity error detailing the validation issues without even entering our function),

    • Convert it into an Item object (item will be an instance of our Pydantic model inside the function, with all attributes accessible).

      Inside the function, we then:

    • Validate a business rule: if item.price < 0, we raise an HTTP 400 (Bad Request) with a message. FastAPI will return a JSON error like:

      {"detail": "Price must be >= 0"} 

      to the client. (If we didn’t do this, a negative price would technically still be accepted by Pydantic because it’s a float – Pydantic can enforce bounds too, but showing manual validation here for illustration).

    • If the item is valid, we generate a new ID (trivial logic here) and save the item in our items_db. Note: the item is a Pydantic model, so you could call item.dict() to get a dictionary, but you can also store the model itself as we did.

    • We return a success message containing the new item’s ID and the item data. FastAPI will serialize the Pydantic model in the response automatically.

Running this example: Save the code to main.py. Make sure you installed FastAPI and Uvicorn as described. Then run:

uvicorn main:app --reload

The --reload flag is useful in development – it auto-restarts the server if you edit the code. You should see console output indicating Uvicorn running, e.g.:

INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [...] using statreload
INFO: Started server process [...]
INFO: Waiting for application startup.
INFO: Application startup complete.

Try the API out:

  • In Swagger UI, you’ll see the two endpoints (GET /items/{item_id} and POST /items/). Click on POST /items/, then “Try it out”. You’ll see a JSON schema for Item. Enter some sample data, e.g.:

    { "name": "Apple", "price": 1.99, "quantity": 10, "description": "Fresh Red Apple" } 

    and execute. You should get a 201 response with a JSON result like:

    { "message": "Item created", "item_id": 1, "item": { "name": "Apple", "price": 1.99, "quantity": 10, "description": "Fresh Red Apple" } } 

    The “Try it out” actually performs a live request to your running API.

  • Now try GET /items/{item_id}. Put 1 as the item_id (the one we just created) and execute. It should return the item. Try with an ID that doesn’t exist, e.g. GET /items/42 – you will get a 404 response:

    {"detail": "Item 42 not found"} 

    FastAPI returns errors in a JSON with a detail field by default (you can customize this format if needed).

  • You can also view the alternative documentation at /redoc (ReDoc UI), which is a different style of docs. Both UIs are powered by the same OpenAPI schema.

This example demonstrates:

  • Defining data models for requests (the Item model) and how FastAPI automatically validates input.

  • Creating path and body parameters (path: item_id, body: item model) and how they are provided to your functions.

  • Basic error handling using exceptions (404 and 400 errors).

  • The automatic documentation that FastAPI provides (listing endpoints and schemas).

You can expand this further by adding more endpoints – for instance, an @app.put("/items/{item_id}") to update an item, or @app.delete("/items/{item_id}") to remove one. FastAPI supports all HTTP methods and features like query parameters, headers, cookies, etc., in a similar declarative way.

Now that we have a basic app running, let’s explore FastAPI’s core features in more depth and how to use them effectively.

Core features of FastAPI (beginner-friendly)

FastAPI has many features, but here we will focus on five core features that are most useful for beginners: (1) Path operation decorators (routing), (2) Data models and validation with Pydantic, (3) Dependency Injection system, (4) Asynchronous capabilities (async/await), and (5) Automatic documentation (OpenAPI). For each feature, we’ll discuss its syntax and parameters, provide examples, highlight common errors (and how to fix them), give performance tips, and suggest integration ideas.

1. Path operations and routing (declarative endpoints)

What it is: Path operations are how you define API routes (endpoints) in FastAPI. They are declared with decorators on normal Python functions. FastAPI provides a decorator for each HTTP method: @app.get, @app.post, @app.put, @app.delete, etc. The decorator’s argument is the URL path. By attaching these to functions, you create route handlers.

Syntax and parameters:

@app.get("/users/{user_id}")
def get_user(user_id: int, verbose: bool = False):
...

In this example:

  • @app.get("/users/{user_id}") defines a GET endpoint at path /users/123 for example. Path parameters are indicated by {...} in the path. Here {user_id} means the function must accept a parameter named user_id. FastAPI will convert the path segment to the type of that parameter (int in this case). If conversion fails (e.g. /users/abc when user_id is int), FastAPI will automatically return a 422 error.

  • The function get_user has a path parameter user_id: int and also a verbose: bool = False. Because verbose has a default, FastAPI treats it as an optional query parameter (i.e., /users/123?verbose=true). By default, function params not found in the path are assumed to be query parameters (unless explicitly marked otherwise). So verbose can be provided as ?verbose=true. FastAPI will convert true/false strings to boolean automatically.

  • The return type isn’t declared here, but you could add -> User (Pydantic model) or -> dict. FastAPI uses annotations for documentation but doesn’t require an explicit return type.

Examples: Let’s illustrate a few variations:

@app.get("/items/{id}")
def read_item(id: str, q: Optional[str] = None):
 # "id" is a path param (string), "q" is an optional query param. if q:
 return {"id": id, "query": q}
 return {"id": id}

  • If you call /items/XYZ?q=test, you get {"id": "XYZ", "query": "test"}.

  • If you call /items/XYZ (no query), you get {"id": "XYZ"}.

@app.get("/users")
def list_users(limit: int = 10, active: bool = True):
 # Query params example: /users?limit=10&active=false
...

Here, limit and active are query parameters with defaults (10 and True). If not provided, they default; if provided, FastAPI parses limit from string to int and active “false” to bool etc.

@app.get("/files/{file_path:path}")
def read_file(file_path: str):
 # Use :path to capture path segments return {"file_path": file_path}

This uses a special converter :path in the decorator. Normally, {param} won’t capture “/” in the value, but :path tells FastAPI to accept a path-like segment. For example, requesting /files/etc/hosts will yield file_path = "etc/hosts".

Common errors & fixes:

  • Path/Function Parameter Mismatch: If you define a path {item_id} but your function signature doesn’t have item_id (or has it named differently), FastAPI will error on app startup. The names must match exactly. Fix: ensure your function parameters align with the path placeholders.

  • Type conversion errors: If a path param can’t convert to the specified type (e.g., /items/abc for item_id: int), FastAPI returns a 422 Unprocessable Entity with details. The fix is usually on the client side (provide correct type) or change the param type if letters should be allowed. You can also add regex constraints in the path if needed.

  • Duplicate routes or methods: If you accidentally declare two functions with the same HTTP method and path, FastAPI will raise an error at startup (duplicate routes). Fix is to ensure unique path+method or use API versioning/prefixes to differentiate.

  • Trailing slashes: FastAPI by default treats /items and /items/ as two different routes. If you only declare one, accessing the other may result in a 404. The solution is to be consistent in your routes or use the redirect_slashes option in Starlette’s routing to auto-redirect. As a beginner, just match the client exactly to how you declared.

  • Missing required query params: If a query parameter is declared without default (e.g., def func(q: str)), it becomes required. If the client doesn’t provide it, FastAPI returns a 422 error saying the query param is missing. The fix is to add = None or a default to make it optional, or ensure the client always sends it.

Performance tips:

  • Path operations themselves are very lightweight. The main overhead is parsing path and query parameters. To optimize, use proper types (int parsing is faster than str if you need numbers anyway). Avoid overly complex regex in paths as it might slow routing (though for most apps this is negligible).

  • If an endpoint is CPU-bound (e.g., heavy computation), using async def won’t make it faster (it may even be slightly slower for CPU tasks). Consider offloading CPU-bound work to a background thread or process. But for I/O-bound routes, making them async def enables concurrency (see Async feature below).

  • Use APIRouter for large apps: As your app grows, you can separate routes using fastapi.APIRouter. This has no runtime overhead but helps organize code (and you can include routers with prefixes for performance grouping, like all /admin routes can be mounted together, saving some routing lookup time). For a beginner project, performance difference is tiny, but structurally it’s beneficial.

Integration ideas:

  • FastAPI path operations integrate easily with front-end frameworks. For example, you can have a React app make AJAX calls to these endpoints. Thanks to automatic docs, you can even use tools like Swagger Codegen to generate API client libraries for your endpoints in various languages.

  • You can secure path operations with dependencies (e.g., requiring an auth token) – more on that in the Dependency Injection section. Essentially, you can attach a security dependency to any route to integrate with authentication systems.

  • If you’re migrating from Flask, you’ll find FastAPI’s routing familiar (decorators). The integration difference is that FastAPI also parses types for you. You no longer need to use request.args or manual parsing; just define parameters in the function signature. This tight integration with Python’s type system is a big win for maintainability.

2. Data models and validation (Pydantic models)

What it is: FastAPI uses Pydantic models (classes that inherit from BaseModel) to define the schema of request bodies and responses. This feature brings automatic data validation and parsing. By declaring a model, you describe the shape of the data (fields, types, optionality, constraints) and FastAPI/Pydantic handle the rest: converting incoming JSON into your model instance, validating types/constraints, and generating docs (using JSON Schema) for these models.

Syntax and parameters:

from pydantic import BaseModel, Field

class User(BaseModel):
 id: int
name: str
signup_date: Optional[datetime] = None
email: str = Field(..., regex=".+@.+\..+")
is_active: bool = True 

In this example:

  • We define a class User inheriting BaseModel. Each attribute is a field in the model with a type annotation.

  • id and name are required (no default, and not Optional). signup_date is optional (can be None) and of type datetime.

  • We use Field(..., regex=...) for email. The ... (Ellipsis) as default tells Pydantic this field is required (no default). We also add a regex constraint: the email must match a basic pattern. Field allows other validations like max_length, ge (>= constraint for numbers), lt (< constraint), etc. For example, we could do name: str = Field(..., max_length=50).

  • is_active has a default True, making it optional for input (defaults to True if not provided).

In FastAPI, you would use this model in an endpoint like:

@app.post("/users/")
def create_user(user: User):
 # user is automatically validated and is a Pydantic model instance
save_to_db(user)  # hypothetical function return user

When a request comes in, FastAPI will:

  • Read JSON body and map it into User (doing type conversions, e.g., if signup_date was a string in ISO format, it will convert to datetime).

  • Validate all fields. If any field is missing (without default) or violates a constraint (wrong type, regex fail, etc.), FastAPI sends a 422 response detailing each error.

  • Only if validation passes, create_user is called with a User object.

Examples and use cases:

  • Nested models: Pydantic models can be nested. For example:

    class Address(BaseModel):
    city: str zip: str class Customer(BaseModel):
    name: str
    address: Address

    If your POST body JSON has an "address": { "city": "...", "zip": "..." }, FastAPI will nest it into an Address instance inside Customer. This is great for representing complex JSON.

  • Response models: You can also specify a model for the response using the response_model parameter in the decorator. E.g.:

    @app.get("/users/{id}", response_model=User)
    def get_user(id: int):
    user_obj = db.get_user(id)
     return user_obj

    If you return a dict or an ORM object, FastAPI will try to convert it to the User model for the response, ensuring the output schema matches (and filtering out any extraneous fields). This adds a slight overhead but guarantees your API responses follow the schema you declare in documentation. It’s particularly useful if you want to hide certain fields (like passwords) – they won’t appear in the response if not in the model.

  • Field aliases & serialization: Pydantic allows aliases for JSON fields. If your JSON uses camelCase but you want Python snake_case, you can declare Field(..., alias="camelCaseName"). FastAPI can read incoming alias fields and also output using alias if configured. By default, it uses the field names as is.

Common errors & fixes:

  • Missing fields: If a required field is missing in the JSON, you get a 422 error with "msg": "field required" for that field. The fix is to provide the field or give it a default/Optional in the model if it’s actually optional.

  • Type mismatch: If a field is wrong type (e.g., string for an int), 422 error "msg": "value is not a valid integer" (for example). Fix is to send correct type; FastAPI/Pydantic are quite strict. They will do some coercion (e.g. "123" string could be accepted as int 123), but for complex types like datetime, you need ISO strings or timestamps, etc.

  • Value constraints: If you set max_length=50 and send a longer string, you’ll get a validation error listing the limit. Similarly for regex. Fix is obvious: meet the constraints.

  • Pydantic validation exceptions: If something in your model’s custom validation (if you add @validator functions in Pydantic) throws, it becomes a 422 error. You should catch issues in those validators and raise ValueError with a message, which Pydantic will include.

  • Mutable default (Gotcha): If you have a field that is a mutable type (like a list) with a default [], it can behave unexpectedly (as it’s shared across instances). Pydantic handles this by making a copy, but best practice: use Field(default_factory=list) for lists/dicts default. This ensures a new empty list per instance.

Performance tips:

  • Pydantic V2 Improvements: If using Pydantic v2 (FastAPI 0.100+ uses Pydantic 2 by default), enjoy significant speed boosts – model parsing is much faster (even uses Rust under the hood). Ensure you use latest FastAPI to leverage this.

  • For extremely high throughput scenarios, the JSON -> Pydantic conversion can be a hotspot. Some tips:

    • Use response_model_exclude_unset or similar if returning models with large optional fields to avoid serialization overhead of defaults.

    • Use orjson or ujson for response serialization if needed (FastAPI can integrate those via ORJSONResponse) which can be faster than default JSON. You’d install orjson and return ORJSONResponse(content=dict_or_model)

    • If input validation overhead is an issue and you trust the data (e.g., internal microservice call), you can skip validation by typing the parameter as dict instead of a model and manually constructing model if needed. But generally, the overhead is small relative to I/O.

  • Pydantic models create Python objects; if your payloads are huge (thousands of items), consider streaming responses or chunking to avoid large memory use. For example, for large query results you might want to use streaming responses rather than loading everything into Pydantic (which would allocate objects for each item).

  • Reuse models: If the same model is used in many endpoints, define it once and reuse. This not only avoids repetition but also is more efficient at runtime (Pydantic caches model schema).

Integration ideas:

  • Databases/ORMs: You can integrate Pydantic models with ORMs. For instance, you might have a SQLAlchemy model and you want to convert it to a Pydantic model for output. Pydantic provides from_orm=True capability on model config to accept ORM objects directly. You can also use libraries like SQLModel (created by FastAPI’s author) which unify Pydantic and SQLAlchemy models).

  • Form data / frontend: FastAPI can automatically handle form submissions or file uploads into Pydantic models (using Form(...) or UploadFile types), which is useful for integrating with HTML forms (though FastAPI is mostly used for JSON APIs).

  • Data science: You can integrate with pandas or numpy by converting data to standard types that Pydantic can handle (e.g., lists). Pydantic models can also have custom validators to, say, check that an image file is in a certain format, enabling integration with PIL or OpenCV for image processing inputs.

  • OpenAPI clients: Because Pydantic models define your schema, the OpenAPI docs generated allow other tools to generate clients. You could use openapi-generator to create, for example, a TypeScript client that has corresponding types for your models, making front-end integration type-safe.

3. Dependency injection with Depends

What it is: FastAPI’s Dependency Injection system allows you to declare “dependencies” for your path operation functions. A dependency is just a callable (function or class) that FastAPI will execute before your endpoint function, and inject the result into your function. This is done using the Depends() marker in function parameters. Dependencies are a powerful way to reuse logic (like auth, DB connection) across endpoints and to handle cross-cutting concerns (logging, rate limiting, etc.) in a clean manner.

Syntax and parameters:

from fastapi import Depends

def get_db():
db = DatabaseConnection(...)
 try:
 yield db
 finally:
db.close()

@app.get("/items/")
def read_items(limit: int = 100, db = Depends(get_db)):
 # FastAPI will call get_db() and inject the returned db
items = db.fetch_items(limit=limit)
 return items

In this snippet:

  • We define a function get_db that perhaps creates a database connection/session. It uses a Python generator with yield – this is a special way to define a "context-dependent" dependency (the code after yield runs after the request, for teardown). FastAPI recognizes that pattern to do setup/teardown (much like with context managers). Here, it yields a db object and ensures db.close() is called after the endpoint is done..

  • In the endpoint read_items, we have a parameter db = Depends(get_db). This tells FastAPI: “for this endpoint, before calling read_items, call get_db() and use its result as the argument db.” So db becomes an instance of our DatabaseConnection. We didn’t specify a type for db (you can, but it’s not required).

  • You can also declare dependencies that themselves have dependencies (sub-dependencies). FastAPI will resolve all in proper order. For example, get_current_user might depend on verify_token which depends on get_db, etc., and FastAPI handles the graph for you.

Examples:

  • Authentication dependency: A common example is checking a JWT token:

    def get_current_user(token: str = Depends(oauth2_scheme)):
     # oauth2_scheme is a dependency that reads an Authorization header
    user = verify_jwt_token(token)
     if not user:
     raise HTTPException(401, "Invalid auth")
     return user

    @app.get("/profile")
    def read_profile(current_user: User = Depends(get_current_user)):
     return {"username": current_user.username}

    Here, oauth2_scheme could be an instance of OAuth2PasswordBearer (provided by FastAPI) which itself is a dependency that extracts token from header. Then get_current_user depends on that to get the token, verifies it, and returns a user object or raises if invalid. The endpoint then depends on get_current_user. This means any request to /profile will automatically enforce authentication and inject the user if valid, simplifying the endpoint logic.

  • Reusable logic: Suppose multiple endpoints need to parse a common query parameter or check a condition:

    def validate_pagination(skip: int = 0, limit: int = 100):
     if limit > 1000:
     raise HTTPException(400, "Limit too large")
     return {"skip": skip, "limit": limit}

    @app.get("/users/")
    def list_users(p: dict = Depends(validate_pagination)):
     # p contains skip and limit return query_users(skip=p["skip"], limit=p["limit"])
    @app.get("/orders/")
    def list_orders(p: dict = Depends(validate_pagination)):
     return query_orders(offset=p["skip"], limit=p["limit"])

    Here, validate_pagination ensures limit is not crazy large for any endpoint that uses it. We don’t repeat that logic in each endpoint.

  • Classes as dependencies: You can use classes with __call__ or even Pydantic settings classes as dependencies, which is an advanced usage. For instance, a class that holds config values can be injected so that each endpoint has access to config without importing global variables.

Common errors & fixes:

  • Missing dependency: If FastAPI can’t resolve a dependency (maybe you forgot to include a default value or use Depends), you’ll see an error at startup or a 500 at runtime. Usually, FastAPI will catch issues at startup, e.g., if a dependency function has parameters that are not provided. Fix by ensuring each dependency’s own parameters have defaults or further Depends as needed.

  • Order of execution and context: If you accidentally create a dependency that depends on something not available, you get errors. For example, you cannot depend on request body directly in a dependency (because body is read after dependencies by default). There are ways around this (dependencies with dependant=... settings) but for beginners: dependencies are meant for things like auth, DB, etc., not to get the request body early.

  • Async vs sync dependencies: If a dependency is an async function (async def), FastAPI will run it in the event loop. If it’s regular def, FastAPI might run it in threadpool (for yield ones, it uses special handling). Usually you don’t need to worry, but if you see warnings about dependencies taking too long, ensure long IO in dependencies uses async.

  • Yield in dependencies: If you use yield in a dependency (context manager style) without the proper structure, you might get odd behavior. Always yield once. If you need to return a value and still have teardown, use yield (like shown in get_db above). Behind the scenes, FastAPI will treat the function as contextmanager. If you mistakenly put multiple yields or none at all, it won’t work as intended.

  • Dependency that returns None unexpectedly: If a dependency is supposed to return something but doesn’t (returns None), and your endpoint expects something, you might hit errors. Example: get_current_user returns None (didn’t raise) and then endpoint tries to use it assuming a user. Best practice is to always either return a valid object or raise HTTPException in a dependency if it fails; don’t return None unless None is an acceptable value in your endpoint logic.

Performance tips:

  • Dependencies add a slight overhead because FastAPI has to call these extra functions. However, they are highly optimized (using caching of dependency injection solving where possible). The overhead per request is usually microseconds to maybe a millisecond, which is trivial compared to I/O.

  • If a dependency is expensive (e.g., hitting a database every time to get settings), you can use caching. FastAPI’s Depends has dependency_cache internally for repeated sub-dependencies on the same request. Also, you can mark dependencies with @lru_cache if they should only run once per startup (like reading a file of config) – or better, do that outside of request flow.

  • Use global resources carefully: If using a global DB connection pool or something, you might not need a dependency to create it each time – instead, have a dependency that yields a session from a pool. This way you reuse connections. Dependencies should ideally manage scope – e.g., open on request, close after – rather than do heavy creation each time unless needed.

  • If you have nested dependencies, FastAPI tries to resolve them efficiently. But deep dependency trees could add overhead. Keep dependency layers as few as make sense (usually 1-3 levels at most). This is rarely an issue except in extremely performance-sensitive micro-optimizations.

Integration ideas:

  • Database sessions: We already saw an example. In larger apps, you often integrate an ORM (like SQLAlchemy or Tortoise) and use dependencies to get a session per request. Many FastAPI tutorials use Depends(get_db) to inject a session and commit/close it automatically.

  • Security schemes: FastAPI provides dependency-based tools for security (OAuth2, API keys, etc.). For instance, OAuth2PasswordBearer is used as Depends(oauth2_scheme) to get a token, and then a custom dependency to retrieve a user. This cleanly separates security logic from business logic.

  • Background tasks & external services: You can use dependencies to integrate services like caching or queues. For example, a dependency might push a message to a queue after the response is sent (though for sending after response, FastAPI’s BackgroundTasks might be more appropriate). But you could, say, have a dependency that checks a Redis cache for a value and either returns it (and your endpoint could short-circuit) or yields control to proceed to main logic. This can act like a caching layer.

In summary, dependency injection in FastAPI is a declarative way to manage concerns and share functionality. It keeps endpoint functions focused on the core logic by offloading tasks like auth, connecting to resources, etc., to separate functions that can be reused and tested in isolation.

4. Asynchronous endpoints (async/await for concurrency)

What it is: FastAPI is built to fully support asynchronous Python (the asyncio library). You can declare your path operation functions with async def, allowing them to perform non-blocking I/O operations concurrently. This means FastAPI can handle multiple requests on a single thread by switching contexts during await points (when I/O happens). For I/O-heavy applications, this dramatically improves throughput and latency compared to synchronous code. FastAPI’s use of the ASGI standard and Uvicorn’s event loop is what enables this.

Syntax and Usage:

To define an async endpoint, simply use async def:

import httpx

@app.get("/external-data")
async def get_external_data():
 async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com/data")  # await external call
data = resp.json()
 # We could do more awaits here (e.g., database calls) concurrently return {"external_data": data}

Important points:

  • Use await for I/O: Inside an async def function, any time you call an async operation (like an HTTP request using httpx.AsyncClient or a database query using an async driver or even asyncio.sleep), you must prefix it with await. This yields control back to the event loop so other tasks (requests) can run while waiting for I/O.

  • If you forget to await an async call, it won’t actually execute (you’ll get a warning or a runtime error).

  • Mixing async and sync: FastAPI allows both sync (def) and async endpoints. If you call blocking code (like a normal HTTP library or heavy computation) inside an async function without offloading it, you will block the event loop, harming concurrency. For blocking external calls, either use their async versions or run them in a threadpool (FastAPI provides run_in_threadpool utility behind the scenes for some standard library calls or you can use loop.run_in_executor).

  • Concurrent tasks: Within an async function, you can use asyncio features to perform tasks concurrently. For example, to fetch multiple URLs concurrently:

    @app.get("/multi-data")
    async def get_multi_data():
    urls = ["http://example.com/a", "http://example.com/b"]
     async with httpx.AsyncClient() as client:
    tasks = [client.get(url) for url in urls]
    responses = await asyncio.gather(*tasks)
     return [r.status_code for r in responses]

    Here both requests are made concurrently, and asyncio.gather awaited to get results. This can yield significant performance improvements when you have independent I/O tasks.

Common pitfalls & errors:

  • Using async where not needed: If your endpoint doesn’t do any awaitable I/O (for example, just CPU-bound work), making it async won’t improve throughput; in fact, running CPU code in the event loop can block other tasks. For CPU-bound tasks, consider running them in a separate thread or process (or use a background tasks feature).

  • Blocking inside async: Calling a regular blocking function (e.g., requests.get or heavy pandas operation) inside an async def will block the entire event loop. One common mistake is using an ORM like SQLAlchemy (synchronous) in an async endpoint. This will bottleneck. The fix is to use an async database library (like encode/databases or SQLAlchemy’s async engine in newer versions, or run the sync DB calls in threadpool via await sync_call() using starlette.concurrency.run_in_threadpool utility).

  • Awaiting non-async: If you try to await a normal function, Python will error. Conversely, forgetting to await an async function call leads to a coroutine that’s never executed (and likely a warning “coroutine was never awaited”). Always ensure every async def you call inside is awaited (or returned to be awaited by FastAPI if it’s a dependency or something – but in endpoints, you should await).

  • Thread-unsafe code: If you do mix sync code in threads, be careful with global state. The fact that FastAPI’s main loop is single-threaded (for async) is nice, but if you use threadpool for a blocking call, that code will run in a separate thread. Ensure any shared data is protected or use only thread-safe libraries. Usually not a big concern for typical DB or HTTP calls.

Performance and concurrency considerations:

  • Single vs multiple workers: By default, Uvicorn runs a single process. Async code lets that process handle many concurrent requests. However, it’s still bound by GIL for CPU tasks. For high load, you might still run multiple workers (processes) – e.g., using uvicorn --workers 4 or Gunicorn – to utilize multiple CPU cores. Each process will have its own event loop handling async tasks.

  • High I/O throughput: Async shines when you have lots of waiting (e.g., many slow external calls). For example, one FastAPI strength is calling other microservices or doing network calls concurrently. It can serve thousands of connections idling (like websockets or long polling) with minimal threads. TechEmpower benchmarks often show FastAPI (async) handling ~20k rps in simple scenarios, whereas Flask (sync) would need many threads or processes to approach that.

  • Latency: Async can also reduce latency under load. Because one request isn’t blocking the server, others don’t have to wait for it to finish if it’s waiting on I/O. This leads to more consistent response times under concurrent load.

  • Profiling async: Standard profilers might not fully capture async context. Tools like asyncio.run or using Py-Spy in async mode are helpful. Yappi is another profiler that works with multi-threaded and async code.

  • Don’t over-async: Not everything benefits from being async. For example, reading a small config file from disk at startup is fine to do sync. Database ORMs can be trickier – if an async ORM isn’t available, sometimes using sync in threadpool may be simpler. Use async primarily for networking/IO-bound tasks where library support exists.

Integration ideas:

  • Databases (async): FastAPI works great with async database libraries. E.g., databases library or Tortoise ORM allows you to await db.fetch_all() etc. If using an async DB, you can integrate it easily in async endpoints or in dependencies. The benefit is you don’t need extra threads for DB calls (unlike a sync ORM which would block).

  • External APIs: Many projects use FastAPI to create backend that gathers data from multiple external APIs (e.g., microservice aggregator or mash-up). With async, you can call all external APIs in parallel instead of sequentially, dramatically cutting down total response time.

  • WebSockets: FastAPI supports WebSocket endpoints via @app.websocket. Those must be async functions. You can await websocket.receive_text() and await websocket.send_text(). Async is essential for handling many websockets concurrently. This makes FastAPI suitable for real-time applications like chat servers, live dashboards, etc., often replacing older frameworks like Tornado in that area.

  • Background tasks: FastAPI has a BackgroundTasks feature to schedule tasks to run after sending a response. This is integrated with the async loop (tasks run in the same event loop after response). Use it to send emails or other follow-ups without delaying the response to the client. It’s not exactly multi-threading – background tasks run sequentially after response, but they can be async def as well. For heavier tasks, consider external task queues (Celery or RQ); FastAPI endpoints (async or not) can enqueue jobs to those.

  • Long-lived connections & streaming: Async allows efficient streaming of responses or request data. For example, streaming large file responses with StreamingResponse (which can be async generator) ensures you’re not tying up a thread for the whole duration. Similarly, reading a large request body in chunks (for example, an upload) can be done asynchronously. This is useful for integrating FastAPI in scenarios like video streaming or big file uploads/downloads.

In essence, use async def for endpoints that perform I/O-bound operations (database, HTTP calls, file I/O, sleeps) so that FastAPI can handle many such operations concurrently. For CPU-bound operations, consider alternative strategies (offload to thread/process or optimize the computation) rather than async. With proper use of async, FastAPI can scale to handle a large number of simultaneous requests efficiently on minimal threads, making it ideal for modern microservice workloads.

5. Automatic interactive documentation (OpenAPI/Swagger UI)

What it is: FastAPI automatically generates an OpenAPI specification (formerly known as Swagger) for your API, and provides interactive documentation UIs for it: Swagger UI and ReDoc. This is a core feature that greatly improves the developer experience. The docs are available by default at http://<your-host>/docs (Swagger UI) and /redoc (ReDoc). They allow you (and your API consumers) to visualize the API endpoints, models, and even execute calls from the browser.

How it works: FastAPI inspects all your path operations, their parameters, request bodies (Pydantic models), responses, and dependencies, and compiles an OpenAPI JSON schema. This schema is exposed at GET /openapi.json. The Swagger UI and ReDoc are static web apps that fetch this schema and render the documentation.

Key features of the docs UIs:

  • List of endpoints: All your routes are listed, grouped by tags if you use tags. Each endpoint shows the method (GET/POST etc.) and path, and an expandable section.

  • Parameters and schemas: When expanded, an endpoint shows its parameters (path, query, header, cookie) with descriptions, types, whether required, etc. If an endpoint expects a body (from a Pydantic model), the schema of that model is displayed (with field types, examples, any constraints like max length).

  • Try it out (Swagger UI): Swagger UI provides a “Try it out” feature where you can input parameter values and request body, then execute the request directly from the docs page. It will show you the response from the server (status code and JSON response or error). This is extremely useful for testing and exploration.

  • Example values and descriptions: You can document each field and endpoint. Pydantic models can include metadata like Field(..., description="..."), which FastAPI includes in the schema. You can also provide example payloads using example= in Field or a separate schema example. These appear in the docs to guide users.

Customization: FastAPI allows customizing the documentation in various ways:

  • You can add a title, description, version for the API (FastAPI app has those parameters). These show up on the docs page.

  • You can include your own API description in Markdown to be displayed on the docs homepage.

  • You can customize the Swagger UI or ReDoc (for instance, FastAPI allows you to serve the docs under a different URL or add your own branding if needed).

  • If certain endpoints should be hidden from docs (e.g., internal ones), you can include include_in_schema=False in the decorator to exclude them from documentation.

FAQ

  • Docs not showing or 404: By default, docs are enabled. If you set docs_url=None when creating FastAPI app, docs won’t be served. Also, ensure you are accessing the correct path (exactly /docs and not /docs/ – though a redirect is usually in place). If you mounted the app under a prefix, the docs may be at /prefix/docs.

  • ReDoc or Swagger not loading: This sometimes happens if you’re behind a proxy that blocks the CDN for the UI assets. By default, FastAPI uses CDN links for Swagger UI and ReDoc. If those are blocked, you can configure FastAPI to serve those assets locally (using app = FastAPI(swagger_ui_parameters={"useLocalAssets": True}) or similar config).

  • Large schemas: If you have very large schemas, the docs might become slower or cluttered. You can manage this with tags to group endpoints, or even disabling docs in extreme cases (and providing a separate static docs).

  • Security of docs: If your API requires auth, note that the docs UI itself doesn’t enforce auth to view the schema (it’s just a webpage). If your API is private, you might want to protect the docs endpoint (e.g., behind login or disable it in production). There was a Reddit thread about protecting Swagger UI from public access. You can do so by adding an @app.get("/docs") route that requires auth and serve docs only to logged-in users, or run API behind firewall. For many cases, having docs public is fine (it doesn’t expose anything not already in the API).

  • Updating docs: If you change something in code and you’re using --reload, the docs update automatically (because they’re generated each time). If not reloading, you’d need to restart to see changes in docs (like new models or descriptions).

Performance impact: The documentation is generated at startup and served statically (except the actual try-it-out requests, which call your API). The OpenAPI JSON is usually cached the first time. So the presence of docs doesn’t slow down your API except a negligible memory overhead for storing the schema. If your schema is huge (thousands of endpoints), generating it might add a bit to startup time.

Integration ideas:

  • Client code generation: Because FastAPI gives you the OpenAPI spec, you can use tools to generate API client libraries. For instance, you can feed the spec to OpenAPI Generator or Swagger Codegen to generate a TypeScript, Java, or Python client that has methods for each API endpoint. This is great for front-end teams or external API consumers. FastAPI’s adherence to OpenAPI means you have a contract for your API for free.

  • API gateways and monitoring: Many API gateway services or monitoring tools can ingest OpenAPI specs to set up routes or checks. You can point them to the /openapi.json of your FastAPI service.

  • Interactive exploration: During development, the docs UI is a wonderful integration point – you can try new endpoints as you build them. Even non-developers (QA, product managers) can hit the docs URL and play with the API if needed to understand it.

  • Documentation sites: You can export the OpenAPI spec and use it to create static documentation. Tools like ReDoc can be used as part of your documentation site. For example, you might host a static HTML that embeds ReDoc with your openapi spec, giving a clean documentation page for external developers without needing a running FastAPI instance. FastAPI’s own documentation site (for the API itself) could be generated from the spec if it were public.

  • Versioning and docs: If you version your API (say v1, v2), FastAPI can generate one schema that includes versioned paths, or you could have separate apps. It’s sometimes useful to host multiple docs (like /docs/v1, /docs/v2), which you can do by mounting sub-apps or customizing docs_url per app.

In summary, the automatic docs are a huge convenience and professionalism boost. They ensure your API is self-documenting and interactive, reducing the guesswork for anyone using your API (including yourself!). This feature often wows developers new to FastAPI, as getting a full Swagger UI with zero effort is a big time saver.

Advanced usage and optimization tips

FastAPI is suitable not just for simple apps but also for production-grade, high-performance services. In this section, we explore advanced topics and best practices to optimize your FastAPI application. This includes memory management, speed optimization, caching, profiling, testing, deployment, and production best practices.

Memory management and efficient resource usage

FastAPI itself is lightweight, but your app’s memory profile depends on how you use it:

  • Scope of objects: Be mindful of objects that persist between requests vs. those created per request. For example, Pydantic models are created per request (which is fine), but large data structures loaded globally will reside in memory permanently. If you have large datasets, consider loading on demand or using streaming.

  • Streaming responses: If you need to send large responses (files, big JSON), consider using StreamingResponse or generator functions to stream data instead of loading everything into memory. For instance, reading a big file in chunks and yielding it will use constant memory instead of reading whole file into a bytes object.

  • Request body size: Similarly, if clients upload large files, use UploadFile which streams data to disk (temporary file) instead of reading entire file into memory as bytes. UploadFile gives you a file-like object to .read() in chunks.

  • Avoiding memory leaks: Use dependencies or context managers to ensure resources are freed. For example, if you open a database connection per request, make sure it’s closed (the yield-style dependency helps here). Python generally cleans up, but be careful with global lists or caches that grow without bounds. If you implement caching, use eviction policies (LRU cache or time-based).

  • Async and memory: Each request in FastAPI (async) shares the same process memory. Ensure your code is thread-safe if you use any global mutable state (because although async is single-threaded by default, if you run multiple workers or background threads, it matters). Using locks or avoiding global state altogether is best. Also, when doing async tasks, if you accumulate a lot of data concurrently, memory can spike. For example, gathering 100k database rows concurrently might hold many results in memory at once – consider processing streams incrementally.

  • Use of yield dependencies for cleanup: As mentioned, dependencies can use yield to guarantee cleanup code runs. Use this to free any large objects or close connections promptly after each request to avoid lingering memory usage.

  • Object reuse: If you have expensive objects (like a machine learning model), load them once at startup and reuse. For example, load a ML model globally and have dependencies that provide it (not reloading it each time). This saves memory compared to reloading per request, at the cost that the global object persists (which is usually fine if it’s needed). Just be conscious of the trade-off (you don’t want dozens of copies of the same model in memory).

Speed optimization techniques

FastAPI is fast by design, but here are ways to squeeze more performance:

  • Use async effectively: As discussed, use async endpoints for I/O-bound operations so you can serve many requests concurrently. Ensure external calls (HTTP, DB) use async libraries so they don’t block. This maximizes throughput for high-latency operations.

  • Use uvicorn[standard]: This installs uvloop (a faster event loop) and other optimizations. Uvicorn with uvloop can significantly improve performance under load. Check Uvicorn logs to ensure uvloop is being used (it should if installed on Unix systems).

  • Gunicorn workers: For CPU-bound tasks or generally utilizing multi-cores, run multiple worker processes. E.g., if you have 4 CPU cores, 4 Uvicorn workers can handle CPU tasks in parallel. Use a process manager like Gunicorn to manage them. However, don’t spawn more workers than you have cores (usually).

  • Caching strategies: Introduce caching to avoid redundant computations or external calls:

    • In-Memory Cache: For quick wins, you can use Python’s functools.lru_cache decorator on dependency functions or any function that given the same input returns the same output. For example, caching results of a static file read or a config fetch so subsequent calls are instant. Be cautious with memory use though (limit maxsize).

    • Distributed cache: For heavier caching needs (like caching API responses or database queries), consider an external cache like Redis. FastAPI doesn’t have built-in caching, but you can easily integrate by using aioredis or similar. For example, in a dependency, check Redis for a cached result before hitting a slow backend. If found, return it, otherwise fetch and store in cache with a TTL. This can drastically reduce response times for repeated requests (e.g., caching expensive computations or rarely changing data).

    • Cache invalidation: If caching, plan how to invalidate stale data. Use TTLs or explicit cache clears on data update endpoints (e.g., after posting a new item, clear relevant cache entries). FastAPI doesn’t automate this, so design it in your logic..

    • Client-side caching: Leverage HTTP caching headers if appropriate (Cache-Control, ETag), so that clients or intermediaries can cache responses. FastAPI allows adding custom headers easily (via Response object or middleware).

  • Optimize database access: If your app uses a database, typical performance tips apply:

    • Use indexing in the database, avoid N+1 query patterns (use ORM lazy loading carefully or fetch related data in one go).

    • For heavy read loads, consider adding a caching layer or read replicas.

    • Use connection pooling (most DB libs do this). If using sync DB with multiple threads/workers, ensure pool size is configured.

    • If using async DB, use drivers like asyncpg for Postgres which are faster.

    • Profile slow queries and optimize them (this goes beyond FastAPI but is critical for speed).

  • Profile and identify bottlenecks: Utilize profiling tools to find where time is spent:

    • Use cProfile in a test client call to see where CPU time is going.

    • For async, try Py-Spy or record a run and use tools to visualize event loop waits. Often, your bottleneck might not be FastAPI itself but your own code or external services.

    • Logging middleware can measure request processing times to identify slow endpoints.

  • Compile Pydantic models (if using v1): If using Pydantic v1 (older FastAPI), there was an option to use Cython for speed. Pydantic v2 however is already quite optimized (written in Rust), so just use the latest versions for best performance.

  • Orjson for JSON serialization: Pydantic v2 by default uses json or orjson if installed. orjson is significantly faster for large JSON dumps. You can switch response class to ORJSONResponse globally to use it. Make sure clients can handle slight differences (orjson returns bytes, but FastAPI’s ORJSONResponse handles it).

  • Batch requests: If appropriate, allow clients to batch operations. For example, an endpoint that fetches 10 users given 10 IDs in one request is more efficient than 10 separate requests. This moves the loop overhead from network to server which can often handle it better. Design your API to support fetching or updating in bulk when it makes sense (this is an app-level design optimization).

Profiling and monitoring

Building on profiling: continuous monitoring in production is crucial:

  • Use APM tools or logging: Integrate application performance monitoring (APM) tools like NewRelic, Datadog, Sentry Performance, etc., which can instrument FastAPI (often via Starlette middlewares or OpenTelemetry). They provide metrics on response times, throughput, error rates.

  • Logging: FastAPI/Uvicorn logs every request by default (in development). In production, consider adding structured logging (JSON logs with details) and include things like request ID, user ID (if available) for tracing issues. Logging can be done via standard logging module or using dependencies/middleware to log at start and end of requests.

  • Profiling in testing: For deeper analysis, you can simulate load with tools like Locust or JMeter and observe resource usage (CPU, memory) and performance. Adjust your strategies accordingly (increase workers, add caching, etc., based on results).

  • Yappi for Async profiling: As noted, Yappi can profile multithreaded or async apps effectively. You can run Yappi in a running server to collect stats and then analyze which coroutines or functions are taking the most time.

  • Identify bottlenecks: Use the data gathered to pinpoint slow spots. Is it external calls, DB queries, serialization of huge responses, etc.? Each has a targeted solution (improving that external service, adding caching, optimizing query or adding indices, splitting huge responses into paginated smaller ones, etc.).

Testing FastAPI applications

Testing is essential and FastAPI makes it straightforward:

  • Use FastAPI’s TestClient: FastAPI provides a TestClient based on Starlette’s TestClient, which allows you to call your API endpoints in tests as if from a client. Example:

    from fastapi.testclient import TestClient
    from myapp import app

    client = TestClient(app)

    def test_create_item():
    response = client.post("/items/", json={"name": "Test", "price": 5.0})
     assert response.status_code == 201
    data = response.json()
     assert data["item"]["name"] == "Test" 

    This spins up the app in test mode and you can call endpoints without actually running a server.

  • Dependency overrides in tests: As mentioned, you can override dependencies in tests to isolate components. E.g., override get_db to use a test database or override get_current_user to avoid real auth. Do:

    app.dependency_overrides[get_db] = lambda: Session(test_engine)  # example 

    And then TestClient calls will use the overridden dependency.

  • Factories and fixtures: Use pytest fixtures to set up state (like creating tables in a test DB, populating initial data, etc.). You might spin up a Docker container for a test database or use an in-memory SQLite for simplicity (if your app logic allows).

  • Test performance (if needed): You can write tests to ensure certain endpoints respond within expected time (maybe not typical unit tests, but performance tests separate from CI). Or test that caching works by calling twice and measuring time difference.

  • Stress Testing Separately: Use tools for load testing outside of unit tests to ensure your service holds up under load and that optimizations are effective.

Deployment and production best practices

When deploying a FastAPI app, consider the following:

  • Use a production server: Uvicorn is production-ready, but usually you run it behind a process manager like Gunicorn (with Uvicorn workers) or something like Uvicorn’s built-in multiple workers mode. Example using Gunicorn (with 4 workers):

    gunicorn -k uvicorn.workers.UvicornWorker -w 4 -b 0.0.0.0:8000 myapp:app

    This combination is common. It gives you multi-process concurrency and graceful restarts, etc.

  • Reverse Proxy (Nginx/Traefik): It’s often advisable to put a reverse proxy in front of your Uvicorn app. Nginx or Traefik can handle TLS termination, gzip compression, buffering, and serving static files more efficiently. They can also manage multiple services on one host.

  • Environment variables for config: Use Pydantic’s BaseSettings to define app configuration that reads from env vars (like DB URL, secrets). FastAPI can integrate this easily (there’s a Settings example in docs. This aligns with Twelve-Factor App methodology and makes switching configs in different environments easier.

  • Security best practices: In production:

    • Enable CORS only as needed. Use CORSMiddleware to restrict origins if your API is not public or only certain domains should call it.

    • Use HTTPS (either via proxy or directly with an ASGI server that supports TLS).

    • If applicable, implement auth on endpoints (FastAPI provides OAuth2 flows, etc.) and protect sensitive data.

    • Keep dependencies up to date for security patches (e.g., if a vulnerability is found in Starlette or Pydantic, update FastAPI accordingly).

    • Consider rate limiting (can be done via dependency or externally via API gateway) to prevent abuse.

    • If you have an open docs endpoint in production, consider whether that’s okay or if you want it behind auth to avoid giving too much info to the public (depending on your use case).

  • Connection pooling: If using Gunicorn with multiple workers, remember each worker has its own connections. Ensure your database can handle that many (e.g., if each opens pool of 5, and 4 workers, that’s 20 connections).

  • Auto-scaling: If deploying on cloud (Kubernetes, AWS ECS, etc.), containerize the app (we covered Docker earlier). Monitor CPU/memory usage, and scale out by running more instances under a load balancer if needed. FastAPI apps, being stateless, work well behind load balancers.

  • Logging and monitoring: Use logging as discussed, and set up monitoring. Uvicorn logs can be piped to logging system. You might also instrument with Prometheus (there’s Starlette middleware for metrics) to gather metrics on requests, latency, etc., for alerts.

  • Startup and shutdown events: FastAPI lets you define event handlers for app startup and shutdown (via @app.on_event("startup")). Use these to initialize things (e.g., warm up a model, connect to DB to verify, schedule recurring tasks if any). Similarly, ensure graceful shutdown closes DB connections, etc., to avoid corruption.

  • Testing in prod-like environment: Always test your container or deployment configuration in a staging environment. Sometimes things like number of workers or environment variable wiring can cause issues that won’t appear in dev.

By following these best practices and utilizing FastAPI’s features, you can build an application that is not only fast and efficient in development, but also robust, scalable, and maintainable in production. Many of these tips are illustrated by real-world FastAPI deployments, which we will look at next.

(The table above provides a snapshot; actual performance can vary with specific workloads. “Requests per second” mentioned are for simple scenarios from benchmarksbetterstack.combetterstack.com, intended for relative comparison.)

As seen, FastAPI offers a blend of high performance, modern features, and an easier learning curve compared to heavy frameworks, making it a top choice for APIs in 2025. Flask remains a go-to for small apps or as a beginner’s introduction, but it lacks FastAPI’s async and built-in validation. Django is unparalleled for complete web applications with a lot of moving parts (if you need an ORM or templating engine out of the box), but for pure API development it can be overkill and slower. Tornado served its purpose for async needs in the past, but now frameworks like FastAPI (built on asyncio) typically offer better performance and ergonomics, so Tornado is less common unless working in a legacy Tornado codebase or specific edge cases.

Migration Guide: Moving to FastAPI

If you have an existing application in Flask, Django, or Tornado, here are guidelines for migrating to FastAPI:

Migrating from Flask to FastAPI:

  • Routing: In Flask you use @app.route with methods; in FastAPI you’ll use @app.get, @app.post, etc. The conversion is straightforward. For example, a Flask route:

    @app.route('/users/<int:user_id>', methods=['GET'])
    def get_user(user_id): ...

    becomes FastAPI:

    @app.get('/users/{user_id}')
    def get_user(user_id: int): ...

    (Add type hints for parameters for better validation).

  • Request Data: Flask uses request.form or request.get_json(). In FastAPI, define function args for query params or body. E.g., instead of request.args['name'], use def endpoint(name: str). For JSON body, define a Pydantic model and use it as a parameter. This is a shift: you'll create classes for your data (which yields more robust code).

  • Globals/Context: Flask’s g and app.context_processor things can be replaced by dependencies in FastAPI. E.g., if you had g.db for a database, create a get_db() dependency that yields a session and use Depends(get_db). Similarly, Flask’s before_request functions can be translated to dependencies that run for each request or middleware.

  • Extensions: Find FastAPI equivalents or alternative approaches:

    • Flask-Login (user session management) doesn’t directly port because FastAPI is stateless by default. You’d implement auth using OAuth2 (JWT or OAuth flows) or use Starlette’s sessions if needed. If session cookies are needed, Starlette’s middleware can handle server-side sessions similar to Flask’s.

    • Flask-WTF (forms) -> you would handle forms either by reading form data via Form(...) dependencies or move logic to the frontend. FastAPI can parse form fields with Form(...) in the signature.

    • Flask-Mail, Flask-Anything – these extensions typically just wrap other libs. In FastAPI, you’d use the underlying library (like an SMTP client) directly, possibly in a background task for sending mail.

  • Templates: If you used Flask’s Jinja2 templating, you can use Starlette’s Jinja2Templates. FastAPI can render templates by returning templates.TemplateResponse("template.html", {"request": request, "data": data}). So you can still serve HTML if needed.

  • Deployment: If you were using Flask’s built-in dev server, switch to Uvicorn for dev, and something like Gunicorn+Uvicorn for production. The concept of running behind WSGI (Flask) changes to ASGI, but if deploying via containers or similar, it’s just a different command (uvicorn main:app). Many hosting services now support ASGI out-of-the-box.

Flask to FastAPI is usually straightforward; you mostly need to introduce Pydantic models for structured data and use dependency injection for things like database connections or auth instead of global Flask app context.

Migrating from Django/Django REST Framework to FastAPI:

  • Project structure: Django projects have apps, urls.py, models.py, etc. FastAPI is much more flexible. You can organize by modules similarly (maybe have a routers folder for different endpoints, analogous to urls include). Use APIRouter in FastAPI to mirror Django's app url includes for modular design.

  • Models: If you have Django ORM models, you can continue to use them in FastAPI (Django’s ORM can be used outside of Django if you configure settings). Or you might migrate to an alternative like SQLAlchemy or Tortoise. If keeping Django ORM, you’ll need to initialize Django in FastAPI (set up settings and call django.setup() before running app). A simpler path might be to gradually replace ORM usage with something like SQLAlchemy which integrates more naturally with FastAPI.

  • Serializers -> Pydantic: In DRF, serializers validate and convert data. In FastAPI, Pydantic models fulfill this role. Translate your DRF Serializer classes to Pydantic models (fields with types and validators). Many field options (max_length, etc.) have equivalents in Pydantic Field.

  • Views -> Endpoint functions: DRF’s APIView or ViewSet classes would become simpler function definitions (or you can use classes if desired, but not required). For example, a DRF ViewSet action retrieve becomes a FastAPI function on a router with GET.

  • Auth: Django’s auth (session or token) could be replaced with FastAPI’s OAuth2 JWT approach. Alternatively, if you still have a Django app, you could even mount the Django app inside FastAPI using WSGIMiddleware to reuse Django features (FastAPI docs demonstrate mounting a Flask or Django app under a certain path). This could help migrate incrementally: for example, keep the Django admin and some pages, but serve new API endpoints via FastAPI in the same server.

  • Middlewares: Django middleware like CSRF or security middleware will not apply in FastAPI. You need to implement or use Starlette Middleware for similar functions (e.g., Starlette has a CORSMiddleware, etc.). Many concerns like CSRF may not apply if you’re using JWT auth for APIs. Be aware of differences (Django autoescapes templates, etc., whereas Jinja in FastAPI will too by default – that’s similar).

  • WebSockets: Something that might excite Django developers: FastAPI handles websockets easily. Django (outside of Channels) doesn’t natively. If your app needs realtime features, migrating to FastAPI will simplify that.

Migrating a full Django app is a bigger effort because of the integrated components. Often, teams choose to create new microservices in FastAPI for new functionality while leaving the Django monolith for what it does well, gradually shifting features over. FastAPI’s mounting capability (mounting WSGI apps) can facilitate a transition where both run side by side under one domain while you migrate routes one at a time.

Migrating from Tornado to FastAPI:

  • Async code: Tornado’s async code can be refactored to async def easily since Tornado had its own coroutine patterns or used gen.coroutine. The logic mostly carries over. Instead of Tornado’s self.write() in a RequestHandler, you return data from an endpoint function. WebSocket handlers in Tornado (which use on_message callbacks) become simpler in FastAPI by using @app.websocket and await websocket.receive_text(), etc.

  • Remove Tornado-specifics: Tornado isn’t compatible with ASGI, so you’ll fully replace the web server part. But any library usage (like motor for MongoDB async, etc.) can remain since it’s async and will work in FastAPI.

  • Routing: Tornado’s routing table (which might be in code adding Handlers) will be replaced by FastAPI’s decorator routes.

  • Template and static: Tornado has its template system and static file serving. In FastAPI you’d use Jinja2 (or even reuse Tornado’s template engine if desired by calling it manually, but typically Jinja2 is used) and StaticFiles middleware for serving static.

  • Performance gains: Expect better request throughput after migrating to FastAPI/Uvicorn, as Tornado’s performance for pure HTTP isn’t as optimized. Also, the developer experience improves with automatic docs and simpler syntax.

General migration tips:

  • You can incrementally migrate by running FastAPI alongside the old framework. For example, you can mount a Flask app inside FastAPI (or vice versa) temporarily, or run them on different ports and use a reverse proxy to route certain paths to the new service. This allows gradual transition rather than a big bang.

  • Understand that FastAPI’s philosophy is more explicit: you’ll declare schema and dependency up front. This might initially seem like more work than a quick Flask route, but it pays off in maintainability and error reductionf.

  • Plan and test thoroughly. Write tests in the old app (if not already) and ensure the new FastAPI app passes the same tests (especially for a behavior-centric migration).

  • Consider training the team on FastAPI basics (especially Pydantic and dependency injection) so they can embrace the new patterns. Often, after an initial learning curve, developers appreciate the clarity and power.

Migrating to FastAPI can modernize your stack, improve performance and developer efficiency, and align your project with current and future Python web development trends. Many developers report that after moving to FastAPI, the speed to add new features increased by 2-3x due to the reduction in boilerplate and helpful tooling. Given its growing ecosystem and support, it’s a solid step for projects that need the advantages discussed throughout this guide.

Resources and further reading

To continue learning FastAPI and get help when needed, check out the following resources:

  • Official documentation: The FastAPI docs (https://fastapi.tiangolo.com) are the best starting point – they include a tutorial, advanced topics, and recipesfastapi.tiangolo.com. The Tutorial - User Guide section is highly recommended for step-by-step learning, and the Advanced User Guide covers deeper features.

  • Official GitHub repository: The source code and issue tracker are on GitHub at https://github.com/tiangolo/fastapi. It’s a good place to see examples, report issues, or read discussions. (FastAPI’s GitHub is very active, reflecting its vibrant community).

  • FastAPI people and external articles: The FastAPI site lists external links and community articlesfastapi.tiangolo.comfastapi.tiangolo.com. This includes blog posts, videos, and tutorials by the community – a great way to see how others are using FastAPI. For instance, the FastAPI tag on Medium and Dev.to often has updated tutorials (search for “FastAPI 2024” etc. for latest).

  • Stack Overflow (Q&A): There are thousands of questions on Stack Overflow tagged [fastapi]. Many common issues have been asked and answered. If you encounter an error, odds are someone asked about it – search there. Examples: “FastAPI CORS issue”stackoverflow.com or “FastAPI background tasks not running” – likely solutions exist on SO.

  • FastAPI discord & forums: FastAPI has an official Discord server for live discussionfastapi.tiangolo.com. You can join to ask quick questions or discuss best practices. There’s also a discussions tab on the GitHub repo where the community and maintainers answer conceptual questions.

  • GitHub Examples and Boilerplates: Check out the FastAPI GitHub organization and other repositories:

    • Full Stack FastAPI template by the creator (Sebastián Ramírez) is a full-stack project generator with FastAPI (including React front-end, Docker, etc.). It’s a great reference for structuring larger projects.

    • Awesome FastAPI (an “awesome-list” on GitHub)reddit.com – a curated list of FastAPI resources, including open-source projects using FastAPI, extensions, and libraries.

  • Books and courses: As FastAPI grows, so do formal learning materials:

    • “High Performance Web Apps with FastAPI” by Malhar Lathkar (2023) – covers using FastAPI and Pydantic for building APIsreddit.com.

    • “FastAPI: Modern Python Web Development” by Bill Lubanovic (O’Reilly, 2022) – provides a comprehensive guide through FastAPI featuresoreilly.com.

    • Online courses on Udemy or others: e.g., “FastAPI Complete Course” (Udemy, 2025) covering building and deploying FastAPI appsudemy.com.

      Ensure books or courses cover FastAPI with Python 3.9+ and Pydantic v2 updates (if they are recent).

  • YouTube tutorials and talks: Many conference talks and YouTube tutorials are available:

    • “Introduction to FastAPI” videos – often walking through building a simple API.

    • PyCon and other conference talks by Sebastián Ramírez (creator) – e.g., “FastAPI from the ground up”.

    • TestDriven.io has a great Moving from Flask to FastAPI guidetestdriven.io, and other blog series that can be very insightful.

  • Community Forums/Reddit: The subreddit r/FastAPI is small but growing, where people share tips or ask questionsreddit.com. Also, broader communities like r/Python or r/learnpython often discuss FastAPI. Keep an eye on those for interesting use cases or problems and solutions.

  • Starlette & Pydantic docs: Since FastAPI builds on Starlette and Pydantic, it can help to reference those docs for advanced use. For example, Starlette’s middleware and test client docsfastapi.tiangolo.com, or Pydantic’s docs for complex model validation (like exotic types, custom validators, etc.)en.wikipedia.org.

  • Deployment guides: Many cloud providers have guides for FastAPI:

    • e.g., Deploying FastAPI on AWS Lambda with API Gateway (using Mangum adapter),

    • Deploying on Google Cloud Run, Heroku, etc. A quick web search often yields step-by-step tutorials as FastAPI has become a common choice.

  • Performance benchmarking: If interested in performance details, see the Benchmarks page on FastAPI’s sitefastapi.tiangolo.com which links to TechEmpower benchmarksfastapi.tiangolo.com, and the GitHub issue discussions like “FastAPI performance vs X” where community discusses tuning.

Katerina Hynkova

Blog

Illustrative image for blog post

Ultimate guide to huggingface_hub library in Python

By Katerina Hynkova

Updated on August 22, 2025

That’s it, time to try Deepnote

Get started – it’s free
Book a demo

Footer

Solutions

  • Notebook
  • Data apps
  • Machine learning
  • Data teams

Product

Company

Comparisons

Resources

Footer

  • Privacy
  • Terms

© 2025 Deepnote. All rights reserved.