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
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 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"}
{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
- Go to
http://127.0.0.1:8000/docs
- Click POST /teas β Try it out
- Paste this body:
{"id":1,"name":"Assam Tea","origin":"India"}
- Click Execute
- 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.