fastapi-nano: a FastAPI template with Flask's directory layout
🐍 Simple FastAPI template that mimics Flask's blueprint directory structure
At a glance
- What is it?
- fastapi-nano is a small FastAPI template whose organising idea is to borrow Flask's blueprint structure, so the code for each API lives in its own package and the routes are assembled in one place. It ships the parts a new service usually ends up needing anyway, including OAuth2 with Argon2 hashing, Bearer JWTs, Caddy for reverse proxying, Docker images for two Python versions, and a working test, lint and type check setup.
- Who is it for?
- fastapi-nano fits a developer starting a small to medium FastAPI service who wants the boring parts already decided, a directory layout that scales past a single main file, and a local run that takes one make target. It does not fit a team that needs a large application framework, a message queue, or a database layer, because the template deliberately stops at the HTTP boundary.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The one idea: Flask's layout, FastAPI's runtime
fastapi-nano is a simple FastAPI template that follows Flask's blueprint directory structure, and the README says the whole thing in a sentence. The project is also listed as a boilerplate in the awesome-fastapi list, so its audience is people choosing a starting point rather than people reading about a framework.
The blueprint idea is worth explaining, because it is the reason to pick this template over the default FastAPI example. In Flask, a blueprint is a way to group a set of routes and their handlers into a named unit that can be registered with the application. A FastAPI project that copies that shape ends up with each API in its own package, its own main module, and the endpoints assembled in a single routes file.
The features list is short and every entry is a decision somebody would otherwise have to make. The endpoints are built with FastAPI. The app is served with the FastAPI CLI, using fastapi dev locally and fastapi run in the container, both running Uvicorn under the hood. Reverse proxying is handled with Caddy. Authentication is OAuth2 with Argon2 password hashing through pwdlib and Bearer JWT tokens through PyJWT. CORS is enabled. Configuration is typed through Pydantic Settings. Dependencies come from the FastAPI standard extra and are managed with uv. Python 3.14 and 3.13 are supported, and the Docker images are multi-stage and based on python:3.14-slim by default, with Dockerfiles for both versions in the dockerfiles/ directory.
The project is MIT licensed, the last push was on 2026-10-13, and there are no GitHub releases, which is normal for a template.
The directory tree is the template's real content
The README prints the folder structure of the default template, and the comments in the tree are the documentation. The tree is a service folder called svc, and inside it the pieces are separated by concern.
The apis directory houses all the API packages, and each one is a package with an empty init file, a main module and a submodule. The core directory holds the configuration: auth.py for OAuth2 authentication, config.py for the typed environment settings and logger.py for the logging configuration, with a note that the svc package's own init file configures logging when the package is imported.
Then there is main.py, which is the file where the FastAPI class is called, the routes directory, which is where all the routes live in a single views.py file, and the tests package with test_apis.py doing integration testing of the API responses, test_logger.py unit testing the logger configuration and test_functions.py unit testing the underlying functions.
At the top level of the repository, outside the service folder, sit the things that are not application code: the dockerfiles directory, the .env file that holds the app variables and the Docker Python target, the Caddyfile, the docker-compose file, the PEP 518 compliant pyproject.toml and the uv.lock with the pinned app and dev dependencies.
That last split, service code in svc/ and everything else at the root, is the part that makes the template usable as a base for a cookiecutter style project rather than a demo you copy once.
Two identical APIs, deliberately
The README explains why the template ships two APIs, and the explanation is the most useful sentence in the document.
api_a and api_b are the main packages where the code of the APIs lives, and they are exposed by the endpoints defined in the routes folder. In the template they have identical logic. They are dummy APIs that take an integer as input and return two random integers between zero and the input value.
The purpose of including two identical APIs, the README says, is to demonstrate how you can decouple the logic of multiple APIs and then assemble their endpoints in the routes directory.
That is a good choice for a template, and it is a deliberate design of the example rather than an accident of the scaffolding. A single API in a single file looks fine and teaches you nothing about where the seams are. Two packages with identical contents, wired up by one routes file, show the decoupled case and make the point that the endpoints are assembled separately from the logic. The response format makes the shape visible too, since each call takes a seed and returns a seed and two random values.
{
"seed": 22,
"random_first": 5,
"random_second": 13
}The seeding is what makes the demo endpoints testable, since the same input always produces the same output, which is why the integration test in test_apis.py can assert on it.
Running it locally, and the credentials that come with it
There are two ways to run the template, and both are one make target.
In containers, you need Docker installed and you run this from the project root.
make run-containerLocally, you install uv for dependency management and run this.
make run-localThe README explains what that does: it uses uv and the .python-version file to create or sync a .venv with Python 3.14 when needed, installs the dependencies, and starts the FastAPI development server on http://localhost:5002.
From there, the interactive documentation is at http://localhost:5002/docs, which is the standard FastAPI page, and you press the authorize button and enter a username and password. In the template those are ubuntu and debian. The APIs use OAuth2 with Argon2 password hashing and Bearer JWT authentication, so the authorize flow is the real thing rather than a mock, and the token is what the curl example on the docs page uses.
Those demo credentials are the first thing to change. The README presents them as part of the walkthrough, but a template that ships a working username and password will be deployed by somebody who skipped that step.
Caddy sits in front of the application
The docker-compose file is a two service setup, and the split between the services is what tells you how the app is meant to be exposed.
The application service is named fnano, restarts unless stopped, takes its variables from the .env file through env_file, and builds from a Dockerfile whose path is interpolated from the environment variable PYTHON_VERSION, so the same compose file produces a 3.13 or a 3.14 image depending on the .env value. It publishes port 5001 on the host.
The caddy service is the public face. It runs the caddy:2-alpine image, takes the same .env file, and publishes 5002, 80 and 443. It binds the Caddyfile from the repository into the container so the proxy configuration is versioned with the code, and it keeps its own state in two named volumes for data and config, which is where Caddy keeps its certificates and its own configuration.
The dependency is declared, with the caddy service depending on fnano, so the ordering is right on a cold start.
Caddy is a deliberate choice over Nginx for a template, because Caddy's default is to obtain and renew a certificate for a hostname without any configuration, and that behaviour is exactly what a small service needs on its first run. The Caddyfile in the repository is described as a simple reverse proxy with caddy, and the ports it publishes are the evidence of the intended shape, with 5002 for the proxied application, 80 for plain HTTP and 443 for TLS.
The quality gates are configured, not just mentioned
The housekeeping section is four make targets, and the configuration behind them is the part that makes the template worth copying.
Tests run with make test, which uses pytest. Linting and type checking run with make lint, using ruff and mypy. Dependencies are updated with make dep-update, and the containers are stopped with make kill-container.
The pyproject file is where the strictness is decided. Mypy is configured with disallow_untyped_defs, no_implicit_optional, warn_no_return and warn_unused_ignores, which is close to strict mode, with follow_imports set to skip and ignore_missing_imports on, and the tests package excluded from error checking with a module override, which is a sensible exception for test code. Ruff selects the Pyflakes and pycodestyle rules plus the pytest plugin rules, the comprehension rules and isort, with the line length rule ignored, and a mccabe complexity cap of 10. Pytest runs with strict markers, a maximum of one failure so the first failure is the one you see, and coverage measured over the svc package.
The dependency set is equally deliberate. The runtime dependencies are fastapi with the standard extra, pydantic-settings, pwdlib with the argon2 extra and pyjwt, and that is the entire list. The dev group is mypy, pytest, pytest-cov and ruff, all with lower bounds rather than upper ones, which is the opposite of the pattern in some other templates and a small decision you may want to change.
Editorial conclusion
fastapi-nano fits a developer starting a small to medium FastAPI service who wants the boring parts already decided, a directory layout that scales past a single main file, and a local run that takes one make target. It does not fit a team that needs a large application framework, a message queue, or a database layer, because the template deliberately stops at the HTTP boundary. Verify three things before you build on it: the Python version your environment has, since the project requires 3.13 or newer and ships images for 3.14 and 3.13, that the demo credentials are replaced before the service is exposed, and that the layout still fits as the codebase grows, since the structure is a convention rather than a framework feature and nothing enforces it beyond the two example packages.
Frequently asked questions
What does fastapi-nano give me that the default FastAPI example does not?
A directory structure modelled on Flask's blueprints, with each API in its own package and the endpoints assembled in a single routes file, plus the parts a service usually ends up needing anyway: OAuth2 with Argon2 hashing through pwdlib, Bearer JWTs through PyJWT, CORS, typed environment settings with Pydantic Settings, Caddy for reverse proxying, Docker images for Python 3.14 and 3.13, and ruff, mypy and pytest configured.
How do I run the fastapi-nano template?
Either make run-container with Docker installed, or install uv and run make run-local, which uses uv and the .python-version file to create or sync a .venv with Python 3.14, installs the dependencies and starts the development server on http://localhost:5002. The interactive API documentation is at http://localhost:5002/docs.
Why does the template ship two identical APIs?
The README says api_a and api_b are dummy APIs that take an integer and return two random integers between zero and it, and that having two identical ones demonstrates how to decouple the logic of multiple APIs and then assemble their endpoints in the routes directory. The response includes the seed, which is what makes the endpoints assertable in the integration test.
How does the Docker Compose setup work in fastapi-nano?
Two services. The application service is built from a Dockerfile chosen by the PYTHON_VERSION variable in the .env file and publishes port 5001, while the caddy service runs caddy:2-alpine, binds the repository Caddyfile into the container, keeps its data and config in named volumes, publishes 5002, 80 and 443, and depends on the application service.
What are the default login credentials in fastapi-nano?
The README's walkthrough uses the username ubuntu and the password debian, with the APIs using OAuth2, Argon2 password hashing and Bearer JWT authentication. They are demonstration credentials for the template, so they are the first thing to change before the service is exposed.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/rednafi-fastapi-nano)Was this useful?