FastAPI + CUBRID quickstart¶
A small web API that saves items in CUBRID. You start a disposable local database in Docker, run the API, create an item with an HTTP request, read it back, then stop and clean up. Everything you need is on this page.
Plan on about 5 minutes once Docker is installed and its images are downloaded; the first download and build take longer on a slow connection. New to CUBRID itself? Do your first CUBRID database from Python first: it explains the database, the connector and transactions.
Korean: FastAPI 빠른 시작
What you need¶
- Linux or macOS with
bashorzsh. On Windows, use a WSL 2 terminal. - Docker with Compose v2:
docker compose versionmust print a version. gitandcurl.- Python 3.11 or later only for way B below (the API on your machine) and for the offline tests. Way A needs no Python on your machine.
The CUBRID image is published for x86_64 (linux/amd64); on an Apple Silicon
Mac, Docker runs it under emulation. This page was checked on Linux x86_64.
1. Get the code¶
Run these from the folder where you keep projects:
git clone https://github.com/cubrid-lab/cubrid-cookbook-python.git
cd cubrid-cookbook-python
cd quickstart/5min-fastapi
Every later command on this page runs from this quickstart/5min-fastapi
folder. If you open a new terminal, cd back into it first.
2. Start the database and the API¶
Choose one way. Both serve the API on http://localhost:8000, so never run
both at the same time.
| A. Everything in Docker (recommended) | B. API on your machine | |
|---|---|---|
| Database | container cubrid |
container cubrid |
| API | container api |
uvicorn in your terminal, from a Python venv |
| Use it when | you just want to see it work | you want to edit app.py and reload |
A. Everything in Docker (recommended)¶
This builds the API image from the Dockerfile, starts the database, waits
until its health check passes, then starts the API and waits until it answers.
Checkpoint. docker compose up returns without an error, and
docker compose ps lists both services as healthy:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
5min-fastapi-api-1 5min-fastapi-api "uvicorn app:app --h…" api 24 seconds ago Up 6 seconds (healthy) 127.0.0.1:8000->8000/tcp
5min-fastapi-cubrid-1 cubrid/cubrid:11.2 "/entrypoint.sh" cubrid 25 seconds ago Up 22 seconds (healthy) 127.0.0.1:33000->33000/tcp
The times differ on your machine. Inside Docker, the API reaches the database
by its Compose service name, cubrid: docker-compose.yml sets
CUBRID_HOST=cubrid for the api container. From your own machine, both
services are at localhost, on the ports in the PORTS column.
B. API on your machine, database in Docker¶
If you already started way A, first stop its API container, which holds port
8000: docker compose stop api.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
docker compose up -d --wait cubrid
uvicorn app:app --reload
- The venv keeps the API's packages (
fastapi,uvicorn,pycubrid) inside this folder. In a new terminal, runsource .venv/bin/activateagain. docker compose up -d --wait cubridstarts only the database and waits until its health check passes.uvicorn app:app --reloadruns the API in this terminal and restarts it when you saveapp.py. Here the API runs on your machine, so it connects tolocalhost:33000, the port Docker publishes for the database:localhostis the default whenCUBRID_HOSTis not set.
Checkpoint. Uvicorn keeps running and prints, after a line naming the folder it watches:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [2993868] using StatReload
INFO: Started server process [2993873]
INFO: Waiting for application startup.
INFO: Application startup complete.
The process numbers differ on your machine.
Leave it running and use a second terminal, in the same folder, for step 3.
3. Create an item and read it back¶
The API stores items in its own table, cookbook_quickstart_items, which it
creates at startup if it does not exist. An item has a text val; the
database assigns its id.
First ask the API whether it can reach the database. GET /health runs
SELECT 1 FROM db_root on a new connection:
Checkpoint.
If the database does not answer, the same request returns HTTP status 503
with {"status":"error","database":"unavailable"}, and no error details.
Create one item:
curl -sS -X POST localhost:8000/items -H 'Content-Type: application/json' -d '{"val": "hello from the quickstart"}'
Checkpoint. On the first run:
Read it back by its id:
curl -sS localhost:8000/items lists every item. The id is the database's
AUTO_INCREMENT value: it is 1 only in a new table. The table keeps its rows
until you drop it (step 4), so a later run returns the next number, for
example "id":2; read that id back instead. In a browser,
http://localhost:8000/docs shows the same API as interactive documentation.
4. Stop, run again and clean up¶
Stop the API.
- Way A:
docker compose stopstops both containers. - Way B: press Ctrl-C in the Uvicorn terminal, then
docker compose stopstops the database. Rundeactivateto leave the venv.
Run it again. Way A: docker compose up -d --build --wait. Way B:
docker compose up -d --wait cubrid, then uvicorn app:app --reload in the
activated venv. Your items are still there.
Remove the quickstart's table, and nothing else, while the database is running:
docker compose exec -T cubrid csql -u dba testdb -c "DROP TABLE IF EXISTS cookbook_quickstart_items"
It prints Execute OK. and Committed. with timings. The API creates the
table again the next time it starts.
Remove the containers. Choose how much to delete:
| Command | Containers | Data |
|---|---|---|
docker compose stop |
stopped; docker compose start resumes them |
kept |
docker compose down |
removed | kept in this quickstart's Docker volumes; the next up brings it back |
docker compose down -v |
removed | deleted, including the testdb database |
These commands act only on this folder's Compose project, named
5min-fastapi after the folder: its two containers, its network and, with
-v, its two volumes (5min-fastapi_cubrid_data,
5min-fastapi_cubrid_databases). They never touch other containers. Way B's
venv is the .venv folder; delete it with rm -rf .venv.
Port 33000 is shared. This quickstart, the
SQLAlchemy quickstart and the repository's root
lab (the cubrid service of the root docker-compose.yml) all publish their database on
127.0.0.1:33000. Each is a separate Compose project with its own data, so
only one can run at a time. Before you switch, run docker compose stop in the
folder of the one you are leaving.
If something goes wrong¶
docker: command not found, ordocker compose versionfails: install Docker with Compose v2 (Get Docker), and make sure the Docker daemon is running.Bind for 127.0.0.1:33000 failed: port is already allocated(or8000): something else holds that port. Find it withdocker ps --filter publish=33000(orpublish=8000). If it is one of this repository's labs, stop it from its own folder withdocker compose stop; otherwise stop the program you started yourself. Do not remove containers or volumes you did not create. A failed start leaves this project's containers behind; rundocker compose downhere before you try again.- Uvicorn stops with
[Errno 98] error while attempting to bind on address ('127.0.0.1', 8000): [errno 98] address already in use(way B): the API container of way A, or another program, holds port 8000. Rundocker compose stop apihere, or stop the other program. - The database does not become healthy (
docker compose up --waitreportsunhealthy, or waits a long time): the first start createstestdband can take a minute. Checkdocker compose logs cubridfor the reason. ModuleNotFoundErrororuvicorn: command not found(way B): the venv is not active. Runsource .venv/bin/activate, thenpython -m pip install -r requirements.txtagain.- Uvicorn stops with
pycubrid.exceptions.OperationalError: failed to connect to CUBRID brokerandApplication startup failed: no database answers onlocalhost:33000. Start it withdocker compose up -d --wait cubrid. - A database or login error: the app connects to the database
testdbasdbawith an empty password, the settings this Compose file creates. Another CUBRID server on port 33000 has different ones; stop it as described above. -353 Failed to connect to database serverafter you update from an earlier Compose file: that file registered the database for the hostcubrid-fastapi, and the database in your volumes still names it. The data is kept; point it atlocalhostand restart. Do not rundocker compose down -v, which deletes it.
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 --wait
More fixes: the CUBRID quirks and pitfalls and the known issues.
Local only. The lab's
dbaaccount has no password, so both ports listen on127.0.0.1only. Never use these settings for a shared or remote server.
How the app works¶
app.py creates its table in FastAPI's lifespan startup hook. Blocking database
work runs in a thread, including the synchronous request handlers. Each database
operation commits on success, rolls back on failure, and closes its cursor and
connection. The ? in its SQL is a parameter placeholder: pycubrid puts each
value into the statement as a safely escaped literal.
Test without a database¶
With the venv of way B active:
The tests replace the DB-API connection and cover host configuration, startup,
CRUD and /health responses, and resource cleanup after database failures.
tests/test_live.py is skipped here: it needs a database (next section).
Test against the database¶
tests/test_live.py runs the same app against a running CUBRID and checks each
answer against the table through a separate connection: /health (200, and
503 when the database cannot be reached), creating an item, reading it by
id and in the list, 404 for a missing id, 422 for an invalid body with
no row written, and a rejected insert that leaves no row. Each test writes only
rows whose val starts with its own random live-qs- marker and deletes them
afterwards, so it never touches your items and can run again.
With the database of step 2 running and the venv active:
The app always connects to port 33000 and database testdb, so the URL may
change only the host. Without CUBRID_TEST_URL these tests skip; with it set
and no database answering, they fail. From the repository root,
make verify-quickstart (also part of the full make verify) runs them against
localhost:33000 with CUBRID_LIVE_GATE=1, which turns a skip into a failure, and
writes a JUnit report and a summary to .live-evidence/quickstart/.
CI runs the same commands: the full make verify on CUBRID 11.4 for every push
to main and the daily schedule, on CUBRID 11.2 and 11.4 for the weekly
schedule, manual runs and release verification, and
make verify-quickstart on CUBRID 11.4 for pull requests that change this
quickstart. make test runs the offline tests.
What next?¶
- A production-shaped API, with connection reuse through SQLAlchemy: the FastAPI service template.
- Core database patterns (CRUD, transactions, errors): the fundamentals.
- SQLAlchemy without a web framework: the SQLAlchemy quickstart.