TL;DR — Build a real-time user safety service that validates user-generated content, logs violations, and exposes metrics, all containerized with Docker. This project demonstrates API design, caching, persistence, observability, and deployment—skills that directly map to senior systems roles.
In a crowded job market, you need a portfolio piece that proves you can ship production‑grade software, not just write algorithms. A User Safety service is a perfect vehicle: it touches APIs, data storage, caching, monitoring, and fault tolerance—all core competencies for backend or full‑stack engineers. Below you’ll find a complete, runnable implementation that you can deploy locally in minutes, then extend into a scalable, observable system.
Why This Project Stands Out on a CV
- End‑to‑end system design – You’ll configure a REST API, a cache, a relational database, metrics collection, and container orchestration, showing you understand how components fit together.
- Real‑world domain – Content moderation and safety are critical in social platforms, marketplaces, and collaboration tools; building a working solution signals domain awareness.
- Production‑ready patterns – The code uses connection pooling, structured logging, Prometheus metrics, and Docker‑based deployment, which are standard in modern SRE and backend teams.
- Scalable architecture – The design is stateless at the API layer, making horizontal scaling trivial—a talking point for senior roles.
- Observability first – By exposing metrics and structured logs, you demonstrate the “you build it, you run it” mindset that hiring managers love.
Architecture Overview
The service is a small micro‑system with five clear boundaries:
- API Layer – FastAPI (Python) exposes
/checkPOST endpoint that receives text and returns a safety verdict. - Validation Engine – A pluggable module that applies rule‑based filters and can later host a ML model.
- Cache – Redis stores recent verdicts to avoid recomputation and reduce latency.
- Persistence – PostgreSQL records every violation with timestamp and payload for audit trails.
- Observability – Prometheus scrapes
/metrics; Grafana (optional) visualizes request rate, error ratio, and latency.
A simple text diagram:
Client ──► FastAPI ──► Redis (cache) ──► PostgreSQL (audit)
│
▼
Prometheus
All components are packaged as Docker containers, orchestrated with docker‑compose.yml.
Building It Step by Step
Step 1 – Scaffold the Project
Create a directory user_safety_service and initialize a virtual environment.
mkdir user_safety_service
cd user_safety_service
python -m venv venv
source venv/bin/activate
Add requirements.txt:
fastapi==0.110.0
uvicorn==0.29.0
redis==5.0.3
psycopg2-binary==2.9.9
sqlalchemy==2.0.29
prometheus-client==0.20.0
pydantic==2.7.1
Install dependencies:
pip install -r requirements.txt
Step 2 – Core API with FastAPI
Create main.py:
from fastapi import FastAPI, HTTPException, Depends
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import redis
import sqlalchemy
from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
import time
import os
# --- Configuration ---
REDIS_URL = os.getenv("REDIS_URL", "redis://localhost:6379")
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:pass@localhost:5432/safety")
# --- FastAPI app ---
app = FastAPI(title="User Safety Service")
# --- Prometheus metrics ---
REQUEST_COUNT = Counter("safety_requests_total", "Total requests to safety service", ["method", "endpoint"])
REQUEST_LATENCY = Histogram("safety_request_latency_seconds", "Latency of safety requests", ["method", "endpoint"])
# --- Redis client ---
redis_client = redis.from_url(REDIS_URL, decode_responses=True)
# --- Database setup ---
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Violation(Base):
__tablename__ = "violations"
id = Column(Integer, primary_key=True, index=True)
text = Column(Text, nullable=False)
reason = Column(String, index=True)
created_at = Column(DateTime, default=sqlalchemy.func.now())
Base.metadata.create_all(bind=engine)
# --- Dependency ---
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# --- Pydantic models ---
class SafetyCheckRequest(BaseModel):
text: str
class SafetyCheckResponse(BaseModel):
safe: bool
reason: str | None = None
# --- Safety validator ---
def validate_text(text: str) -> tuple[bool, str | None]:
# Simple rule‑based filter (replace with ML later)
banned_words = ["spam", "hate", "violence"]
for word in banned_words:
if word in text.lower():
return False, f"Contains banned word: {word}"
return True, None
# --- Endpoints ---
@app.post("/check", response_model=SafetyCheckResponse)
async def check_safety(req: SafetyCheckRequest):
# Increment request counter
REQUEST_COUNT.labels(method="POST", endpoint="/check").inc()
# Measure latency
with REQUEST_LATENCY.labels(method="POST", endpoint="/check").time():
# 1. Check cache
cached = redis_client.get(req.text)
if cached:
safe, reason = cached.split("::", 1)
return SafetyCheckResponse(safe=safe == "True", reason=reason if reason else None)
# 2. Validate
safe, reason = validate_text(req.text)
# 3. Cache result (TTL 5 minutes)
redis_client.setex(req.text, 300, f"{safe}::{reason or ''}")
# 4. Persist if violation
if not safe:
db = SessionLocal()
try:
db.add(Violation(text=req.text, reason=reason))
db.commit()
finally:
db.close()
return SafetyCheckResponse(safe=safe, reason=reason)
@app.get("/metrics")
async def metrics():
return generate_latest()
@app.get("/health")
async def health():
return {"status": "ok"}
Step 3 – Add Docker Compose
Create docker-compose.yml:
version: "3.8"
services:
api:
build: .
ports:
- "8000:8000"
environment:
- REDIS_URL=redis://redis:6379
- DATABASE_URL=postgresql://user:pass@db:5432/safety
depends_on:
- redis
- db
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: safety
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- pg_data:/var/lib/postgresql/data
ports:
- "5432:5432"
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
volumes:
redis_data:
pg_data:
Add prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'safety'
static_configs:
- targets: ['api:8000']
metrics_path: /metrics
Create a Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Step 4 – Run the Stack
docker compose up --build
The API will be available at http://localhost:8000, Prometheus at http://localhost:9090.
Running and Testing It
Health Check
curl http://localhost:8000/health
# {"status":"ok"}
Safety Endpoint
curl -X POST http://localhost:8000/check \
-H "Content-Type: application/json" \
-d '{"text":"hello world"}'
# {"safe":true,"reason":null}
curl -X POST http://localhost:8000/check \
-H "Content-Type: application/json" \
-d '{"text":"this is spam and hate"}'
# {"safe":false,"reason":"Contains banned word: spam"}
Verify Persistence
Open a PostgreSQL client:
docker compose exec db psql -U user -d safety -c "SELECT * FROM violations;"
You should see the rows for each unsafe request.
Observe Metrics
curl http://localhost:8000/metrics | grep safety_requests_total
Prometheus UI will show the same data in graph form.
Extending It: Your Roadmap to Senior-Level
- Plug in a Machine Learning Model – Replace the rule‑based filter with a transformer (e.g.,
distilbert-base-uncased) served via TorchServe. Why it matters: Shows you can integrate inference pipelines and manage model latency. - Add Rate Limiting & Quotas – Use Redis
INCRwith TTL to enforce per‑user limits. Why it matters: Prevents abuse and demonstrates API governance. - Horizontal Scaling with Kubernetes – Deploy the API as a Deployment with multiple replicas behind a Service. Why it matters: Proves you understand stateless scaling and load distribution.
- Distributed Tracing – Instrument with OpenTelemetry and export to Jaeger. Why it matters: Enables end‑to‑end latency analysis in microservice architectures.
- Circuit Breaker Pattern – Wrap Redis/DB calls with a library like
py‑circuitto fail fast on downstream outages. Why it matters: Improves fault tolerance and prevents cascading failures. - Benchmarking & Load Testing – Use
locustto simulate 1000 concurrent users and capture throughput/latency curves. Why it matters: Provides data‑driven evidence of performance for capacity planning.
Key Takeaways
- A portfolio project should showcase API design, caching, persistence, observability, and deployment in a single, coherent system.
- Containerization with Docker and orchestration via
docker‑compose(or later Kubernetes) signals production readiness. - Metrics and logging are not afterthoughts—they are first‑class citizens, proving you can monitor and debug in real time.
- The architecture is stateless at the API layer, making horizontal scaling straightforward and interview‑friendly.
- Extending the service with ML, rate limiting, tracing, and fault‑tolerance patterns demonstrates a path from junior to senior engineering.
Further Reading
- FastAPI Documentation – Build production APIs with automatic OpenAPI docs.
- Redis Command Reference – Deep dive into caching, rate limiting, and pub/sub patterns.
- PostgreSQL Performance Tuning – Indexing and query optimization for audit tables.
- Prometheus Monitoring Best Practices – Instrumenting microservices with metrics.
- Kubernetes Deployment Guide – Scaling stateless services horizontally.
- OpenTelemetry Specification – Standard for distributed tracing and metrics collection.