Part 4: What Can You Build? (Spoiler: Almost Anything)
Part 5: What's Missing? Preview of System Design
Demo: End-to-End System Build
Exploration: System Analysis & Modification
No prep work this week! Today is about seeing the big picture.
Part 1
Your Technology Stack
The Journey So Far
Week 2 Python The language
Week 3 Flask REST APIs
Week 5 Docker Containers
Week 6 Marshmallow Validation
Week 7 SQLAlchemy Databases
Week 8 Redis + rq Async tasks
Six weeks of technologies. Today we see how they combine.
Your Complete Stack
Python
Language
Flask
Web Framework
PostgreSQL
Database
SQLAlchemy
ORM
Marshmallow
Validation
Redis
Queue Broker
rq
Background Jobs
Docker
Deployment
This stack can build most backend systems.
How They Fit Together
Request Flow
User sends HTTP request
Flask + Marshmallow validates and routes
Flask connects to one or more backends:
PostgreSQL - persistent data storage
Redis - job queue broker
rq Worker - background task processing
All running in Docker containers
Each Technology Has a Job
Technology
What It Does
When You Use It
Flask
Handle HTTP requests
Every incoming request
Marshmallow
Validate & transform data
API input/output
PostgreSQL
Store structured data
Users, orders, products
Redis
Job queue broker
Background job dispatch
rq
Process jobs in background
Slow operations
Docker
Package & run everything
Development & deployment
Part 2
Functions: The Universal Unit of Work
Everything is Functions
# Flask route - a function
@app.route('/users', methods=['POST'])
def create_user():
...
# Background task - a function
@job('emails', connection=redis)
def send_welcome_email(user_id):
...
# Validation - a function
def validate_email(value):
...
# Database query - functions on objects
user = User.query.filter_by(email=email).first()
Routes, tasks, validators, queries - all functions
Functions are the universal unit of work
Functions Encapsulate Work
def process_order(order_id, user_email):
"""
A function encapsulates:
1. WHAT to do (the code inside)
2. WHAT it needs (the parameters)
3. WHAT it returns (the output)
"""
order = get_order(order_id) # Get data
validate_order(order) # Check it
charge_payment(order) # Do work
send_confirmation(user_email) # Side effect
return {"status": "completed"} # Return result
Functions package up work with a name
Call them, pass them around, schedule them
This is true in every programming context
Decorators Change Execution Context
# The SAME work, different EXECUTION CONTEXTS:
# Runs NOW when HTTP request arrives
@app.route('/email', methods=['POST'])
def send_email_api():
send_email(data['to'], data['subject'], data['body'])
return {"sent": True}
# Runs LATER when worker picks it up
@job('emails', connection=redis)
def send_email_task(to, subject, body):
send_email(to, subject, body)
return {"sent": True}
@app.route → "Run when HTTP request arrives"
@job → "Run when worker picks up from queue"
The function is the same - the trigger changes
Sync vs Async: Same Function, Different Timing
Synchronous
@app.route()
User calls function
Function executes NOW
User waits...
Result returned
Asynchronous
@job()
User queues function
User gets job ID (instant)
Worker executes LATER
User polls for result
Choose based on: Does the user need the result right now?
Part 3
The Four Pillars of Backend Development
Four Principles, Every Week
Looking across all our lectures, four principles kept appearing:
Validate Everything - Never trust data crossing a boundary
Separate Concerns - Each function/component does one thing
Defer When Possible - Don't block on slow operations
Transform Explicitly - Be intentional about data shape
Pillar 1: Validate Everything
"Never trust data crossing a boundary"
# Week 6: Marshmallow at the API boundary
data = UserSchema().load(request.get_json()) # Validates!
# Week 7: Database constraints as backup
class User(db.Model):
email = db.Column(db.String(255), unique=True, nullable=False)
# Week 8: Validate before queuing
if not is_valid_email(email):
return {"error": "Invalid email"}, 400
send_email.delay(email, message) # Only queue valid data
HTTP input, database writes, queue messages - all boundaries
Validate at every boundary
Pillar 2: Separate Concerns
"Each function/component does one thing"
# Bad: One function does everything
def handle_order(data):
validate(data); charge_card(data); update_inventory(data)
send_email(data); log_analytics(data)
# Good: Each function has one job
def handle_order(data):
order = validate_order(data) # Just validate
payment = process_payment(order) # Just charge
update_inventory(order) # Just inventory
send_confirmation.delay(order.id) # Defer email
One function = one responsibility
Easier to test, debug, reuse, and modify
Pillar 3: Defer When Possible
"Don't block on slow operations"
# Slow: User waits for everything
@app.post('/order')
def create_order():
order = save_order(data)
send_email(order.user.email) # 2 seconds...
generate_invoice(order) # 3 seconds...
notify_warehouse(order) # 1 second...
return order.to_dict() # User waited 6+ seconds!
# Fast: User gets immediate response
@app.post('/order')
def create_order():
order = save_order(data)
process_order.delay(order.id) # Queue all the slow stuff
return order.to_dict(), 202 # User waited < 100ms!
Pillar 4: Transform Explicitly
"Be intentional about data shape at each boundary"
# Data transforms at EVERY step - be explicit!
json_input = request.get_json() # Bytes → Python dict
validated = UserSchema().load(json_input) # Dict → Validated
user = User(**validated) # Validated → ORM model
db.session.add(user) # Model → SQL INSERT
db.session.commit() # SQL → Database row
# Going back out:
result = UserSchema().dump(user) # Model → Dict (controlled!)
return jsonify(result) # Dict → JSON bytes
Stripe, Twilio, SendGrid - all started with stacks like yours.
The Pattern is Always the Same
Every request follows this flow:
User Request arrives
Flask validates, routes, responds
Flask uses backends as needed:
PostgreSQL
Redis + rq
Persist data
Defer slow work
Different apps, same architecture
Learn the pattern, apply it anywhere
Part 5
What's Missing? Looking Ahead
What Your Stack Doesn't Cover (Yet)
Caching
Speed up repeated reads
Reduce database load
Solution: Redis
File Storage
Images, videos, PDFs
User uploads
Solution: AWS S3
Document Store
Flexible JSON documents
No fixed schema
Solution: MongoDB, DynamoDB
Vector/AI Search
Semantic similarity
"Find similar" features
Solution: Pinecone, pgvector
Redis as Cache: Speed Up Reads
# The problem: database queries are slow for hot data
# Every page view queries PostgreSQL
product = Product.query.get(product_id) # 50ms each time
# Solution: Cache in Redis (same Redis you already have!)
from redis import Redis
redis_client = Redis.from_url('redis://redis:6379/0')
# Check cache first
cached = redis_client.get(f"product:{product_id}")
if cached:
product_data = json.loads(cached) # 1ms - much faster!
else:
product = Product.query.get(product_id) # Cache miss
redis_client.setex(f"product:{product_id}", 3600, json.dumps(product.to_dict()))
Redis is in-memory: microseconds vs database milliseconds
Same Redis you use for queues - different use case
Common for: sessions, hot data, API rate limiting
File Storage: The Missing Piece
# The problem: where do user profile pictures go?
# NOT in PostgreSQL (too large, wrong tool)
# NOT in Redis (ephemeral, memory-based)
# Solution: Object storage (S3)
import boto3
s3 = boto3.client('s3')
s3.upload_file('avatar.jpg', 'my-bucket', 'users/123/avatar.jpg')
# Store the URL in PostgreSQL
user.avatar_url = 'https://my-bucket.s3.amazonaws.com/users/123/avatar.jpg'
db.session.commit()
Store the file in S3
Store the URL in PostgreSQL
Best of both worlds
Vector Databases: The AI Piece
# The problem: "Find products similar to this one"
# PostgreSQL can't do semantic similarity
# Redis doesn't understand meaning
# Solution: Vector database
from pinecone import Pinecone
pc = Pinecone(api_key="...")
index = pc.Index("products")
# Store embeddings (numbers that represent meaning)
index.upsert([(product.id, product.embedding, {"name": product.name})])
# Query by similarity
results = index.query(query_embedding, top_k=10) # "Find 10 similar"
Embeddings = numbers that capture meaning
Vector DBs find similar items by meaning
Powers: recommendations, semantic search, RAG
Coming Next: System Design
Week 11: System Design Fundamentals
The "Seven Building Blocks" framework
Categorizing every technology you know
A vocabulary for designing any system
Week 12: Technology Mapping
Week 13: Case Studies
Preview: Flask is a "Service". PostgreSQL is a "Relational Database". Redis is both "Key-Value Store" and "Queue". You'll learn to think in building blocks.
Live Demo
URL Shortener - Full Stack Integration
What We'll Build
Flask API to create and redirect short URLs
Marshmallow validation for URL format
PostgreSQL to store URL mappings
Redis + rq for background click tracking
Docker Compose running all four services
Watch for:
Redirect returning instantly (user doesn't wait)
Worker logs showing click tracking after redirect
Stats endpoint showing click count incrementing
In-Class Exploration
System Analysis & Modification
Your Mission
You'll receive a complete, working notification system.
Analyze (15 min):
How does data flow through the system?
Where are the four pillars applied?
What does each file do?
Modify (25 min):
Add a new feature (choose from options)
Apply the four pillars in your changes
Test (5 min): Verify your changes work
The System: Notification Hub
API to send notifications (email, SMS, push)
Background delivery via workers
Status tracking and history
Files to explore:
app.py - Flask routes
models.py - Database schema
schemas.py - Validation
tasks.py - Worker functions
Modification Challenges
Choose one:
A: Rate Limiting
Limit 10 notifications/minute per user
Use Redis to track counts
Return 429 when exceeded
B: Retry Failed
Add retry logic to workers
Track retry count in database
Max 3 retries per notification
C: Priority Queues
Add "priority" field to notifications
High priority processed first
Use separate queues in rq
Getting Started
Clone the repository (link in bCourses)
Run docker-compose up
Test the existing API with curl
Read through the codebase
Identify where to add your feature
Implement and test
Tip: Read the code before changing it. Understand, then modify.
Summary
Key Takeaways
What We Covered Today
Your stack is complete - Flask, PostgreSQL, Redis, rq, Docker
Functions are the unit of work - Decorators change execution context
Four pillars guide everything - Validate, Separate, Defer, Transform
You can build most systems - E-commerce, social, SaaS, APIs