Build a REST API with FastAPI in Python: A Beginner Guide

Building APIs is one of the most valuable skills a developer can have in 2026. Whether you are creating a backend for a mobile app, connecting microservices, or powering a frontend with data, REST APIs are at the core of modern software. FastAPI has quickly become one of the most popular Python frameworks for building them, and for good reason.
In this tutorial, you will learn how to build a REST API with FastAPI in Python from scratch. By the end, you will have a working API with CRUD operations, automatic validation, and interactive documentation.
Why FastAPI?
FastAPI stands out among Python web frameworks for several reasons:
- Speed: It is one of the fastest Python frameworks, on par with Node.js and Go thanks to its async support and Starlette foundation.
- Automatic documentation: Every endpoint you create automatically appears in an interactive Swagger UI and ReDoc page.
- Type safety: It uses Python type hints and Pydantic models for automatic request validation and serialization.
- Developer experience: Less boilerplate, better error messages, and excellent editor support with autocomplete.
If you have used Flask or Django REST Framework before, FastAPI will feel familiar but significantly more streamlined.
Setting Up Your Project
First, make sure you have Python 3.9 or later installed. Then create a new project directory and set up a virtual environment:
mkdir fastapi-todo && cd fastapi-todo
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install fastapi uvicorn
uvicorn is the ASGI server that will run your FastAPI application. Create a file called main.py to get started.
Your First Endpoint
Open main.py and add the following code:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Welcome to the Todo API"}
Run your server with:
uvicorn main:app --reload
Visit http://localhost:8000 in your browser and you will see the JSON response. Now visit http://localhost:8000/docs to see the auto-generated Swagger documentation. This is one of FastAPI's killer features and it works out of the box.
Defining Data Models with Pydantic
FastAPI uses Pydantic models to validate request and response data. This means you define what your data looks like using Python classes, and FastAPI handles the rest.
Add a Pydantic model for a todo item:
from pydantic import BaseModel
from typing import Optional
class TodoCreate(BaseModel):
title: str
description: Optional[str] = None
completed: bool = False
class TodoResponse(TodoCreate):
id: int
The TodoCreate model defines what fields a client must send when creating a todo. The TodoResponse model extends it with an id field that the server generates. If a client sends invalid data, FastAPI automatically returns a clear 422 error with details about what went wrong.
Building CRUD Operations
Now let us build out the full set of CRUD (Create, Read, Update, Delete) endpoints. We will use an in-memory list for simplicity:
from fastapi import FastAPI, HTTPException
app = FastAPI()
todos: list[dict] = []
counter = 0
@app.post("/todos", response_model=TodoResponse, status_code=201)
def create_todo(todo: TodoCreate):
global counter
counter += 1
new_todo = {"id": counter, **todo.model_dump()}
todos.append(new_todo)
return new_todo
@app.get("/todos", response_model=list[TodoResponse])
def list_todos():
return todos
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int):
for todo in todos:
if todo["id"] == todo_id:
return todo
raise HTTPException(status_code=404, detail="Todo not found")
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, updated: TodoCreate):
for i, todo in enumerate(todos):
if todo["id"] == todo_id:
todos[i] = {"id": todo_id, **updated.model_dump()}
return todos[i]
raise HTTPException(status_code=404, detail="Todo not found")
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int):
for i, todo in enumerate(todos):
if todo["id"] == todo_id:
todos.pop(i)
return
raise HTTPException(status_code=404, detail="Todo not found")
Each endpoint is clean and readable. The response_model parameter tells FastAPI exactly what shape the response should have, and it validates the output automatically.
Testing Your API
With the server running, open http://localhost:8000/docs. The Swagger UI lets you test every endpoint directly from your browser. Click on any endpoint, hit "Try it out", fill in the request body, and execute. You can create todos, list them, update them, and delete them without writing a single line of frontend code.
You can also test with curl:
curl -X POST http://localhost:8000/todos \
-H "Content-Type: application/json" \
-d '{"title": "Learn FastAPI", "description": "Build a REST API"}'
Adding Query Parameters and Filtering
FastAPI makes it easy to add optional query parameters. Let us add a filter to list only completed or incomplete todos:
@app.get("/todos", response_model=list[TodoResponse])
def list_todos(completed: Optional[bool] = None):
if completed is not None:
return [t for t in todos if t["completed"] == completed]
return todos
Now you can call GET /todos?completed=true to filter results. FastAPI validates that the parameter is a boolean and documents it automatically.
Handling Errors Properly
FastAPI uses HTTPException for error responses. You have already seen it in action with 404 errors. You can customize error responses further:
from fastapi.responses import JSONResponse
@app.exception_handler(Exception)
async def general_exception_handler(request, exc):
return JSONResponse(
status_code=500,
content={"detail": "An unexpected error occurred"},
)
This catches unhandled exceptions and returns a clean JSON response instead of exposing internal details to the client.
Next Steps: From Prototype to Production
The in-memory storage we used is great for learning, but a real application needs a database. Here is what to explore next:
- Database integration: Use SQLAlchemy or Tortoise ORM with PostgreSQL or SQLite for persistent storage.
- Authentication: Add JWT-based auth with FastAPI's built-in security utilities.
- Async endpoints: Replace
defwithasync deffor database queries and external API calls to handle more concurrent requests. - Deployment: Use Docker to containerize your app and deploy to platforms like Railway, Render, or AWS.
FastAPI also has excellent support for dependency injection, middleware, background tasks, and WebSockets, making it suitable for production applications of any scale.
Conclusion
FastAPI makes building REST APIs in Python faster and more enjoyable than ever. With automatic validation, interactive documentation, and high performance, it removes much of the boilerplate that slows developers down. You built a complete CRUD API in under 50 lines of meaningful code.
If you are looking to level up your backend development skills, FastAPI is one of the best tools to learn in 2026. Start with a simple project like this todo API, then gradually add a database, authentication, and deployment. The skills transfer directly to building real-world applications that power modern software.

