{heroAlt}

How to Build a REST API with FastAPI: A Step-by-Step Guide


In the world of web development, speed and efficiency are paramount. Developers are constantly searching for tools that streamline the creation of robust, scalable, and high-performance applications. Enter FastAPI, a modern Python web framework that has taken the community by storm. It’s designed to be incredibly fast, easy to use, and perfect for building REST APIs, making it a go-to choice for companies like Netflix, Microsoft, and Uber.

This guide will walk you through everything you need to know to build your first REST API with FastAPI. We’ll start with the basics, from setting up your environment to creating your first endpoint, and then dive into building a complete Create, Read, Update, Delete (CRUD) API with powerful data validation.

Why Choose FastAPI for Your Next API?

Before we dive into the code, it’s essential to understand what makes FastAPI stand out from other Python frameworks like Flask or Django. Its advantages are not just incremental; they represent a significant leap forward in API development.

Blazing Fast Performance

FastAPI lives up to its name. Built on top of Starlette (for the web parts) and Pydantic (for the data parts), it’s one of the fastest Python frameworks available. It leverages modern Python features like async and await to handle concurrent requests efficiently, allowing you to build highly responsive applications. In fact, FastAPI consistently ranks among the top performers in the TechEmpower Web Framework Benchmarks, often rivaling frameworks in compiled languages like Go and Rust (TechEmpower, 2023).

Automatic Interactive Documentation

One of FastAPI’s most beloved features is its automatic, interactive API documentation. Out of the box, it generates a user interface based on the OpenAPI Specification (formerly Swagger) and ReDoc. This means you get a fully interactive API documentation page where you can test your endpoints directly from the browser without writing a single line of configuration. This feature drastically accelerates development, testing, and collaboration with frontend teams.

Intuitive and Modern with Pydantic

FastAPI uses Python type hints for defining your data models and request bodies. This isn’t just for show; it powers FastAPI’s data validation, serialization, and documentation. By using Pydantic, you can define complex data structures with specific types, and FastAPI will automatically validate incoming data, returning clear, informative errors if the data doesn’t match the schema. According to the Postman 2023 State of the API Report, ensuring API quality and reliability is a top priority for developers, and robust data validation is a cornerstone of that goal.

Setting Up Your Development Environment

Getting started with FastAPI is straightforward. All you need is a recent version of Python and its package installer, pip.

Prerequisites

Ensure you have Python 3.7+ installed on your system. You can check your version by running:

python --version

Installation

First, it’s a best practice to create a virtual environment to keep your project dependencies isolated.

# Create a virtual environment
python -m venv venv

# Activate it (on macOS/Linux)
source venv/bin/activate

# Or on Windows
.\venv\Scripts\activate

Next, install FastAPI and an ASGI server to run it, such as Uvicorn. The standard option for uvicorn includes helpful dependencies.

pip install fastapi "uvicorn[standard]"

That’s it! You now have everything you need to build and run a FastAPI application.

Building Your First FastAPI Application

Let’s start with the classic “Hello World” example to see how simple it is to get an API endpoint up and running.

The “Hello World” Endpoint

Create a file named main.py and add the following code:

from fastapi import FastAPI

# Create an instance of the FastAPI class
app = FastAPI()

# Define a path operation decorator
@app.get("/")
def read_root():
    # This function will be called when a GET request is made to the root URL
    return {"Hello": "World"}

Let’s break this down:

  1. from fastapi import FastAPI: We import the main class we need from the library.
  2. app = FastAPI(): We create an instance of this class, which will be our main point of interaction for creating the API.
  3. @app.get("/"): This is a path operation decorator. It tells FastAPI that the function below is responsible for handling GET requests to the URL /.
  4. def read_root(): ...: This is our path operation function. FastAPI will call this function whenever it receives a GET request to the specified path. The dictionary it returns is automatically converted to a JSON response.

To run this application, go to your terminal and execute:

uvicorn main:app --reload
  • main: refers to the main.py file.
  • app: refers to the app = FastAPI() object inside the file.
  • --reload: makes the server restart automatically after you make code changes.

