← Back to list

Building Reliable APIs With Pydantic: A Developer’s Journey Through the Courses-Portal Python API

Introduction

Vineet Sharma · 2025-12-04 13:22 · 0 claps · 5.0 min read
#pydantic #etag #python-programming #fastapi
Open on Medium ↗
Wiki topics: 💻 · Programming

Building Reliable APIs With Pydantic: A Developer’s Journey Through the Courses-Portal Python API

Introduction

When a Backend Grows Beyond CRUD

In the world of modern software development, Python frameworks such as FastAPI have completely redefined how quickly we can build production-ready APIs. But behind every clean API lies an even cleaner foundation — data validation, schema consistency, type safety, and predictable behavior, even as systems grow more complex.

Recently, while re-visiting the GitLab repo **Courses-Portal-API-Python**:

At first, things are peaceful: a few endpoints, a few JSON bodies, some simple validation logic.

But as requirements evolve — roles, permissions, metadata, video processing, dashboards, integrations — the data begins to rebel.

Suddenly:

  • Web clients send half-filled JSON structures
  • Mobile apps omit required fields
  • Browser caching causes outdated updates
  • Multiple instructors unknowingly edit the same course
  • Automated scripts ingest malformed data

The AI-Powered Video Tutorial Portal, built in the Courses-Portal-API-Python repository by Vineet Sharma, was no exception.

It needed structure, predictability, and safety.

And that’s when Pydantic stepped in — not as a helper, but as the backbone of the entire API.

Chapter 1 — Pydantic Arrives: Giving Shape to Chaos

Pydantic isn’t flashy.

It doesn’t try to replace your ORM, your API framework, or your business logic. Instead, it focuses on a single mission:

“Ensure that data entering your system is correct, typed, validated, and predictable.”

For this project, it became the silent architect that transformed sprawling JSON blobs into structured, meaningful Python objects.

Chapter 2 — The Core Models: Where the API Learns to Speak Clearly

Let’s start with a realistic model structure used in the Courses Portal backend.

These models are inferred from design patterns in the repository and typical FastAPI + SQLAlchemy architectures.

Course Schema (Pydantic)

from pydantic import BaseModel, Field, EmailStr
from typing import Optional, List
from datetime import datetime

class CourseBase(BaseModel):
    title: str = Field(..., example="Machine Learning 101")
    description: Optional[str] = Field(None)
    tags: List[str] = Field(default_factory=list)
    thumbnail_url: Optional[str] = None

class CourseCreate(CourseBase):
    author_email: EmailStr
    published: bool = False

class CourseUpdate(BaseModel):
    title: Optional[str] = None
    description: Optional[str] = None
    tags: Optional[List[str]] = None
    published: Optional[bool] = None

class CourseResponse(CourseBase):
    id: int
    created_at: datetime
    updated_at: datetime
    author_email: EmailStr
    published: bool
    class Config:
        orm_mode = True

What this model guarantees

  • All required fields must exist.
  • Wrong types are rejected automatically.
  • Optional fields become optional.
  • Response models convert ORM → JSON cleanly.
  • Automatic OpenAPI documentation generation.

Chapter 3 — How Pydantic Cleans the API Layer

Here’s a typical POST endpoint from the project:

@router.post("/courses", response_model=CourseResponse)
async def create_new_course(payload: CourseCreate):
    course = await course_service.create_course(payload)
    return course

No validation. No type checking. No defensive programming.

POST /courses — Create Course (Example)

from fastapi import APIRouter, HTTPException
from models.course import CourseCreate, CourseResponse
from services.course_service import create_course

router = APIRouter()
@router.post("/courses", response_model=CourseResponse)
async def create_new_course(payload: CourseCreate):
    try:
        course = await create_course(payload)
        return course
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

What’s happening under the hood:

  1. Incoming JSON is automatically validated
  • Missing fields → 422
  • Wrong types → 422
  • Bad emails → 422

2. Payload is automatically converted into a Python object

4 . Errors are automatically structured into JSON responses

Without Pydantic, this endpoint would require 40–50 lines of manual validation code.

Pydantic handled all of that before this function even runs.

Chapter 4 — The Lifecycle of a Data Request (Diagram)

Here’s how data flows through the API using Pydantic:

flowchart LR
    A[Client Sends JSON] --> B[Pydantic Validates Input]
    B -->|Valid| C[Service Layer]
    B -->|Invalid| X[422 Validation Error]
    C --> D[Database ORM]
    D --> E[Response Model via Pydantic]
    E --> F[Client Receives Clean JSON]

