Configure FastAPI with Nginx and uvicorn

Created: Thu 05 Oct 2023 Updated: 8 months, 3 weeks ago

article-featured-image

This article is all about the configuration of FastAPI with uvicorn and then reverse proxy to Nginx. FastAPI is a pure Python-based web framework widely used in the industry for developing RESTful APIs. Due to being fast, simple, and highly productive, FastAPI gained so much popularity in a very short amount of time.

Introduction

We will start a new FastAPI project from scratch. Uvicorn will be used to run this Python application. For managing database credentials and other sensitive information, we'll use environment variables file with .env extension. To serve the application using Uvicorn, systemd socket and service will be used for better efficiency and reliability. Then we perform the reverse proxy by utilizing Nginx web server.

If you are new to FastAPI and came here to learn, then try to follow the process as it is without changing any directory or variable names. This will put us in synchronization for better consistency. For this process, I'm using Ubuntu 22.04 LTS but the same method can be applied to other Debian-based distributions with some minor changes. I'm using python 3.10.12 in this process.

Create new virtual environment

Before setting up a virtual environment, first you need to create a new project directory. Use below command:

$
mkdir ~/fastapi_project

Now creating a new virtual environment for the project. It's always best practice to use a dedicated virtual environment for each project. This article Learn to create Python virtual environment can be helpful for you if you don't know how to set up a new virtual environment.

Let's say you created a new virtual environment named virtual_env and make sure It's inside your project directory fastapi_project. In order to use it, you first need to activate it. Use below command to activate the environment:

$
cd ~/fastapi_project && source virtual_env/bin/activate

Above command will also change the location of the current working directory to fastapi_project directory. Now that your virtual environment is created and activated, move to the next section and follow the process.

Install required packages

There are some system packages as well as Python pip packages that are required for this process. To install the system packages, use the below command:

(virtual_env)$
sudo apt update && sudo apt -y install nginx mysql-server libmysqlclient-dev python3-dev

It will update the system repositories and install nginx and mysql server in the system. Also run this command to enable and start both of the services:

(virtual_env)$
sudo systemctl enable --now nginx mysql-server
Installing pip packages

There are a couple of python pip packages that you need in your virtual environment. Use the following command to install these packages:

(virtual_env)$
pip install fastapi mysqlclient uvicorn sqlalchemy python-dotenv

After successfully installing the pip packages in virtual environment, you are all done for the packages requirement section. Moving on to the next section, you'll learn how to set up a new FastAPI project.

Start new FastAPI project

To start a new FastAPI project, create a new sub-directory named app inside project directory fastapi_project. You can start working on the project without creating the app directory but just to make things clear and simple, we'll place all Python files in a separate directory. Make sure you are in fastapi_project directory before using the below command to create necessary sub-directories and files:

(virtual_env)$
mkdir app html static
It will create three new sub-directories in your project directory and they serve the following purpose:
  • app directory will be used to hold python script files.
  • html directory will be used for saving all HTML files.
  • static directory will be used for placing all the static files required by the application.

Setting up main.py file

There must be a primary script file that acts as an entry point of your application. To achieve this, create main.py file inside app directory. Use the below command to create and open the file:

(virtual_env)$
nano app/main.py
After running the above command, a blank file will open. Copy and paste the below Python code in this file:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import os

app = FastAPI()
@app.get("/", response_class=HTMLResponse)
def read_root():
    with open("html/index.html", "r") as html_file:
        content = html_file.read()
    return content

Now save and close the file by pressing Ctrl + x and y. Lets breakdown what this code means:

  • app = FastAPI() will create an instance of FastAPI class and assign it to app variable. This variable will be used to handle HTTP requests and responses made to the application.
  • @app.get(...) specifies that root ("/") will handle HTTP GET request and the response will be in HTML form.
  • def read_root(): is the handler for ("/"). Here we are reading the contents of index.html file and returning the content as a response.

This is the least required content of main.py file that we need to test our FastAPI project. You don't need to change anything in the code.

Create sample index.html file

In this part, we need to create a sample HTML page to test the project. Use this command to create and open a new index.html file in html directory:

(virtual_env)$
nano html/index.html
A blank file will be opened. Add the below content in this file:
<!doctype html>
<html>
<head>
    <title>FastAPI test project</title>
    <link rel="stylesheet" type="text/css" href="/static/style.css">
</head>
<body>
    <p>boooooooommmmmmm!!!!!!!!</p>
</body>
</html>

Now save and close the file. We've set up the least configuration to test FastAPI project. It's time to check if the project is working or not. Use this uvicorn command in the terminal to test:

