Backend Engineering
Servers, databases, caches, jobs
A backend is a kitchen: the server is the chef taking orders (requests); the database is the pantry (everything stored); the cache is the fridge (things you use constantly, kept close); background jobs are the prep cook working ahead of the rush so the chef never blocks.
You don't need to build every part from scratch — you need to know which parts are dangerous. Auth, money, and data loss are dangerous. Everything else is plumbing.
FastAPI: types in, docs out
FastAPI generates validation AND interactive docs from your type hints. Declare the shape of a request with a Pydantic model, and the framework rejects bad input with a 422 before your code runs:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str # type = validation, for free
price: float
ITEMS: dict[int, Item] = {} # in-memory; real DB in a moment
@app.post("/items", status_code=201)
def create_item(item: Item):
id = len(ITEMS) + 1
ITEMS[id] = item
return {"id": id, **item.model_dump()}
@app.get("/items/{item_id}")
def get_item(item_id: int):
if item_id not in ITEMS:
raise HTTPException(status_code=404, detail="Item not found")
return ITEMS[item_id]
# Open http://localhost:8000/docs — live Swagger UI, try-it buttonsSQL: the 20% that does 80% of the work
Databases answer questions. The questions you'll ask 90% of the time are: create a table, insert rows, join two tables, and find rows fast. The index is the database's shortcut — without one, `WHERE user_id = 42` reads every row:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
plan TEXT NOT NULL DEFAULT 'free',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
amount NUMERIC(10,2) NOT NULL CHECK (amount > 0),
status TEXT NOT NULL DEFAULT 'pending'
);
-- the #1 pattern: JOIN orders with their user
SELECT o.id, o.amount, u.name, u.email
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE u.plan = 'pro';
-- without this index, the JOIN scans every row
CREATE INDEX idx_orders_user_id ON orders (user_id);
-- prove it: Seq Scan = bad, Index Scan = good
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 42;Migrations and caches: change safely, serve fast
Migrations are version control for your database schema. Alembic diffs your code models against the live database and generates the ALTER TABLE for you — and `alembic check` fails CI when they drift, so an AI-written change can't silently break production:
pip install alembic && alembic init alembic
# set sqlalchemy.url in alembic.ini to your DATABASE_URL
alembic revision --autogenerate -m "add orders table" # diff models vs DB
alembic upgrade head # apply it
alembic check # CI gate: fails if models and DB drifted apartimport redis, json
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
def get_pricing(device: str) -> dict:
cached = r.get(f"price:{device}") # 1. fridge first
if cached:
return json.loads(cached)
price = query_database(device) # 2. pantry if miss
r.setex(f"price:{device}", 300, json.dumps(price)) # 3. restock, TTL 5min
return pricetest yourself
01EXPLAIN ANALYZE shows Seq Scan on your hot query. The fix:
02Why SQLite in dev and Postgres in prod?
03What does alembic check do in CI?
the real check
alembic check passes in CI. EXPLAIN ANALYZE shows an Index Scan on the hot query. A background job survives an app restart.
0 stamps earned·streak: 0 days
Signed out: progress lives in this browser. to carry it everywhere.
vibe-coder payoff
Agents build backends fast — you must know which parts are dangerous (auth, money, data loss) and which are plumbing.