Lesson 1: From Project Setup to Production-Style API

Lesson 4 60

Week 1: Project Setup and Backend Foundations — A Production-Style FastAPI Stack with a Learning Layer

Introduction

Most “week 1” backend projects stop at scaffolding, a couple endpoints, and a todo list. This one is more interesting: it turns the first week of curriculum into a single Docker-run system with clear boundaries, real persistence, caching, and an engineer-facing dashboard—without spawning seven separate apps.

You’ll walk away with a concrete blueprint for structuring a FastAPI service as product workflows plus a learning layer, backed by PostgreSQL + Redis + MongoDB, and operated through a single docker compose up.

System overview

This project is a unified AI quiz platform with two API surfaces:

  • Product layer (/api/v1): real workflows engineers ship—JWT auth, quiz CRUD, business rules, and session lifecycle.

  • Learning layer (/learn/lesson-0N): lesson-shaped endpoints that expose the curriculum as structured routes inside the same runtime.

Under the API sits a small composition core (core/) that owns domain rules, data access boundaries, and orchestration. The week’s topics show up as live modules: requirements and structure become predictable routes and tooling, architecture design becomes layering and dependencies, schema design becomes durable storage, and the “locked” lessons become full auth/quiz/session behaviors behind the product API.

Architecture Diagram

Component Architecture

System Architecture: Quiz Platform Frontend (Static SPA) FastAPI Backend Product API (/api/v1) + Learning API (/learn/lesson-0N) Composition Core (core/) Orchestration, domain rules, repositories, session manager Auth Module JWT + Mongo users Quiz Module Postgres + Redis cache Session Module Attempts + Redis TTL MongoDB (quiz_platform) PostgreSQL (quizzes, attempts) Redis (cache + sessions)

Engine / core system design

The core idea is to keep dependencies one-way:

  • backend/app/... (FastAPI routers) depend on…

  • core/... (composition + domain services) which depends on…

  • database clients (Postgres/Redis/Mongo) and DTOs

This makes it possible to keep /learn/... and /api/v1/... as separate route trees while sharing the same execution engine.

The engine-style composition is not a “framework inside a framework”. It’s a disciplined set of services with explicit wiring at the API edge.

python
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession

from app.shared.database.postgres import get_db_session
from app.shared.database.redis_client import get_redis
from core.quiz.data_service import QuizDataService
from core.quiz.domain_service import QuizDomainService
from core.quiz.repository import QuizRepository

async def quiz_domain_service(
    db: AsyncSession = Depends(get_db_session),
    redis = Depends(get_redis),
) -> QuizDomainService:
    repo = QuizRepository(db, redis)
    return QuizDomainService(repo, QuizDataService(repo))

That one constructor boundary is where “system architecture design” becomes real: you can swap caching strategies, add observability, or enforce business rules without touching routers.

Data Flow Diagram

Flowchart

Client curl / dashboard FastAPI /api/v1/* /learn/* Core Engine services + repos rules + orchestration Auth (MongoDB) Quiz Data (PostgreSQL) Cache/Sessions (Redis) HTTP DTOs verify JWT CRUD TTL session result JSON

API design

The API surface is small on purpose: a few endpoints that represent end-to-end behavior and keep the rest internal.

1) Register

POST /api/v1/auth/register
Request: { "username": "string", "email": "string", "password": "string" }
Response: { "message": "string", "user": { "id": "string", "username": "string", "email": "string" } }
Role: creates an account in MongoDB and prepares the user for JWT flows.

2) Login

POST /api/v1/auth/login
Request: { "username": "string", "password": "string" }
Response: { "access_token": "string", "token_type": "bearer", "expires_in": 1800 }
Role: issues a JWT and establishes identity for product workflows.

3) Create a quiz

POST /api/v1/quizzes/
Request: { "title": "string", "category": "string", "difficulty": "string", "questions": [ ... ] }
Response: { "id": 1, "title": "string", "is_published": false, "questions": [ ... ] }
Role: persists quiz data in Postgres and uses Redis for query caching.

4) Start and complete a session

POST /api/v1/sessions/
Request: { "quiz_id": "1" }
Response: { "id": "uuid", "user_id": "string", "quiz_id": "string", "status": "started", "version": 1 }
Role: creates an attempt row (Postgres) and a cached session record (Redis).

POST /api/v1/sessions/{session_id}/complete
Request: {}
Response: { "message": "Session completed", "session_id": "uuid" }
Role: flips attempt status to completed and drives “Completed attempts” metrics.

The delegation pattern is straightforward: routers authenticate, parse DTOs, then call core services.

python
from fastapi import APIRouter, Depends
from core.contracts.quiz import QuizCreate, QuizResponse
from app.shared.middleware.auth import get_current_user
from core.contracts.user import UserResponse

router = APIRouter()

@router.post("/", response_model=QuizResponse)
async def create_quiz(
    data: QuizCreate,
    user: UserResponse = Depends(get_current_user),
    svc = Depends(quiz_domain_service),
):
    return await svc.create_quiz(data, user.id)

State Machine Diagram

State Machine

Idle Receiving Request /api/v1 or /learn Auth Check Execute Workflow core services Respond JSON + status Error 401/403/500 HTTP request route match JWT required valid token result exception error response request complete

Why this architecture holds up

This structure scales because it makes the composition boundary explicit:

  • Adding new product features becomes adding new core services and thin routers.

  • Adding new lesson routes becomes adding new learning modules that call the same core, without contaminating product code paths.

  • The persistence split (Mongo for identity, Postgres for quiz/sessions, Redis for cache) is a deliberate trade-off: you gain operational clarity and speed, but you accept cross-store consistency work. In this project, that shows up most clearly around user identity: the quiz tables store creator_id as a string, and you rely on JWT identity rather than foreign keys into MongoDB.

Conclusion

Week 1 becomes meaningful when scaffolding turns into a system you can operate: one runtime, one compose stack, and clear boundaries between learning routes and production workflows. This project’s core shows how to keep FastAPI thin, core services testable, and persistence intentional.

Next step: add one more product workflow (e.g., scoring on completion) and surface it as both a product endpoint and a lesson route—without changing the engine’s dependency direction.

Questions & Discussion

Leave a Reply

Your email address will not be published. Required fields are marked *