๐ Simple FastAPI template that mimics Flask's blueprint directory structure
Python
1,034
707 commits
updated Oct 13, 2026
This is a simple FastAPI template that follows Flask's blueprint directory structure.
Uses FastAPI to build the HTTP API endpoints.
Served with the FastAPI CLI using fastapi dev locally and
fastapi run in the container, both running Uvicorn under the hood.
Simple reverse-proxying with Caddy.
OAuth2 authentication with Argon2 password hashing via pwdlib and Bearer JWT tokens via PyJWT.
CORS (Cross Origin Resource Sharing) enabled.
Flask inspired divisional directory structure, suitable for small to medium backend development.
Uses FastAPI standard dependencies and uv for dependency management.
Uses Pydantic Settings for typed environment configuration.
Supports Python 3.14 and 3.13.
Dockerized with multi-stage Dockerfiles based on python:3.14-slim by default.
Dockerfiles for Python 3.14 and 3.13 can be found in the dockerfiles directory.
Clone the repo and navigate to the root folder.
To run the app using Docker, make sure you've got Docker installed on your system. From the project's root directory, run:
make run-container
If you want to run the app locally, without using Docker, then:
Clone the repo and navigate to the root folder.
Install uv for dependency management.
Start the app. Run:
make run-local
This will use uv and .python-version to create or sync a .venv with
Python 3.14 when needed, install dependencies, and start the FastAPI development
server on http://localhost:5002.
To play around with the APIs, go to the following link on your browser:
http://localhost:5002/docs
This will take you to an UI like below:

Press the authorize button on the right and add username and password. The APIs
use OAuth2 with Argon2 password hashing and Bearer JWT authentication. In this case,
the username and password are ubuntu and debian respectively.

Clicking the authorize button will bring up a screen like this:

Then select any of the api_a or api_b APIs, put an integer in the number box,
and click the Execute button.

Hitting the API should give a json response with random integers.

