Skip to content

FastAPI + CUBRID API Service Template

Overview

Production-ready starter template for building REST APIs with FastAPI, SQLAlchemy 2.0, and CUBRID 11.2. It is intentionally lean: sync SQLAlchemy session lifecycle, clean repository pattern, and Docker-first local setup.

Quick Start

  1. Copy this template and move into the directory.
  2. Create your environment file:
cp .env.example .env
  1. Start services:
docker compose up --build
  1. Open:
  2. API: http://localhost:8000
  3. Swagger UI: http://localhost:8000/docs
  4. Health: http://localhost:8000/health

What is published, and resetting the data

Compose publishes the CUBRID broker (127.0.0.1:33000) and the API (127.0.0.1:8000) on this host only. The lab dba account has no password, so the broker is never published on other interfaces. To let other machines reach the API on purpose, set API_HOST_IP (for example API_HOST_IP=0.0.0.0 docker compose up --build, or a line in .env).

The database lives in the named volumes cubrid_data and cubrid_databases. docker compose down keeps it; docker compose down -v deletes it, which resets the template to an empty database.

Upgrading a stack started from an earlier compose file

CUBRID records the host name of the container that created the database. Earlier files gave the cubrid service no fixed host name (before #413) or the host name cubrid-api-service, and named the containers cubrid-db and fastapi-service. The current file sets CUBRID_DB_HOST: localhost and leaves the container names to Compose. It recreates the container but keeps the database in its volume, so the health check fails with -353 Failed to connect to database server and the API does not start. Point the database at localhost once, from this folder:

docker compose up -d cubrid
docker compose exec -T -u cubrid cubrid sh -c 'sed -i -E "s/^(testdb[[:space:]]+[^[:space:]]+[[:space:]]+)[^[:space:]]+/\1localhost/" "$CUBRID_DATABASES/databases.txt"'
docker compose restart cubrid
docker compose up -d

If the data is disposable, run docker compose down -v and then docker compose up --build instead, to start with a fresh database.

Project Structure

app/
├── main.py          # FastAPI app factory, lifespan, CORS
├── config.py        # pydantic-settings based configuration
├── database.py      # SQLAlchemy engine/session/base
├── models.py        # cookbook_items, cookbook_categories
├── schemas.py       # Request/response models
├── crud.py          # Repository pattern CRUD logic
└── routes/
    ├── health.py    # GET /health
    └── items.py     # Item CRUD endpoints

API Endpoints

  • GET /health → {"status": "ok", "database": "connected"}
  • GET /items?skip=0&limit=20 → paginated item list
  • GET /items/{item_id} → single item
  • POST /items → create item
  • PUT /items/{item_id} → update item
  • DELETE /items/{item_id} → delete item

Configuration

Environment variables (see .env.example):

  • DATABASE_URL (default cubrid+pycubrid://dba@cubrid:33000/testdb)
  • APP_HOST (default 0.0.0.0)
  • APP_PORT (default 8000)

Additional settings in app/config.py:

  • APP_NAME
  • DEBUG
  • CORS_ORIGINS

Development

  • Tables are auto-created at startup via Base.metadata.create_all().
  • SQLAlchemy access is fully parameterized through ORM/query builder usage.
  • Keep this template starter-focused: no auth, no migrations, no framework over-abstraction.
  • Run locally without Docker:
pip install -r requirements.txt
uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
  • Do not add index=True to primary key columns: CUBRID rejects a second index on columns a primary key or UNIQUE constraint already indexes, so create_all() fails at startup.
  • Each recipe under recipes/ ships a test suite that runs against live CUBRID when CUBRID_TEST_URL is set and falls back to in-memory SQLite otherwise. See the Test section of each recipe's README.