FastAPI

FastAPI is a modern, async web-framework for building APIs with Python 3.6+ making use of type hints. It is a microframework, in many ways quite similar to Flask and uses the MIT license.


Note

For this guide you should be familiar with the basic concepts of

Installation

We are creating a very basic proof-of-concept API that will do a calculation and return the result as valid JSON.

Create application directory and files

[isabell@stardust ~]$ mkdir ~/fastapi
[isabell@stardust ~]$

Create a file ~/fastapi/main.py with the following content:

#!/usr/bin/env python3.11
import fastapi
import uvicorn

api = fastapi.FastAPI()

@api.get('/')
def calculate():
  answer = 24 + 18
  return {'answer': answer}

# To test this app locally, uncomment this line:
# uvicorn.run(api, host="localhost", port=8000)

Python Virtual Environment

To install the packages your application depends on, you want to use a virtual environment that is isolated from the system’s Python packages. FastAPI requires Python 3.6+ but we are going to use the latest version available, which is currently Python 3.11:

[isabell@stardust ~]$ cd ~/fastapi
[isabell@stardust fastapi]$ python3.11 -m venv venv
[isabell@stardust fastapi]$ source venv/bin/activate
(venv) [isabell@stardust fastapi]$

In your virtual environment, install the dependencies we are using in main.py:

(venv) [isabell@stardust fastapi]$ pip install fastapi uvicorn
(venv) [isabell@stardust fastapi]$

Note

You can already check if your application is working by uncommenting the ucivorn.run() line and executing python main.py in the virtual environment. Open a second ssh connection and get the output with curl localhost:8000

While uvicorn handles the asynchronous side of FastAPI, we want gunicorn as a well-tested and production-ready solution to run and manage multiple uvicorn workers. Install it with two dependencies in your virtual environment:

(venv) [isabell@stardust fastapi]$ pip install gunicorn uvloop httptools
(venv) [isabell@stardust fastapi]$

You can deactivate the virtual environment context like this:

(venv) [isabell@stardust fastapi]$ deactivate
[isabell@stardust fastapi]$

Configuration

Incoming https requests are routed through nginx to a web backend where our application server is listening. Let’s get this working:

Gunicorn

First, we are telling gunicorn to spin up several uvicorn processes and listen on port 8000 for incoming requests. Create the config file ~/fastapi/conf.py:

import os
app_path = os.environ['HOME'] + '/fastapi'

# Gunicorn configuration
wsgi_app = 'main:api'
bind = ':8000'
chdir = app_path
workers = 4
worker_class = 'uvicorn.workers.UvicornWorker'
errorlog =  app_path + '/errors.log'

This is a minimal working example based on gunicorn’s example config, you can find a comprehensive list of options here.

Note

Test if everything is working with ~/fastapi/venv/bin/gunicorn --config ~/fastapi/conf.py --check-config. If there is no output, you’re good.

Supervisord

Next, create a configuration for supervisord in ~/etc/services.d/fastapi.ini:

[program:fastapi]
directory=%(ENV_HOME)s/fastapi
command=%(ENV_HOME)s/fastapi/venv/bin/gunicorn --config %(ENV_HOME)s/fastapi/conf.py

After creating the configuration, tell supervisord to refresh its configuration and start the service:

[isabell@stardust ~]$ supervisorctl reread
SERVICE: available
[isabell@stardust ~]$ supervisorctl update
SERVICE: added process group
[isabell@stardust ~]$ supervisorctl status
SERVICE                            RUNNING   pid 26020, uptime 0:03:14
[isabell@stardust ~]$

Web backend

Note

Gunicorn is set up to run on port 8000.

To make the application accessible from the outside, configure a web backend:

[isabell@stardust ~]$ uberspace web backend set / --http --port <port>
Set backend for / to port <port>; please make sure something is listening!
You can always check the status of your backend using "uberspace web backend list".
[isabell@stardust ~]$

That’s it

This concludes the setup. Point your browser to your url and check that everything is running correctly.


Written by: Christian Macht <https://github.com/cmacht/>