(virtual_env)$
uvicorn app.main:app --port 8080 --reload

Now open your browser and access http://127.0.0.1:8080 address. If everything worked fine as expected, you'll see similar image as below:

uvicorn launch test

Press Ctrl + c to stop the uvicorn. You can also deactivate the virtual environment using deactivate command. Now that we know our FastAPI application is working and is configured correctly. It's time to take it one step further by connecting MySQL database and using Nginx to serve HTTP requests.

Use environment file to connect database

In this section, we'll create a new database and then attach that database to our FastAPI project by utilizing the environment variables file. Use sudo mysql command to launch MySQL CLI and set up a new database and user:

mysql>
CREATE DATABASE fastapi_db;

mysql>
CREATE USER 'fastapi_user'@'localhost' IDENTIFIED BY 'password';

mysql>
GRANT ALL PRIVILEGES ON fastapi_db.* TO 'fastapi_user'@'localhost';

mysql>
FLUSH PRIVILEGES;

There are some sensitive and confidential details like the database that is required by the application but we should not simply expose them in the main.py file for security purposes. We need to create a .env file which will be used to list all the required sensitive information as environment variables.

Use this command to create and open a new file in root directory of the project:
$
nano env_var.env
A new file will open. Add the following lines in it:
DEBUG=True
DATABASE_URL=mysql://fastapi_user:password@localhost/fastapi_db
  • DEBUG=True states that the application is in development mode. You'll see detailed error messages when an error occurred. Keep it False in production environment.
  • DATABASE_URL= is used to provide the database credentials. Basic Syntax used here is as follows: mysql://<username>:<user-password>@localhost/<database-name>

We need to make some changes in main.py file in order to test our database connection with FastAPI application. Open file using nano app/main.py command and replace current code with the below code:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import os
import MySQLdb
from sqlalchemy import create_engine
from dotenv import load_dotenv

# Creating an instance and rendering index.html file on root
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
def read_root():
    with open("html/index.html", "r") as html_file:
        content = html_file.read()
    return content
           
# Loading environment file and testing database connection
load_dotenv('.env')
db_url = os.environ.get("DATABASE_URL")
engine = create_engine(db_url)
try:
    connection = engine.connect()
    print("Database connection successful")
    connection.close()
except Exception as e:
    print(f"Error connecting to the database: {e}")
  • load_dotenv('.env') is used to load the environment variables file. We'll create this file in next part.
  • db_url =.. is another variable used to retrieve and assign the value of DATABASE_URL variable from environment file.
  • engine =.. is a variable specified to establish a connection to the MySQL database.
  • try: block made an attempt to build a connection with MySQL database and print the output whether the connection is successful or failed.

Now that we have updated the main.py file. Use the below command to load the environment file and test the FastAPI application with MySQL database. Make sure your virtual environment is activated:

(virtual_env)$
uvicorn app.main:app --port 8080 --env-file env_var.env --reload

If no error occurred, uvicorn will start normally with minimal output and It'll be something like below image:

fastapi with mysql

You can see the Database connection successful message printed in the output which means MySQL database connection with FastAPI has been successfully established.

Serve static files to FastAPI app

To serve static content such as images, CSS & Javascript files, you have to mount the static files directory using main.py file. We have already created static directory in our project that will contain all the static content. Use nano app/main.py command to open the file and add the following lines at the end of it:

from fastapi.staticfiles import StaticFiles

app.mount("/static", StaticFiles(directory="static"), name="static")

This line specifies that we can use /static prefix to link the static content that is inside static directory. A complete main.py file will look like this image:

completed main.py

If you noticed, we have linked a CSS file in index.html page using /static/style.css. Let's create this file and define some styling attributes in it. Use the below command:

(virtual_env)$
nano static/style.css
A blank file will open. Copy and paste the following CSS content in it:
body {background-color: blue;}

p {color: white;}

Now save and close the file. To test if static files are being served or not, start the uvicorn using below command:

(virtual_env)$
uvicorn app.main:app --port 8080 --env-file env_var.env --reload
If static content is being served correctly, you'll get the same output as below image: completed main.py

As you can see our style attributes are loaded as defined in the style.css file.This way we can serve static content whether It's CSS files, JavaScript files, or simple images. In the next section we'll configure systemd service and socket to serve FastAPI application more efficiently. You can stop the uvicorn and also deactivate the virtual environment for the next section.

Configure uvicorn systemd socket and service

You can simply use uvicorn command that we use earlier to lauch FastAPI application and pass the reverse proxy to Nginx server. But there is a more efficient and reliable way to handle the process by running uvicorn as systemd service. One major benefit of systemd service is that we don't have to start the process manually after system reboot.

