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:
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:
sudo systemctl enable --now nginx mysql-server
There are a couple of python pip packages that you need in your virtual environment. Use the following command to install these packages:
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:
mkdir app html static
- 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:
nano app/main.py
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:
nano html/index.html
<!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:
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:
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:
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
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:
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:
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:
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:
nano static/style.css
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:
uvicorn app.main:app --port 8080 --env-file env_var.env --reload
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
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:
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.