Setup
pip install fastapi uvicorn sqlalchemy
uvicorn main:app --reloadComplete Implementation
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import create_engine, Column, Integer, String, text
from sqlalchemy.orm import DeclarativeBase, Session
import random
engine = create_engine("sqlite:///quotes.db")
class Base(DeclarativeBase): pass
class QuoteModel(Base):
__tablename__ = "quotes"
id = Column(Integer, primary_key=True, index=True)
text = Column(String, nullable=False)
author = Column(String, nullable=False)
category = Column(String, default="general")
Base.metadata.create_all(engine)
class QuoteCreate(BaseModel):
text: str
author: str
category: str = "general"
class QuoteResponse(QuoteCreate):
id: int
class Config: from_attributes = True
app = FastAPI(title="Quotes API")
@app.get("/quotes", response_model=list[QuoteResponse])
def list_quotes(category: str | None = Query(None), skip: int = 0, limit: int = 20):
with Session(engine) as db:
q = db.query(QuoteModel)
if category: q = q.filter(QuoteModel.category == category)
return q.offset(skip).limit(limit).all()
@app.get("/quotes/random", response_model=QuoteResponse)
def random_quote(category: str | None = None):
with Session(engine) as db:
q = db.query(QuoteModel)
if category: q = q.filter(QuoteModel.category == category)
quotes = q.all()
if not quotes: raise HTTPException(404, "No quotes")
return random.choice(quotes)
@app.get("/quotes/{quote_id}", response_model=QuoteResponse)
def get_quote(quote_id: int):
with Session(engine) as db:
q = db.get(QuoteModel, quote_id)
if not q: raise HTTPException(404, "Not found")
return q
@app.post("/quotes", response_model=QuoteResponse, status_code=201)
def create_quote(quote: QuoteCreate):
with Session(engine) as db:
q = QuoteModel(**quote.dict())
db.add(q); db.commit(); db.refresh(q)
return q
@app.delete("/quotes/{quote_id}", status_code=204)
def delete_quote(quote_id: int):
with Session(engine) as db:
q = db.get(QuoteModel, quote_id)
if not q: raise HTTPException(404, "Not found")
db.delete(q); db.commit()# API docs: http://localhost:8000/docs🏋️ Practical Exercise
Extend the API project:
- Add a new endpoint that updates an existing resource (PUT/PATCH).
- Return a 404 with a clear message when a requested id does not exist.
- Add input validation so invalid payloads are rejected with a 422/400.
- Add a simple filter query parameter to the list endpoint.
🔥 Challenge Exercise
Take the API to production-readiness: persist data in a database instead of memory, add token-based authentication on write routes, paginate the list endpoint, and write automated tests with TestClient covering success and error paths. Bonus: containerize the app with a Dockerfile and document the endpoints.
📋 Summary
- This project builds a working REST API with create, read, update, and delete endpoints.
- CRUD maps to HTTP methods with appropriate status codes (200/201/404/400).
- Request bodies are validated with schemas (e.g. Pydantic) before processing.
- Authentication protects write operations.
- Automated tests verify both happy paths and error cases.
- Swapping in-memory storage for a database makes the API persistent and production-ready.
Interview Questions on Building a REST API
- How do you structure a REST API project for maintainability?
- How do you map CRUD operations to HTTP methods and status codes?
- How do you validate request data in an API?
- How do you add authentication to selected routes?
- How do you test an API?
- How do you handle errors and return consistent error responses?
- How would you move from in-memory storage to a real database?
Related Topics
FAQ
FastAPI is a great fit for a modern API: automatic validation, type hints, and built-in docs. Flask works well too and is very flexible. Either lets you complete this project; FastAPI just removes more boilerplate.
Replace the Python list/dict store with a database layer — SQLite via sqlite3 or SQLAlchemy to start. Define models, and update each endpoint to query and persist through the database instead of the in-memory structure.
Use the framework’s test client (FastAPI’s TestClient or Flask’s test_client) to send requests in your tests and assert on status codes and response bodies — no running server needed.
Return appropriate status codes with a consistent JSON error body (e.g. {"detail": "Not found"}). Frameworks provide helpers like FastAPI’s HTTPException to do this cleanly.