Pydantic acts as both a gatekeeper and a translator. Nothing enters or leaves the system without passing through it.

Chapter 5 — The Concurrency War: Why ETags Became Essential

As multiple instructors and admin tools began interacting with courses, a new challenge emerged:

“How do we prevent overwriting someone else’s update?”

Imagine:

  1. Instructor A loads course v3
  2. Instructor B loads course v3
  3. Instructor A updates (v4)
  4. Instructor B submits outdated data (still v3)
  5. B overwrites A without even realizing it

This scenario is dangerous in real systems — leading to:

  • Lost work
  • Confusion
  • Inconsistent UI
  • Broken metadata

The solution?

ETag-based Optimistic Concurrency Control

Chapter 6 — ETags in Action: A Practical Implementation

An ETag is essentially a version fingerprint.

GET returns ETag

@app.get("/courses/{id}")
async def get_course(id: int, response: Response):
    course = get_course_from_db(id)
    response.headers["ETag"] = course.etag
    return course

PATCH requires If-Match

@app.patch("/courses/{id}")
async def update_course(id: int, payload: CourseUpdate, if_match: str = Header(None)):
    course = get_course_from_db(id)

    if if_match != course.etag:
        raise HTTPException(
            status_code=412,
            detail="ETag mismatch - concurrent update detected"
        )
    updated_course = apply_updates(course, payload)
    updated_course.etag = generate_new_etag()
    save(updated_course)
    return updated_course

Chapter 7 — ETag Flow (Mermaid Diagram)

If the If-Match does NOT equal the current ETag, the server responds:

  • STATUS: 412 Precondition Failed

This prevents silent overwrites.

Chapter 8 — How Pydantic + ETags Form a Beautiful Symbiosis

Pydantic ETags Ensures data is correct Ensures data is fresh Validates structure Validates version Protects API from invalid payloads Protects data from race conditions Cleans controllers Prevents destructive updates Powers OpenAPI docs Powers concurrency safety

This combination isn’t accidental — It is a deliberate architectural design decision visible in the repository’s evolution.

Chapter 9 — High-Level System Architecture

Pydantic and ETags operate as bookends around the service layer:

  • One protects input
  • One protects updates

Chapter 10 — Patterns Reinforced Through This Architecture

1. Service Layer Pattern

Controllers stay lean. Services handle logic. ORM handles persistence.

2. DTO-Based Thinking

Data shapes behavior, not vice-versa.

3. Separation of Concerns

Validation ≠ business logic Business logic ≠ persistence Persistence ≠ transport

4. Predictability

Every API interaction is deterministic.

5. Developer Happiness

  • Error messages are clear.
  • Payloads are structured.
  • Docs are always accurate.

This leads to a backend that is:

  • Maintainable
  • Scalable
  • Easy to onboard into
  • Hard to misuse

Chapter 11 — About the Author and Coding Philosophy

Throughout the Courses-Portal-API-Python repository, a consistent philosophy emerges from the choices made by M. Vineet Sharma:

Clean structure

Predictable model-driven design

Safe concurrency decisions

Frameworks chosen for clarity (FastAPI + Pydantic)

Domain models separated from transport logic

The project wasn’t built just to work;

it was built to be understood.

And that makes it future-proof.

Chapter 12 — Final Reflection: A Story of Structure, Safety, and Thoughtful Engineering

This is a story about a backend that refused to fall into inconsistency.

A backend built with:

  • Pydantic enforcing correctness
  • ETags enforcing safety
  • Clear models enforcing structure
  • Clean endpoints enforcing maintainability
  • A thoughtful developer enforcing best practices

And through these choices, the Courses Portal backend became:

  • Reliable
  • Predictable
  • Scalable
  • Professional
  • Pleasant to develop on

It’s a reminder that great engineering doesn’t always require complexity — just the right tools used with intention.


메타데이터
post_id
70738817596c
slug
building-reliable-apis-with-pydantic-a-developers-journey-through-the-courses-portal-python-api-70738817596c
url
https://medium.com/@mvineetsharma/building-reliable-apis-with-pydantic-a-developers-journey-through-the-courses-portal-python-api-70738817596c
canonical_url
https://medium.com/@mvineetsharma/building-reliable-apis-with-pydantic-a-developers-journey-through-the-courses-portal-python-api-70738817596c
author_url
https://medium.com/@mvineetsharma
status
ok
fetched_at
2026-07-14 13:39:04