Also, notice the curl section in the above screen shot. You can directly use the
highlighted curl command in your terminal. Make sure you've got jq installed in your
system.
curl -X GET "http://localhost:5002/api_a/22" \
-H "accept: application/json" \
-H "Authorization: Bearer $(curl -X POST "http://localhost:5002/token" \
-H "accept: application/x-www-form-urlencoded" \
-d "username=ubuntu&password=debian" | jq -r ".access_token")"
This should show a response like this. The random values will vary.
{
"seed": 22,
"random_first": 5,
"random_second": 13
}
make test (uses pytest).make lint.make dep-update.make kill-container.This shows the folder structure of the default template.
fastapi-nano
โโโ svc # primary service folder
โ โโโ apis # this houses all the API packages
โ โ โโโ api_a # api_a package
โ โ โ โโโ __init__.py # empty init file to make the api_a folder a package
โ โ โ โโโ mainmod.py # main module of api_a package
โ โ โ โโโ submod.py # submodule of api_a package
โ โ โโโ api_b # api_b package
โ โ โโโ __init__.py # empty init file to make the api_b folder a package
โ โ โโโ mainmod.py # main module of api_b package
โ โ โโโ submod.py # submodule of api_b package
โ โโโ core # this is where the configs live
โ โ โโโ auth.py # authentication with OAuth2
โ โ โโโ config.py # typed environment settings
โ โ โโโ logger.py # logging configuration
โ โ โโโ __init__.py # empty init file to make the core folder a package
โ โโโ __init__.py # configures logging when the svc package is imported
โ โโโ main.py # main file where the FastAPI() class is called
โ โโโ routes # this is where all the routes live
โ โ โโโ views.py # file containing the endpoints for api_a and api_b
โ โโโ tests # test package
โ โโโ __init__.py # empty init file to make the tests folder a package
โ โโโ test_apis.py # integration testing the API responses
โ โโโ test_logger.py # unit testing logger configuration
โ โโโ test_functions.py # unit testing the underlying functions
โโโ dockerfiles # Dockerfiles for supported Python versions
โโโ .env # env file containing app variables and Docker Python target
โโโ Caddyfile # simple reverse-proxy with caddy
โโโ docker-compose.yml # docker-compose file
โโโ pyproject.toml # pep-518 compliant config file
โโโ uv.lock # pinned app and dev dependencies
In the above structure, api_a and api_b are the main packages where the code of the APIs
live and they are exposed by the endpoints defined in the routes folder. Here, api_a and
api_b have identical logic. These 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 in the template is to demonstrate how you can decouple the logics of multiple APIs and
then assemble their endpoints in the routes directory. The following snippets show the logic
behind the dummy APIs.
This is a dummy submodule that houses a function called rand_gen which generates a
dictionary of random integers.
# This is a dummy module.
# This gets called in mainmod.py.
from __future__ import annotations
import random
def rand_gen(num: int) -> dict[str, int]:
num = int(num)
d = {
"seed": num,
"random_first": random.randint(0, num),
"random_second": random.randint(0, num),
}
return d
The main_func in the primary module calls the rand_gen function from the submodule.
from __future__ import annotations
from .submod import rand_gen
def main_func(num: int) -> dict[str, int]:
d = rand_gen(num)
return d
The endpoint is exposed like this:
router = APIRouter()
logger = logging.getLogger(__name__)
CurrentUser = Annotated[UserInDB, Depends(get_current_user)]
# endpoint for api_a (api_b looks similar)
@router.get("/api_a/{num}", tags=["api_a"])
async def view_a(
num: int,
_auth: CurrentUser,
) -> dict[str, int]:
result = main_func_a(num)
logger.info(f"API A: {result}")
return result
So hitting the API with a random integer will give you a response like the following. The random values will vary.
{
"seed": 22,
"random_first": 5,
"random_second": 20
}
You can put your own API logic following the shape of api_a and api_b packages.
You'll have to add additional directories like api_a or api_b if you need to expose
more endpoints.
Then expose the API URLs in the routes/views.py file. You may choose to create
multiple views files to organize your endpoint URLs.
This template uses OAuth2 based authentication and it's easy to change that. FastAPI docs has a comprehensive list of the available authentication options and instructions on how to use them.
During prod deployment, you might need to fiddle with the reverse-proxy rules in the Caddyfile.
123 followers ยท starred Dec 2024
354 followers ยท starred May 2024
11 followers ยท starred Apr 2022
16 followers ยท starred Sep 2026
๐ Simple FastAPI template that mimics Flask's blueprint directory structure
Python
1,034
707 commits
updated Oct 13, 2026
This is a simple FastAPI template that follows Flask's blueprint directory structure.
Uses FastAPI to build the HTTP API endpoints.
Served with the FastAPI CLI using fastapi dev locally and
fastapi run in the container, both running Uvicorn under the hood.
Simple reverse-proxying with Caddy.
OAuth2 authentication with Argon2 password hashing via pwdlib and Bearer JWT tokens via PyJWT.
CORS (Cross Origin Resource Sharing) enabled.
Flask inspired divisional directory structure, suitable for small to medium backend development.
Uses FastAPI standard dependencies and uv for dependency management.
Uses Pydantic Settings for typed environment configuration.
Supports Python 3.14 and 3.13.
Dockerized with multi-stage Dockerfiles based on python:3.14-slim by default.
Dockerfiles for Python 3.14 and 3.13 can be found in the dockerfiles directory.
Clone the repo and navigate to the root folder.
To run the app using Docker, make sure you've got Docker installed on your system. From the project's root directory, run:
make run-container
If you want to run the app locally, without using Docker, then:
Clone the repo and navigate to the root folder.
Install uv for dependency management.
Start the app. Run:
make run-local
This will use uv and .python-version to create or sync a .venv with
Python 3.14 when needed, install dependencies, and start the FastAPI development
server on http://localhost:5002.
To play around with the APIs, go to the following link on your browser:
http://localhost:5002/docs
This will take you to an UI like below:

Press the authorize button on the right and add username and password. The APIs
use OAuth2 with Argon2 password hashing and Bearer JWT authentication. In this case,
the username and password are ubuntu and debian respectively.

Clicking the authorize button will bring up a screen like this:

Then select any of the api_a or api_b APIs, put an integer in the number box,
and click the Execute button.

Hitting the API should give a json response with random integers.