First we will create a uvicorn socket file. Use the below command with sudo privileges:

$
sudo nano /etc/systemd/system/uvicorn.socket

A blank file named uvicorn.socket will open. Copy and paste the below content:

[Unit]
Description=uvicorn socket

[Socket]
ListenStream=/run/uvicorn.sock

[Install]
WantedBy=sockets.target

Save and close the file as there is no need to change anything in it. This socket is no use for us without a dedicated systemd service. Create uvicorn service using the below command:

$
sudo nano /etc/systemd/system/uvicorn.service

A blank file named uvicorn.service will open. Copy and paste the following content in the file to configure uvicorn as a systemd service:

[Unit]
Description=uvicorn service
Requires=uvicorn.socket
After=network.target

[Service]
User=pbxforce
Group=www-data
WorkingDirectory=/home/pbxforce/fastapi_project
ExecStart=/home/pbxforce/fastapi_project/virtual_env/bin/uvicorn \
            app.main:app --host 0.0.0.0 --port 8080 --env-file env_var.env --reload

[Install]
WantedBy=multi-user.target

Replace pbxforce with your system username who have permissions over FastAPI project. Now we need to reload the systemd daemon to make the changes effective. Use sudo systemctl daemon-reload command to achieve this. To test if our systemd configuration is working or not, use the following command:

$
sudo systemctl enable --now uvicorn.socket uvicorn.service

Above command will enable the uvicorn socket and service to automatically start on system startup and also start both of them in the current session. To check if everything is working as expected, open your web browser and access <your-local-IP-address>:8080 url. You will see our FastAPI test page.

Nginx reverse proxy to uvicorn socket

In this section we'll configure Nginx web server to act as reverse proxy to unix socket and respond to HTTP requests. We have already installed nginx package in the system. Use sudo systemctl status nginx command to check Its status. If not active, use this command to enable and activate it:

$
sudo systemctl enable --now nginx

Now we need to create a new nginx configuration file in /etc/nginx/sites-available to define all the required parameters of our FastAPI application. Use this command to create a new configuration file:

$
sudo nano /etc/nginx/sites-available/fastapi_conf
It will open a new blank file. Copy and paste the following content in it:
server {
        listen 80;
        server_name fastapi.test.server;

        location / {
            include proxy_params;
            proxy_pass http://unix:/run/uvicorn.sock;
        }
}

Replace fastapi.test.server with your own domain name or IP-address. After changing the server_name attribute, save and close the file. Now our new configuration for FastAPI is listening on port 80 and Nginx default configuration is also activated and listening on port 80. Two applications cannot listen to the same port at the same time. So we have to disable the nginx default configuraiton.

Use below commands to disable the default and enable the FastAPI configuration:
$
sudo unlink /etc/nginx/sites-enabled/default

$
sudo ln -s /etc/nginx/sites-available/fastapi_conf /etc/nginx/sites-enabled/

First command will unlink the default configuration file and make it inactive where second command ln will link the fastapi_conf configuraiton file and make it active. To verify the configuration file and check for any errors, use sudo nginx -t command and you will see the similar output:

completed main.py

This command will check the syncax of your active configuration file. If your output looks like the image above, means your configuration file syntax is correct. Run sudo systemctl reload nginx command to reload the Nginx server. Now open your web browser and access http://<your-IP-address> url to access your FastAPI application.

Conclusion

This is a very basic FastAPI application configuration using uvicorn and Nginx web server. This article was focused on localhost but if you are trying to deploy your application on a production environment, then you have to make some necessary changes from the security aspect. Configuring the firewall to allow HTTP and HTTPS traffic is one thing. You can learn more about uvicorn while considering application deployment with something advanced techniques.

Suggested Posts:
CYBER SECURITY post image
picoCTF Web Exploitation Challenges and Solutions

picoCTF is an open-source project. It's an enhanced platform for education and organizing competitions …

LINUX post image
Setup a new project on git and learn useful git commands

Git has become an essential tool for modern software development, enabling developers to collaborate, track …

LINUX post image
Configure Firewalld in Linux

Firewalld is a powerful dynamic firewall management tool whose sole purpose is to manage firewall …

PROGRAMMING post image
Fastest method to list all Prime Numbers in Python

While writing code, most developers prefer to code less. And up to some point, …

LINUX post image
Block level full disk cloning using dd in Linux

Taking a backup of a system is very sensitive and critical process and sometimes …

Sign up or Login to post comment.

Sign up Login

Comments (0)