External Publication
Visit Post

FastAPI Crash Course: Build a CRUD REST API in ~40 Lines of Python

DEV Community [Unofficial] June 28, 2026
Source

I spend most of my days writing Java and Kotlin for FinTech systems at scale. But every now and then, Python pulls me back in β€” and FastAPI is exactly the reason why. This is a hands-on crash course based on a project I published on GitHub.

πŸ‘‰ View the full source on GitHub

Why FastAPI?

Coming from a Spring Boot background, my first instinct was scepticism. Another Python framework? But FastAPI is genuinely different:

  • Automatic interactive docs via Swagger UI β€” no extra config needed
  • Request validation powered by Pydantic β€” think of it as @Valid + @RequestBody baked in
  • Type hints as the source of truth β€” Python's type system does the heavy lifting
  • Async-ready from the ground up β€” ASGI all the way

The best part? You can build a fully working REST API in under 50 lines.

What We're Building

A Tea CRUD API β€” simple, but covers every REST pattern:

Method Endpoint What It Does
GET / Health / welcome message
GET /teas List all teas
POST /teas Add a new tea
PUT /teas/{tea_id} Update an existing tea
DELETE /teas/{tea_id} Remove a tea

No database β€” we're using an in-memory list to keep the focus on FastAPI itself.

Prerequisites

  • Python 3.10+
  • pip

Verify:

python --version
pip --version

Setup

Create and activate a virtual environment:

# Windows (PowerShell)
python -m venv venv
.\venv\Scripts\Activate

# macOS / Linux
python3 -m venv venv
source venv/bin/activate

Install dependencies:

pip install "fastapi[standard]"

This single command installs FastAPI, Uvicorn (the ASGI server), and all optional extras β€” all you need to get running.

The Full Code

Create a file called main.py and drop this in:

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

app = FastAPI()

class Tea(BaseModel):
    id: int
    name: str
    origin: str

teas: List[Tea] = []

@app.get("/")
def read_root():
    return {"message": "Welcome to chai code"}

@app.get("/teas")
def get_teas():
    return teas

@app.post("/teas")
def add_teas(tea: Tea):
    teas.append(tea)
    return tea

@app.put("/teas/{tea_id}")
def update_tea(tea_id: int, update_tea: Tea):
    for index, tea in enumerate(teas):
        if tea.id == tea_id:
            teas[index] = update_tea
            return update_tea
    return {"error": "Tea not found"}

@app.delete("/teas/{tea_id}")
def delete_tea(tea_id: int):
    for index, tea in enumerate(teas):
        if tea.id == tea_id:
            deleted = teas.pop(index)
            return deleted
    return {"error": "Tea not found"}

That's it. 43 lines. Let's break it down.

Code Walkthrough

1. The App Instance

app = FastAPI()

This is your application object β€” equivalent to new SpringApplication() in Spring Boot, but in one line with no annotations, XML, or config files.

2. The Pydantic Model

class Tea(BaseModel):
    id: int
    name: str
    origin: str

BaseModel from Pydantic is the magic here. Any class that extends it automatically gets:

  • Request body parsing β€” FastAPI reads the incoming JSON and maps it to this class
  • Type validation β€” if id isn't an integer, FastAPI returns a 422 Unprocessable Entity with a clear error message
  • Serialization β€” returning a Tea instance automatically serializes it to JSON

For Java developers: think @Data + @Valid + Jackson β€” except it's one import and zero annotations on your fields.

3. The In-Memory Store

teas: List[Tea] = []

Just a Python list typed with List[Tea]. It acts as our database for this example. Doesn't survive restarts β€” but that's intentional for a learning project. You'd swap this with SQLAlchemy + PostgreSQL in a production setup.

4. GET β€” List All Teas

@app.get("/teas")
def get_teas():
    return teas

Return the list directly. FastAPI serializes List[Tea] to a JSON array automatically. No ResponseEntity, no @ResponseBody, no boilerplate.

5. POST β€” Add a Tea

@app.post("/teas")
def add_teas(tea: Tea):
    teas.append(tea)
    return tea

The function parameter tea: Tea tells FastAPI: "expect a JSON body matching theTea schema." Pydantic validates it. If validation fails, FastAPI returns a detailed 422 error before your function is even called.

6. PUT β€” Update by ID

@app.put("/teas/{tea_id}")
def update_tea(tea_id: int, update_tea: Tea):
    for index, tea in enumerate(teas):
        if tea.id == tea_id:
            teas[index] = update_tea
            return update_tea
    return {"error": "Tea not found"}

{tea_id} in the path is a path parameter β€” FastAPI extracts it and coerces it to int automatically. You get both a path param (tea_id) and a request body (update_tea) from a single function signature. Clean.

7. DELETE β€” Remove by ID

@app.delete("/teas/{tea_id}")
def delete_tea(tea_id: int):
    for index, tea in enumerate(teas):
        if tea.id == tea_id:
            deleted = teas.pop(index)
            return deleted
    return {"error": "Tea not found"}

Linear scan through our in-memory list β€” fine for learning, but you'd index by ID with a dict or use a database in production.

Run It

python -m uvicorn main:app --reload

Expected output:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process

The --reload flag means the server restarts automatically on every file save β€” great for development.

Interactive API Docs (Free!)

This is FastAPI's killer feature. Open your browser:

URL What You Get
http://127.0.0.1:8000/docs Swagger UI β€” test every endpoint interactively
http://127.0.0.1:8000/redoc ReDoc β€” clean, readable API reference

No extra setup. No Postman required for a quick test. The docs are generated directly from your type hints and Pydantic models.

Quick Test via Swagger UI

  1. Go to http://127.0.0.1:8000/docs
  2. Click POST /teas β†’ Try it out
  3. Paste this body:
{
  "id": 1,
  "name": "Assam Tea",
  "origin": "India"
}
  1. Click Execute
  2. Now hit GET /teas β€” you'll see your tea in the list

What's Next (Production Roadmap)

This in-memory app is intentionally minimal. Here's how you'd evolve it toward production:

Concern Tool
Persistent storage SQLAlchemy + PostgreSQL or SQLite
Migrations Alembic
Auth JWT via python-jose + passlib
Dependency Injection FastAPI's built-in Depends() system
Environment config pydantic-settings
Containerization Docker + uvicorn in production mode
Testing pytest + httpx (async test client)
Async DB asyncpg + SQLAlchemy async sessions

Java/Kotlin Developer Take

Having spent years with Spring Boot, here's my honest comparison:

FastAPI wins on:

  • Speed of prototyping β€” no boilerplate ceremony
  • Auto-generated docs that are actually useful
  • Cleaner validation without annotation overload

Spring Boot still wins on:

  • Enterprise ecosystem maturity
  • Structured DI with complex bean graphs
  • Mature monitoring (Actuator, Micrometer)

FastAPI fills a real gap: it's the right tool when you want Python's data ecosystem (ML, data pipelines) behind a production-quality HTTP API. The two aren't in competition β€” they're tools for different contexts.

Source Code

Everything in this post is available here:

github.com/MihirMohapatra/fast-api-example

Drop a ⭐ if it helped you get started.

Building toward senior remote roles at US/EU companies. Follow along as I explore FastAPI, Rust, Go, and AI infrastructure β€” writing from the perspective of a practising backend engineer, not a tutorial blog.

Discussion in the ATmosphere

Loading comments...