This is a minimalistic and extensible FastAPI template that incorporates divisional pattern architecture with divisional folder structure. It's suitable for developing small to medium sized API oriented micro-services. The architecture is similar to what you'd get with Flask's Blueprint.
-
It uses FastAPI framework for API development. FastAPI is a modern, highly performant, web framework for building APIs with Python 3.6+.
-
The APIs are served with Gunicorn server with multiple Uvicorn workers. Uvicorn is a lightning-fast "ASGI" server. It runs asynchronous Python web code in a single process.
-
Simple reverse-proxying with Caddy.
-
OAuth2 (with hashed password and Bearer with JWT) based authentication
-
CORS (Cross Origin Resource Sharing) enabled.
-
Flask inspired divisional folder structure for better decoupling and encapsulation. This is suitable for small to medium backend development.
-
Dockerized using python:3.10-slim-bullseye and optimized for size and functionality. Dockerfile for Python 3.9 and 3.8 can also 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 and Docker Compose V2 installed on your system. From the project's root dirctory, run:
docker compose up -d
If you want to run the app locally, without using Docker, then:
-
Clone the repo and navigate to the root folder.
-
Create a virtual environment. Here I'm using Python's built-in venv in a Unix system. Run:
python3.10 -m venv .venv
-
Activate the environment. Run:
source .venv/bin/activate
-
Go to the folder created by cookie-cutter (default is fastapi-nano).
-
Install the dependencies. Run:
pip install -r requirements.txt && pip install -r requirements-dev.txt
-
Start the app. Run:
uvicorn app.main:app --port 5000 --reload
If you just want to test out the app without cloning anything, then run:
docker run -p 5000:5000 --expose 5000 rednafi/fastapi-nano:0.1
-
To play around with the APIs, go to the following link on your browser:
http://localhost:5000/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 hashed password and Bearer with JWT) based authentication. In this case, the username and password isubuntu
anddebian
respectively.Clicking the
authorize
button will bring up a screen like this: -
Then select any of the
api_a
orapi_b
APIs and put an integer in the number box and click theauthorize
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 gotjq
installed in your system.curl -X GET "http://localhost:5000/api_a/22" \ -H "accept: application/json" \ -H "Authorization: Bearer $(curl -X POST "http://localhost:5000/token" \ -H "accept: application/x-www-form-urlencoded" \ -d "username=ubuntu&password=debian" | jq -r ".access_token")"
This should show a response like this:
{ "seed": 22, "random_first": 5, "random_second": 13 }
-
To test the
GET
APIs with Python, you can use a http client library like httpx:import httpx with httpx.Client() as client: # Collect the API token. r = client.post( "http://localhost:5000/token", headers={"Content-Type": "application/x-www-form-urlencoded"}, data={"username": "ubuntu", "password": "debian"}, ) token = r.json()["access_token"] # Use the token value to hit the API. r = client.get( "http://localhost:5000/api_a/22", headers={"Accept": "application/json", "Authorization": f"Bearer {token}"}, ) print(r.json())
This shows the folder structure of the default template.
fastapi-nano
βββ app # primary app 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 # sample config file
β β βββ __init__.py # empty init file to make the config folder a package
β βββ __init__.py # empty init file to make the app folder a package
β βββ main.py # main file where the fastAPI() class is called
β βββ routes # this is where all the routes live
β β βββ views.py # file containing the endpoints of api_a and api_b
β βββ tests # test package
β βββ __init__.py # empty init file to make the tests folder a package
β βββ test_api.py # integration testing the API responses
β βββ test_functions.py # unit testing the underlying functions
βββ dockerfiles # directory containing all the dockerfiles
βββ .env # env file containing app variables
βββ Caddyfile # simple reverse-proxy with caddy
βββ docker-compose.yml # docker-compose file
βββ pyproject.toml # pep-518 compliant config file
βββ requrements-dev.in # .in file to enlist the top-level dev requirements
βββ requirements-dev.txt # pinned dev dependencies
βββ requirements.in # .in file to enlist the top-level app dependencies
βββ requirements.txt # pinned app 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. Basically 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 random_gen
which basically generates a dict of random integers.
# This a dummy module
# This gets called in the module_main.py file
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 app.api_a.submod import rand_gen
def main_func(num: int) -> dict[str, int]:
d = rand_gen(num)
return d
The endpoint is exposed like this:
# app/routes/views.py
from __future__ import annotations
#... codes regarding authentication ...
# endpoint for api_a (api_b looks identical)
@router.get("/api_a/{num}", tags=["api_a"])
async def view_a(num: int, auth: Depends =Depends(get_current_user)) -> dict[str, int]:
return main_func_a(num)
So hitting the API with a random integer will give you a response like the following:
{
"seed": 22,
"random_first": 27,
"random_second": 20
}
-
You can put your own API logics in the shape of
api_a
andapi_b
packages. You'll have to add additional directories likeapi_a
andapi_b
if you need more APIs. -
Then expose the APIs in the
routes/views.py
file. You may choose to create multipleviews
files to organize your endpoints. -
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.
-
You can change the application port in the
.env
file. -
During prod deployment, you might need to fiddle with the reverse-proxy rules in the Caddyfile.