Moniruzzaman Saikat

Posted Sep 29, 2026 · 5 min read · 0 views

Report

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: int in the path is validated and converted automatically. Requesting /books/abc returns a clear 422 error.
  • author and limit are query parameters because they are not part of the path. Try /books?author=Tagore&limit=5.
  • response_model filters 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:

  1. Replace the in-memory dictionary with a real database
  2. Load configuration from environment variables, not hardcoded values
  3. Add authentication, for example OAuth2 with JWT, which FastAPI supports through fastapi.security
  4. Restrict CORS origins with CORSMiddleware instead of allowing everything
  5. Put the app behind Nginx or a cloud load balancer with HTTPS

Common mistakes

  • Using def versus async def blindly. Use async def only when you call async libraries. Blocking code inside async def slows 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.

Be the first to react
0

Written by

Moniruzzaman Saikat

Software Engineer at TheSoftking Ltd

Software engineer who loves building useful things, solving hard problems, and turning ideas into scalable products. Always learning, shipping, and experimenting with new tech.

Founding MemberNew MemberProlific Writer

14 articles · Dhaka Bangladesh · Joined Sep 2026

Discussion (0)

Sign in to join the discussion.