Also, notice the curl section in the above screen shot. You can directly use the
highlighted curl command in your terminal. Make sure you've got jq installed in your
system.
curl -X GET "http://localhost:5002/api_a/22" \
-H "accept: application/json" \
-H "Authorization: Bearer $(curl -X POST "http://localhost:5002/token" \
-H "accept: application/x-www-form-urlencoded" \
-d "username=ubuntu&password=debian" | jq -r ".access_token")"
This should show a response like this. The random values will vary.
{
"seed": 22,
"random_first": 5,
"random_second": 13
}
make test (uses pytest).make lint.make dep-update.make kill-container.This shows the folder structure of the default template.
fastapi-nano
โโโ svc # primary service folder
โ โโโ apis # this houses all the API packages
โ โ โโโ api_a # api_a package
โ โ โ โโโ __init__.py # empty init file to make the api_a folder a package
โ โ โ โโโ mainmod.py # main module of api_a package
โ โ โ โโโ submod.py # submodule of api_a package
โ โ โโโ api_b # api_b package
โ โ โโโ __init__.py # empty init file to make the api_b folder a package
โ โ โโโ mainmod.py # main module of api_b package
โ โ โโโ submod.py # submodule of api_b package
โ โโโ core # this is where the configs live
โ โ โโโ auth.py # authentication with OAuth2
โ โ โโโ config.py # typed environment settings
โ โ โโโ logger.py # logging configuration
โ โ โโโ __init__.py # empty init file to make the core folder a package
โ โโโ __init__.py # configures logging when the svc package is imported
โ โโโ main.py # main file where the FastAPI() class is called
โ โโโ routes # this is where all the routes live
โ โ โโโ views.py # file containing the endpoints for api_a and api_b
โ โโโ tests # test package
โ โโโ __init__.py # empty init file to make the tests folder a package
โ โโโ test_apis.py # integration testing the API responses
โ โโโ test_logger.py # unit testing logger configuration
โ โโโ test_functions.py # unit testing the underlying functions
โโโ dockerfiles # Dockerfiles for supported Python versions
โโโ .env # env file containing app variables and Docker Python target
โโโ Caddyfile # simple reverse-proxy with caddy
โโโ docker-compose.yml # docker-compose file
โโโ pyproject.toml # pep-518 compliant config file
โโโ uv.lock # pinned app and dev dependencies
In the above structure, api_a and api_b are the main packages where the code of the APIs
live and they are exposed by the endpoints defined in the routes folder. Here, api_a and
api_b have identical logic. These 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 in the template is to demonstrate how you can decouple the logics of multiple APIs and
then assemble their endpoints in the routes directory. The following snippets show the logic
behind the dummy APIs.
This is a dummy submodule that houses a function called rand_gen which generates a
dictionary of random integers.
# This is a dummy module.
# This gets called in mainmod.py.
from __future__ import annotations
import random
def rand_gen(num: int) -> dict[str, int]:
num = int(num)
d = {
"seed": num,
"random_first": random.randint(0, num),
"random_second": random.randint(0, num),
}
return d
The main_func in the primary module calls the rand_gen function from the submodule.
from __future__ import annotations
from .submod import rand_gen
def main_func(num: int) -> dict[str, int]:
d = rand_gen(num)
return d
The endpoint is exposed like this:
router = APIRouter()
logger = logging.getLogger(__name__)
CurrentUser = Annotated[UserInDB, Depends(get_current_user)]
# endpoint for api_a (api_b looks similar)
@router.get("/api_a/{num}", tags=["api_a"])
async def view_a(
num: int,
_auth: CurrentUser,
) -> dict[str, int]:
result = main_func_a(num)
logger.info(f"API A: {result}")
return result
So hitting the API with a random integer will give you a response like the following. The random values will vary.
{
"seed": 22,
"random_first": 5,
"random_second": 20
}
You can put your own API logic following the shape of api_a and api_b packages.
You'll have to add additional directories like api_a or api_b if you need to expose
more endpoints.
Then expose the API URLs in the routes/views.py file. You may choose to create
multiple views files to organize your endpoint URLs.
This template uses OAuth2 based authentication and it's easy to change that. FastAPI docs has a comprehensive list of the available authentication options and instructions on how to use them.
During prod deployment, you might need to fiddle with the reverse-proxy rules in the Caddyfile.
123 followers ยท starred Dec 2024
354 followers ยท starred May 2024
11 followers ยท starred Apr 2022
16 followers ยท starred Sep 2026