This is a clean and minimal Python REST API template built with FastAPI and Uvicorn. It is designed to be used as a starting point for small API services, standalone HTTP backends that bots and other clients call over the network instead of bundling heavy dependencies locally.
- FastAPI with automatic OpenAPI documentation at
/docs. - Bearer token authentication shared across all protected endpoints. A missing or wrong token gets
401 Unauthorized, and tokens are compared in constant time. - A
GET /healthendpoint for uptime monitoring, which the Docker image also uses as its health check. - Configuration via environment variables using
pydantic-settings. No values are hardcoded, and the service refuses to start with a placeholder or short secret. - Structured JSON logging with a configurable log level. Every line is one JSON object, including uvicorn's access log.
- One version, set in
pyproject.toml, which the health endpoint and the OpenAPI docs read from the installed package. pyproject.tomlwith thehatchlingbuild backend, managed with uv and locked inuv.lock.Dockerfileanddocker-compose.ymlfor containerized deployment.- A test suite with
pytestcovering health, auth rejection, and a template endpoint. - Tests, linting, formatting, strict type checking, and a Docker build run in CI on every push to
mainand on every pull request through the shared dev-standards workflow.
- Docker and Docker Compose for containerized setup.
Running without Docker requires Python 3.12 and uv. On Windows, install uv with winget install --id astral-sh.uv.
The setup script prepares the project in a single run, and it is safe to run again at any time.
On Windows, double-click setup.bat or run it from a terminal:
setup.bat
On macOS or Linux, run the following commands:
chmod +x setup.sh
./setup.sh
The script asks before it installs anything, and it does the following:
- It installs uv when uv is missing. uv also provides Python 3.12 when the machine lacks it.
- It offers to install Docker, and in a Git clone also the tools that the Docker image includes for running outside Docker. It uses winget on Windows, Homebrew on macOS, and the system package manager on Linux. On Windows it also turns on WSL, which Docker Desktop needs, and says when Windows needs a restart or virtualization is turned off in the firmware. It shows the computer's RAM and offers to cap the memory of Docker Desktop's virtual machine, at a suggested or your own size.
- In a Git clone, it runs
uv sync, which installs the package and its locked dependencies into.venv. - It copies
.env.templateto.envon the first run and fillsAPI_SECRETwith a random value.
A step that fails says what went wrong, why it matters, and what to do next, and the summary at the end lists it again.
The steps live in scripts/bootstrap.py, which needs only the Python standard library.
If you prefer to perform the setup manually, follow these steps:
uv sync
cp .env.template .env
# Edit .env and set API_SECRET and other values as needed.
uv run uvicorn api_template.main:app --reloadAfter setup has run once, the run script starts the API.
Double-click run.bat on Windows, or run ./run.sh on macOS and Linux.
It starts the API in Docker in the background, waits until its health check passes, and shows its status.
A downloaded release runs the image that GitHub publishes for every release, and a Git clone builds the image from its own files.
The container then starts again whenever Docker starts.
The script also takes an action, such as run.bat stop on Windows or ./run.sh stop elsewhere:
| Action | What it does |
|---|---|
start |
Starts everything in Docker and waits until it is ready. It is the default. |
stop |
Stops the containers. They stay stopped until the next start. |
status |
Shows whether each container runs and is healthy, its version, and whether updates are automatic. |
logs |
Follows the logs. Press Ctrl+C to stop following. |
update |
Installs the newest release. In a Git clone, it pulls the latest code, rebuilds on fresh base images, and restarts. |
schedule |
Installs new releases automatically every night at 04:00. |
unschedule |
Stops installing new releases automatically. |
local |
Runs the project in the terminal without Docker. Press Ctrl+C to stop it. |
When a service crashes right after it starts, the script shows the end of its log and stops it, so it does not restart over and over.
In a downloaded release, the update action downloads the newest image and takes the run, setup, and compose files from it.
.env and the other settings stay as they are.
When the new release fails to start, it puts the previous image and files back, and later updates skip that release.
The schedule action runs this update every night at 04:00, and each run writes what it did into update.log.
Alternatively, you can run the API as a Docker container.
-
Copy
.env.templateto.envand setAPI_SECRET. -
Build and start the container:
docker compose up --build
The API listens on port 8000 by default.
The service keeps no state, so the container needs no volume.
All configuration is read from environment variables or from a .env file in the project root.
| Variable | Required | Default | Description |
|---|---|---|---|
API_SECRET |
Yes | None | Shared bearer token of at least 16 characters. Callers must send this value in the Authorization header. Generate one with python -c "import secrets; print(secrets.token_urlsafe(32))". |
LOG_LEVEL |
No | INFO |
Log verbosity. Accepts DEBUG, INFO, WARNING, ERROR, or CRITICAL. Every log line, including uvicorn's access log, is one JSON object. |
api-template/
├── src/api_template/
│ ├── main.py # FastAPI application, lifespan, and route definitions.
│ ├── config.py # This service's settings on top of ServiceSettings.
│ ├── service.py # Shared settings, secret validation, and the version lookup.
│ ├── logging_config.py # JSON logging for every logger, including uvicorn's.
│ ├── auth.py # Bearer token dependency used to protect endpoints.
│ └── models.py # Pydantic request and response models.
├── tests/
│ ├── test_api.py # Tests for this service's own routes.
│ └── test_shared.py # Tests for the shared auth, settings, logging, and version code.
├── Dockerfile
├── docker-compose.yml
├── pyproject.toml # Project metadata and dependencies.
├── uv.lock # Locked dependency versions.
├── ruff.toml # Lint and format settings on top of the shared baseline.
├── .template-manifest.toml # Core files that derived APIs keep identical to this template.
├── setup.bat # Windows setup script.
├── setup.sh # macOS and Linux setup script.
├── scripts/bootstrap.py # The steps that both setup scripts run.
├── scripts/run.py # The actions that both run scripts take.
├── run.bat # Windows run script.
├── run.sh # macOS and Linux run script.
└── .env.template # Template for environment variables.
Use the GitHub template button to create a new repository based on this project. Then follow these steps to customize it:
-
Rename the package. In
pyproject.toml, change the projectnameand thepackagespath under[tool.hatch.build.targets.wheel]. Rename thesrc/api_template/directory to match (e.g.src/media_api/). The source files import each other relatively, so onlytests/test_api.pyand theCMDline inDockerfilename the package. Also set the new name aspackageunder[tool.dev-standards.template]inpyproject.tomland asknown-first-partyinruff.toml. SetSERVICEinmain.pyto the new project name, because the version is read from the installed project of that name. Then runuv syncsouv.lockrecords the new name. -
Add your dependencies. Edit the
dependencieslist inpyproject.toml. -
Add configuration variables. Extend the
Settingsclass inconfig.pyand document new variables in.env.template. -
Add startup and shutdown logic. Use the
lifespancontext manager inmain.pyto load models, open connections, or perform any one-time initialisation. -
Add endpoints. Define new routes in
main.py. Usedependencies=[Depends(require_auth)]to protect them. Add request and response models tomodels.py. -
Remove the template endpoint. Delete the
/template/echoroute and theTemplateRequest/TemplateResponsemodels once you have your own routes in place. -
Update the
docker-compose.ymlport. Change the host side of8000:8000to the port assigned to your service, such as8001:8000. The container itself keeps listening on port 8000. -
Write tests. Add test functions to
tests/test_api.py. Leavetests/test_shared.pyunchanged, because it is a core file.
uv run pytest
uv run mypy srcRun every lint and format check with uvx pre-commit run --all-files, or install the hooks once with uvx pre-commit install so they run on each commit.
The coding, prose, and commit conventions are documented in dev-standards.
All endpoints except /health require a bearer token in the Authorization header.
A request without the header or with a wrong token gets 401 Unauthorized with a WWW-Authenticate: Bearer header:
POST /template/echo HTTP/1.1
Authorization: Bearer your-secret-here
Content-Type: application/json
{"text": "hello"}A Python client calling this API can use httpx.AsyncClient with the token set as a default header:
import httpx
client = httpx.AsyncClient(
base_url="http://localhost:8000",
headers={"Authorization": "Bearer your-secret-here"},
)The following APIs were built from this template and can serve as fuller implementation examples.
| Service | Description |
|---|---|
| api-media | Resolves YouTube, SoundCloud, and Spotify track metadata and stream URLs. |
| api-scraper | Scrapes structured data from external websites. |
| api-scheduler | Schedules persistent reminders delivered via Discord webhooks. |
| api-morshu | Generates Morshu TTS audio and video from text. |
This project is licensed under the MIT License. You may use, change, and share it, as long as every copy keeps the copyright notice and the license text.