Curator API : Content Management
Sole Engineer

Curator API : Content Management

The Architecture

Every project in my professional stack reaches for TypeScript, Next.js, and NestJS. Curator API was built deliberately outside that comfort zone — a Python-first backend to stress-test my understanding of REST API design principles independent of the frameworks I already know well.

The domain is a content curation platform: authenticated users can submit URLs, the service auto-fetches the page title via HTTP scraping, and users can tag, organize, and discover content through a personalized feed. It's a bounded, realistic problem — complex enough to require relational data modelling, but not so large that architectural decisions get obscured.

The data model is the most interesting part. Content and tags share a true many-to-many relationship via a join table, managed entirely through SQLAlchemy ORM with Alembic handling schema versioning. The tag-follow system — where users subscribe to tags and receive a personalized feed — required a second many-to-many (users ↔ tags) and a feed query that joins across three tables with deduplication logic.

FastAPI was chosen deliberately over Flask or Django REST Framework — its Pydantic-native request/response validation, automatic OpenAPI schema generation, and async-first design map closely to patterns I know from NestJS, making the conceptual translation easier to reason about and document.

Authentication follows the same JWT + Passlib pattern used in production work — bcrypt-hashed passwords, signed access tokens, and a reusable dependency injection guard on all protected routes. Docker Compose orchestrates the FastAPI app and PostgreSQL instance locally, making the dev environment reproducible in a single command.

Strategic Methodology

Schema-first development — the PostgreSQL data model and Alembic migrations were written before any route was built. This forced deliberate thinking about the many-to-many relationships and feed query logic upfront rather than retrofitting. Each feature (auth, content CRUD, tagging, feed) was built and tested as an isolated module before wiring together.

Engineering Challenges

  • Modelling the many-to-many content ↔ tags relationship cleanly in SQLAlchemy — avoiding the ORM's tendency to generate N+1 queries when loading a content item with its associated tags, solved with eager loading via joinedload.
  • Designing the personalized feed query — joining content, tags, and user-followed-tags across three tables while deduplicating content that matches multiple followed tags. Written as a single optimised SQLAlchemy query rather than in-memory Python logic.
  • Auto-fetching page titles from submitted URLs without blocking the request — handled with httpx in async mode, with a graceful fallback to a null title if the target URL is unreachable or returns non-HTML content.
  • Translating NestJS guard/interceptor patterns into FastAPI's dependency injection system — understanding how FastAPI's Depends() chain replicates the constructor injection model was the steepest part of the learning curve.

Project Impact

"Built to learn — a deliberately over-engineered content curation backend exploring FastAPI, PostgreSQL, and Python-first architecture as a counterweight to a TypeScript-heavy professional stack."

Core Arsenal

Python 3.10+FastAPIPostgreSQLSQLAlchemyAlembicJWTPasslib / bcryptUvicorn
STATUS: DEPLOYED
Check Live
Intelligence Unit

Technical Log.

A high-fidelity breakdown of the build's architectural achievements and performance markers.

Synthesis

"A Python/FastAPI REST API backend for a content curation platform — featuring JWT auth, URL submission with auto title-fetch, many-to-many content tagging, tag-follow subscriptions, and a personalised feed endpoint. Python-first backend architecture"

Hard Evidence

SQLAlchemy joinedload eliminating N+1 on content-tag queries
-table DISTINCT feed query with zero in-memory filtering
httpx async title-fetch with graceful null fallback
Alembic versioned migrations from day one
Passlib bcrypt + JWT auth pattern matching production-grade implementations
Marker 1

Proves stack versatility — Python, FastAPI, and PostgreSQL alongside a professional TypeScript/NestJS background signals genuine backend depth, not framework dependency.

Marker 2

Schema-first discipline — Alembic migrations written before routes, enforcing intentional data modelling over ad-hoc schema evolution.

Marker 3

Many-to-many feed query engineered as a single optimised SQLAlchemy join — no N+1, no in-memory filtering.

Marker 4

Async URL title-fetching with httpx — non-blocking, gracefully degraded, production-pattern even in a learning context.

Marker 5

Docker Compose dev environment — single-command local setup, same pattern used in production-grade deployments.

Query Archive

01Why FastAPI over Django REST Framework or Flask for this project?
FastAPI's Pydantic-native validation and dependency injection system mirrors NestJS patterns closely — it made the cross-ecosystem learning more direct. DRF's magic and Flask's minimalism both obscure the underlying HTTP mechanics in different ways; FastAPI keeps them visible.
02What does the personalised feed endpoint actually do under the hood?
It queries content items whose associated tags overlap with the authenticated user's followed tags — a three-table join (content → content_tags → user_tag_follows) with DISTINCT to prevent duplicate content appearing for multi-tag matches. Paginated, sorted by submission date descending.
03Why Alembic instead of letting SQLAlchemy auto-create tables?
04Why Alembic instead of letting SQLAlchemy auto-create tables?
Auto-create works for demos, not for anything you'd ever evolve. Alembic gives you versioned, reversible schema migrations — the same discipline you'd apply in any production PostgreSQL project. It was a conscious choice to build learning-project habits that transfer directly to real work.
05Is this project complete?
Core MVP features — auth, content CRUD, tagging, and feed — are implemented. Planned extensions include rate limiting on the URL fetch endpoint, tag-based content search with full-text PostgreSQL queries, and a read/unread tracking layer. Not deployed; built to learn, not ship.