Skip to main content...
Python + Classical CV — the Catalog Tool
25 min

Day 13: Serving it: FastAPI endpoint on the droplet

From CLI tool to service

You already know REST APIs cold from NestJS. FastAPI's job today is narrow: wrap yesterday's process_image in one endpoint, deployed on the same droplet as the rest of the stack, callable from a Next.js page. Focus on what's actually new, not on relearning what an endpoint is.

What's actually different from NestJS

  • Route handlers are plain functions (often async def), not classes/decorators-on-methods
  • Request/response shapes are declared with Pydantic models — FastAPI's rough equivalent of a NestJS DTO + class-validator, but it also generates OpenAPI docs from them automatically
  • File uploads use UploadFile, an async-friendly wrapper around the incoming multipart data
  • This service is stateless and CPU-only for now — no need for the async DB/queue patterns you'd reach for in NestJS at this stage
src/catalog/api.py
from fastapi import FastAPI, UploadFile, HTTPException
from fastapi.responses import Response
import cv2
import numpy as np
from .pipeline import remove_background, resize_and_normalize

app = FastAPI(title="FitXpert Catalog Tool")

@app.post("/catalog/clean")
async def clean_garment_photo(file: UploadFile, size: int = 1024) -> Response:
    if file.content_type not in ("image/jpeg", "image/png"):
        raise HTTPException(400, "Expected a JPEG or PNG image")

    raw = await file.read()
    img = cv2.imdecode(np.frombuffer(raw, np.uint8), cv2.IMREAD_COLOR)
    if img is None:
        raise HTTPException(400, "Could not decode image")

    cutout = remove_background(img)
    result = resize_and_normalize(cutout, size)

    ok, encoded = cv2.imencode(".png", result)
    return Response(content=encoded.tobytes(), media_type="image/png")

@app.get("/health")
async def health():
    return {"status": "ok"}
Running it — same uvicorn ASGI server every FastAPI service in this roadmap uses
uv run uvicorn catalog.api:app --host 0.0.0.0 --port 8001

# smoke test
curl -X POST -F "file=@garment.jpg" http://localhost:8001/catalog/clean -o cleaned.png

Calling it from Next.js

A minimal Next.js page action calling the Python service — same cross-service call shape you'll reuse for every Python inference service through Stage 6
async function cleanGarmentPhoto(file: File): Promise<Blob> {
  const formData = new FormData();
  formData.append('file', file);

  const res = await fetch(process.env.CATALOG_SERVICE_URL + '/catalog/clean', {
    method: 'POST',
    body: formData,
  });
  if (!res.ok) throw new Error(`Catalog service error: ${res.status}`);
  return res.blob();
}

The shape that repeats through Stage 6

NestJS/Next.js stays the gateway and UI; a Python (FastAPI) service does the actual inference work behind it. Today's tiny CPU-only endpoint is architecturally identical to Stage 1's classifier service, Stage 2's measurement engine, and Stage 3's stylist — only the model inside changes.

Key terms

Pydantic model
FastAPI's data-validation/serialization class — declares request/response shapes and auto-generates OpenAPI docs from them.
UploadFile
FastAPI's async-friendly wrapper for an incoming multipart file upload.
uvicorn
An ASGI server that runs FastAPI (and other async Python web) applications.

In this roadmap's architecture, what role does FastAPI play relative to NestJS/Next.js?

We use cookies

We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of cookies. Learn more

    Day 13: Serving it: FastAPI endpoint on the droplet | RBTechIconX