DEV Community

Cover image for FastAPI Crash Course: Build a CRUD REST API in ~40 Lines of Python
mihir mohapatra
mihir mohapatra

Posted on • Originally published at github.com

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

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Install dependencies:

pip install "fastapi[standard]"
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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


Code Walkthrough

1. The App Instance

app = FastAPI()
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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] = []
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The function parameter tea: Tea tells FastAPI: "expect a JSON body matching the Tea 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"}
Enter fullscreen mode Exit fullscreen mode

{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"}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Expected output:

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

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 /teasTry it out
  3. Paste this body:
{
  "id": 1,
  "name": "Assam Tea",
  "origin": "India"
}
Enter fullscreen mode Exit fullscreen mode
  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.

Top comments (0)