Now, open your browser and navigate to http://127.0.0.1:8000. You should see the JSON response: {"Hello":"World"}. More impressively, navigate to http://127.0.0.1:8000/docs to see the interactive Swagger UI documentation.

Data Validation and Modeling with Pydantic

This is where FastAPI truly shines. Let’s create an endpoint that accepts data in a request body and validates it using a Pydantic model.

Creating a Pydantic Model

First, we need to import BaseModel from Pydantic. Then, we can define our data model as a class that inherits from it.

Update main.py:

from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

@app.post("/items/")
async def create_item(item: Item):
    return item

Here, the Item class defines the expected structure of our request body. name and price are required fields, while description and tax are optional. Pydantic ensures that name is a string, price is a float, and so on.

Using the Model in a POST Request

In the create_item function, we’ve type-hinted the item parameter with our Item model. With this single line, FastAPI does all the work:

  1. Reads the body of the request as JSON.
  2. Converts the types to match the model (e.g., string to string, number to float).
  3. Validates the data. If any required fields are missing or have the wrong type, it returns a detailed 422 Unprocessable Entity error response.
  4. Passes the validated data as the item argument.

If you run the app and go to the docs, you can now test the POST /items/ endpoint with a sample JSON payload.

Building a Complete CRUD API

Let’s expand our application to perform all four CRUD operations. We’ll use a simple Python dictionary as an in-memory database.

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional, List

app = FastAPI()

# --- Pydantic Models ---
class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None

class UpdateItem(BaseModel):
    name: Optional[str] = None
    description: Optional[str] = None
    price: Optional[float] = None
    tax: Optional[float] = None

# --- In-Memory Database ---
db = {}

# --- API Endpoints ---

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

@app.get("/items/{item_id}", response_model=Item)
async def read_item(item_id: int):
    if item_id not in db:
        raise HTTPException(status_code=404, detail="Item not found")
    return db[item_id]

@app.get("/items/", response_model=List[Item])
async def read_all_items():
    return list(db.values())

@app.put("/items/{item_id}", response_model=Item)
async def update_item(item_id: int, item: UpdateItem):
    if item_id not in db:
        raise HTTPException(status_code=404, detail="Item not found")
    
    stored_item_data = db[item_id].dict()
    update_data = item.dict(exclude_unset=True)
    updated_item = stored_item_data.copy()
    updated_item.update(update_data)
    db[item_id] = Item(**updated_item)
    return db[item_id]

@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
    if item_id not in db:
        raise HTTPException(status_code=404, detail="Item not found")
    del db[item_id]
    return {"message": "Item deleted successfully"}

In this complete example, we have:

  • Create (POST /items/): Adds a new item to our db dictionary.
  • Read (GET /items/{item_id}): Retrieves a single item by its ID. It uses HTTPException to return a 404 error if the item doesn’t exist.
  • Read All (GET /items/): Returns a list of all items in the database.
  • Update (PUT /items/{item_id}): Updates an existing item’s fields. We use a separate UpdateItem model with all optional fields for this.
  • Delete (DELETE /items/{item_id}): Removes an item from the database.

What’s Next?

You’ve successfully built a fully functional REST API with FastAPI! This is just the beginning. The framework offers many more advanced features to explore, including:

  • Dependency Injection: A powerful system for managing dependencies and reusing logic across endpoints.
  • Database Integration: Use libraries like SQLAlchemy or SQLModel (built by the creator of FastAPI) to connect to a real database.
  • Authentication and Security: Implement security schemes like OAuth2 with password flows and JWT tokens.
  • Deployment: Learn how to containerize your application with Docker and deploy it to the cloud.

FastAPI’s combination of performance, ease of use, and robust features makes it an exceptional choice for any Python backend developer. Its ability to automatically handle validation and documentation saves countless hours of development time, allowing you to focus on building great features.

Start building your own high-performance APIs today and see how FastAPI can streamline your development workflow.