Posted Sep 29, 2026 · 5 min read · 0 views
Build Your First REST API with Python and FastAPI
If you want to build a modern API in Python, FastAPI is hard to ignore. It is fast, uses standard Python type hints, validates input automatically, and generates interactive documentation without extra work.
In this tutorial you will build a small book library API with create, read, update, and delete endpoints, validation, and tests.
Why FastAPI?
- Type hints drive everything. You declare types once and get validation, serialization, and docs for free.
- Automatic docs. Swagger UI and ReDoc are built in.
- Async support. Handle many concurrent requests efficiently.
- Great editor support. Autocomplete and error hints work well because everything is typed.
Step 1: Set up the environment
You need Python 3.9 or newer. Create a virtual environment and install FastAPI:
mkdir fastapi-demo && cd fastapi-demo
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install "fastapi[standard]"
The standard extra installs Uvicorn (the server) and the FastAPI command line tool.
Step 2: Your first endpoint
Create main.py:
from fastapi import FastAPI
app = FastAPI(title="Book Library API")
@app.get("/")
def read_root():
return {"message": "Welcome to the Book Library API"}
Run the development server:
fastapi dev main.py
Open http://127.0.0.1:8000 to see the JSON response. Now visit http://127.0.0.1:8000/docs to see the interactive Swagger UI that FastAPI generated automatically. You can test every endpoint right from the browser.
Step 3: Define your data models
FastAPI uses Pydantic models to describe and validate data. Add these to main.py:
from pydantic import BaseModel, Field
class BookCreate(BaseModel):
title: str = Field(min_length=1, max_length=200)
author: str = Field(min_length=1, max_length=100)
year: int = Field(ge=1000, le=2100)
class Book(BookCreate):
id: int
We use two models on purpose. BookCreate is what clients send, and Book is what we return, which also includes the id the server generates. This separation keeps clients from choosing their own IDs.
Step 4: Build the CRUD endpoints
For simplicity we will store books in memory. In a real project you would use a database such as PostgreSQL with SQLAlchemy or SQLModel.
from fastapi import FastAPI, HTTPException, status
books: dict[int, Book] = {}
next_id = 1
@app.post("/books", response_model=Book, status_code=status.HTTP_201_CREATED)
def create_book(payload: BookCreate):
global next_id
book = Book(id=next_id, **payload.model_dump())
books[next_id] = book
next_id += 1
return book
@app.get("/books", response_model=list[Book])
def list_books(author: str | None = None, limit: int = 10):
results = list(books.values())
if author:
results = [b for b in results if b.author.lower() == author.lower()]
return results[:limit]
@app.get("/books/{book_id}", response_model=Book)
def get_book(book_id: int):
book = books.get(book_id)
if book is None:
raise HTTPException(status_code=404, detail="Book not found")
return book
@app.put("/books/{book_id}", response_model=Book)
def update_book(book_id: int, payload: BookCreate):
if book_id not in books:
raise HTTPException(status_code=404, detail="Book not found")
updated = Book(id=book_id, **payload.model_dump())
books[book_id] = updated
return updated
@app.delete("/books/{book_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_book(book_id: int):
if book_id not in books:
raise HTTPException(status_code=404, detail="Book not found")
del books[book_id]
Notice how much FastAPI handles for you:
book_id: intin the path is validated and converted automatically. Requesting/books/abcreturns a clear 422 error.authorandlimitare query parameters because they are not part of the path. Try/books?author=Tagore&limit=5.response_modelfilters and documents the output shape.- Invalid request bodies are rejected before your function even runs, with a detailed error message.
Try posting a book with year: 5 in the Swagger UI and you will see validation in action.
Step 5: Use dependencies for shared logic
Dependency injection is one of FastAPI's best features. Suppose you want to reuse the "find a book or return 404" logic:
from fastapi import Depends
def get_book_or_404(book_id: int) -> Book:
book = books.get(book_id)
if book is None:
raise HTTPException(status_code=404, detail="Book not found")
return book
@app.get("/books/{book_id}", response_model=Book)
def get_book(book: Book = Depends(get_book_or_404)):
return book
Dependencies are also how you handle database sessions, authentication, and pagination in larger apps.
Step 6: Write tests
FastAPI ships with a test client. Install the test tools:
pip install pytest httpx
Create test_main.py:
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_create_and_get_book():
response = client.post(
"/books",
json={"title": "Gitanjali", "author": "Rabindranath Tagore", "year": 1910},
)
assert response.status_code == 201
book_id = response.json()["id"]
response = client.get(f"/books/{book_id}")
assert response.status_code == 200
assert response.json()["title"] == "Gitanjali"
def test_invalid_year_is_rejected():
response = client.post(
"/books",
json={"title": "Bad Data", "author": "Someone", "year": 5},
)
assert response.status_code == 422
Run the tests with pytest. You get fast feedback with no server running.
Step 7: Prepare for production
The development server is not meant for production. Run Uvicorn directly, or use several workers:
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
Before going live, make sure you:
- Replace the in-memory dictionary with a real database
- Load configuration from environment variables, not hardcoded values
- Add authentication, for example OAuth2 with JWT, which FastAPI supports through
fastapi.security - Restrict CORS origins with
CORSMiddlewareinstead of allowing everything - Put the app behind Nginx or a cloud load balancer with HTTPS
Common mistakes
- Using
defversusasync defblindly. Useasync defonly when you call async libraries. Blocking code insideasync defslows the whole server. - Returning database objects directly. Use response models so sensitive fields never leak.
- Ignoring status codes. Return 201 for creation and 404 for missing resources, so clients can react correctly.
- Storing state in global variables in production. With multiple workers, each process has its own copy, so data will be inconsistent.
Final thoughts
FastAPI removes a lot of boilerplate. You write type hints, and you get validation, documentation, and a clean API. Start with this small project, add a real database next, and then explore background tasks, authentication, and WebSockets.
Have you used FastAPI or are you considering it over Flask or Django? Share your thoughts in the comments.
Discussion (0)