FastAPI Tutorial: Build a Typed Python API

FastAPI turns Python type hints into a fast, self-documenting web API. This guide takes you from install to a working, validated endpoint.

Get started - freeLive cloud workspace in your browser · nothing to install
#fastapi#python#backend#api#pydantic

What is FastAPI?

FastAPI is a modern Python web framework for building HTTP APIs. It sits on top of two well-established libraries: Starlette, which provides the ASGI web toolkit (routing, requests, responses, background tasks), and Pydantic, which provides data validation and serialization driven by Python type hints. The headline idea is that you describe your inputs and outputs once, using ordinary type annotations, and FastAPI handles parsing, validation, error responses, and documentation for you.

That design has a few concrete consequences. Because every parameter and model is typed, your editor gives you real autocompletion and type checking inside route handlers. Because the framework already knows the shape of each request and response, it can generate an OpenAPI schema and serve interactive documentation without any extra work. And because it runs on ASGI, you can write asynchronous handlers that handle many concurrent connections efficiently when your workload is I/O bound, such as calling other services or a database.

How FastAPI works (key concepts)

A FastAPI application is an instance of the FastAPI class. You attach route handlers to it with path-operation decorators like @app.get("/") or @app.post("/items"). Each handler is a normal Python function whose parameters tell FastAPI where data comes from.

The rules are based on the parameter's type and where its name appears:

If validation fails, FastAPI returns a structured 422 Unprocessable Entity response describing exactly which field was wrong, so you do not write that error handling yourself. The return value of a handler is serialized to JSON; if you set a response_model, FastAPI also filters and validates the outgoing data against that model.

Dependency injection is the other core concept. The Depends mechanism lets you declare reusable pieces of logic - database sessions, authentication, shared query parameters - as functions, and FastAPI resolves and caches them per request. This keeps handlers small and makes shared behavior testable in isolation.

Installing FastAPI

FastAPI needs a currently supported Python 3 release (check the FastAPI docs for the exact minimum). The recommended way to install it for development is the standard extra, which pulls in the Uvicorn ASGI server and the fastapi command-line tool along with common optional dependencies:

python3 -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"

Create a file named main.py, then start the development server with the bundled CLI. The fastapi dev command runs Uvicorn with auto-reload enabled, so edits restart the server automatically:

fastapi dev main.py

By default the app is served at http://127.0.0.1:8000, the interactive Swagger UI is at http://127.0.0.1:8000/docs, and the ReDoc view is at http://127.0.0.1:8000/redoc. For production you would instead run fastapi run, or invoke Uvicorn directly with multiple workers. If you only want the framework without the extras, pip install fastapi works too, but then you must install and run an ASGI server such as Uvicorn yourself.

Build a validated CRUD endpoint

Here is a small but complete example: a typed model, a create route that validates its body, and a read route with a path parameter and an optional query parameter.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI()

class Item(BaseModel):
    name: str = Field(min_length=1)
    price: float = Field(gt=0)
    in_stock: bool = True

# In-memory store for the example
items: dict[int, Item] = {}

@app.post("/items/{item_id}", response_model=Item, status_code=201)
def create_item(item_id: int, item: Item):
    if item_id in items:
        raise HTTPException(status_code=409, detail="Item already exists")
    items[item_id] = item
    return item

@app.get("/items/{item_id}", response_model=Item)
def read_item(item_id: int, include_price: bool = True):
    if item_id not in items:
        raise HTTPException(status_code=404, detail="Item not found")
    stored = items[item_id]
    if not include_price:
        return stored.model_copy(update={"price": 0})
    return stored

A few things are happening automatically. The Item model enforces that name is non-empty and price is greater than zero; a POST body that violates either rule gets a 422 with a clear message. The item_id path parameter is coerced to int, and a non-numeric value is rejected before your code runs. The include_price query parameter defaults to True because it has a default value and is not part of the path. Raising HTTPException produces the right status code and JSON error body. Setting response_model=Item guarantees the response matches the schema, which also documents the output in /docs.

For workloads that wait on I/O, switch the handler to async def and await an async client. The dependency system pairs well with this - a single dependency can open and close a database session around each request without cluttering every route.

When to use FastAPI

FastAPI is a strong default for new Python backends that expose JSON or HTTP APIs: REST services, microservices, internal tools, and the serving layer in front of machine-learning models. It works well when you value strict request validation, automatic documentation, and async concurrency for I/O-bound work. The type-driven approach also helps more as a codebase grows, because the contracts are explicit and checkable.

It is a weaker fit when you need a batteries-included, server-rendered web application with an admin panel, ORM, and templating built in - Django covers that ground better. For tiny scripts or a single short-lived endpoint, a minimal Flask app may feel lighter. And if your workload is purely CPU bound, async handlers give little benefit; you would scale with processes or move heavy computation off the request path regardless of framework. For the common case of a typed, well-documented API that has to handle real traffic, FastAPI is one of the best choices in Python.

Frequently asked questions

What is FastAPI?

FastAPI is a modern Python web framework for building APIs on top of Starlette and Pydantic. It uses standard Python type hints to validate requests, serialize responses, and generate interactive OpenAPI documentation automatically. It supports both async and sync route handlers.

Is FastAPI faster than Flask or Django?

FastAPI is built on Starlette and an ASGI server, so it handles concurrent I/O-bound work well using async def handlers. For raw throughput it generally outperforms a default Flask or Django WSGI setup. The bigger practical win is built-in validation and docs, which remove a lot of boilerplate.

Do I need to write async code to use FastAPI?

No. You can define route handlers with plain def and FastAPI runs them in a threadpool. Use async def when you call libraries that support awaiting, such as httpx or an async database driver. Mixing both is fine within the same app.

How does FastAPI generate API documentation?

FastAPI builds an OpenAPI schema from your route signatures and Pydantic models, then serves interactive docs at /docs (Swagger UI) and /redoc (ReDoc). The raw schema is available at /openapi.json. No extra annotations are required beyond your type hints.

Do real production work, free. Live cloud workspaces in your browser - every project you complete lands on your portfolio.

Get started - free
Get started - free