Creating an API with python: Part 11: Running as a Service

In my previous post, Creating an API with python: Part 10: Integration Tests, I added integration tests, which replaced the test.sh script with python unit tests that test the API functionality. In this post, I will set up the FastAPI API to run as a service, controllable via the systemctl command.

Prerequisites

These prerequisites are assumed for this post, although this post can be used as a guide to setting up any FastAPI python API as a service, even without these specific prerequisites:

  1. Creating an API with python: Part 1: GET Endpoints
  2. Creating an API with python: Part 2: MariaDB Database
  3. Creating an API with python: Part 3: POST Endpoints
  4. Creating an API with python: Part 4: DELETE Endpoints
  5. Creating an API with python: Part 5: Authentication
  6. Creating an API with python: Part 6: HTTPS and Proxying
  7. Creating an API with python: Part 7: CORS
  8. Creating an API with python: Part 8: Multiple Account Support
  9. Creating an API with python: Part 9: Authentication Scopes
  10. Creating an API with python: Part 10: Integration Tests

As a reminder, the code for the FastAPI API is now on GitHub, with the project name taglink-api. You can checkout the latest copy of the code here: https://github.com/liz-allthecoding/taglink-api.

Step 1: Create a Service User

First up, we will create a service user, which will be the user under which the service will be running.

  1. On your server, run the following to create a new group called taglink:
    $ sudo su
    $ groupadd -r taglink
    
  2. Now create the taglink user and assign it to the group taglink:
    $ useradd -r -g taglink -d /home/taglink -s /sbin/nologin -c "Taglink service account" taglink
    

Step 2: Deploy the Code

The code should currently be residing over the shared directory on your server. Create a new directory on your server, which will be the installation location for the code. Then copy the code across to this directory.

  1. On your server, run the following (replacing /home/osboxes/vboxshare/taglink-api/ with the path to your code, if located elsewhere):
    $ sudo su
    $ mkdir -p /opt/taglink-api
    $ cp -r /home/osboxes/vboxshare/taglink-api/* taglink-api/
    $ cd /opt/
    $ chown -R taglink:taglink taglink-api
    
  2. Now create the venv-fastapi venv and install the required python packages to it:
    $ sudo su
    $ cd /opt/taglink-api
    $ python -m venv .venv-fastapi
    $ . .venv-fastapi/bin/activate
    (.venv-fastapi) $ pip install --upgrade pip
    (.venv-fastapi) $ pip install "fastapi[all]"
    (.venv-fastapi) $ pip install sqlalchemy
    (.venv-fastapi) $ pip install mariadb
    (.venv-fastapi) $ pip install python-multipart
    (.venv-fastapi) $ pip install "python-jose[cryptography]"
    (.venv-fastapi) $ pip install "passlib[bcrypt]"
    
  3. Change the ownership of the venv-fastapi venv to the taglink user:
    $ chown -R taglink:taglink .venv-fastapi
    

Step 3: Add start.sh

The start.sh file should be in the top directory of /opt/taglink-api if you copied your code from a recent clone of the github repo. If not, create the file:

  1. Create a file called start.sh in the /opt/taglink-api directory, and populate it with these contents:
    #!/bin/bash
    . /opt/taglink-api/.venv-fastapi/bin/activate
    uvicorn --host 0.0.0.0 main:app --root-path /api --reload
    
  2. Add executable permissions:
    $ chmod ug+x start.sh
    

Step 4: Create the Systemd Service Configuration File

Create the systemd service configuration file:

  1. Change directory to where the systemd service configuration is located:
    $ sudo su
    $ cd /etc/systemd/system
    
  2. Create a file called taglink-api.service in the /etc/systemd/system directory, and populate it with these contents:
    [Unit]
    Description=TagLink API Service
    After=network.target
    ConditionPathExists=/opt/taglink-api/.venv-fastapi
    
    [Service]
    Type=simple
    SyslogIdentifier=taglink-api
    WorkingDirectory=/opt/taglink-api
    Environment="PYTHONPATH=/opt/taglink-api"
    Environment="SYSLOG_IDENT=taglink-api"
    ExecStart=/opt/taglink-api/start.sh
    
    User=taglink
    Group=taglink
    Restart=always
    RestartSec=20
    
    [Install]
    WantedBy=multi-user.target
    

Step 5: Start the Service

Start the service using the systemctl command.

  1. $ sudo systemctl start taglink-api
    
  2. Check if the service is running with this command:
    $ sudo systemctl status taglink-api
    
  3. If all is well, you should get an output something like this:
    [osboxes@osboxes fastapi]$ sudo systemctl status taglink-api
    ● taglink-api.service - TagLink API Service
       Loaded: loaded (/etc/systemd/system/taglink-api.service; disabled; vendor preset: disabled)
       Active: active (running) since Fri 2022-11-18 18:39:09 GMT; 1s ago
     Main PID: 1474 (start.sh)
        Tasks: 4
       CGroup: /system.slice/taglink-api.service
               ├─1474 /bin/bash /opt/taglink-api/start.sh
               ├─1476 /opt/taglink-api/.venv-fastapi/bin/python3.6 /opt/taglink-api/.venv-fastapi/bin/uvicorn --host 0.0.0.0 main:app --root-path /api --reload
               ├─1479 /opt/taglink-api/.venv-fastapi/bin/python3.6 -c from multiprocessing.semaphore_tracker import main;main(4)
               └─1480 /opt/taglink-api/.venv-fastapi/bin/python3.6 -c from multiprocessing.spawn import spawn_main; spawn_main(tracker_fd=5, pipe_handle=7) --multiprocessing-fork
    
    Nov 18 18:39:09 osboxes.org systemd[1]: Started TagLink API Service.
    Nov 18 18:39:09 osboxes.org taglink-api[1474]: INFO:     Will watch for changes in these directories: ['/opt/taglink-api']
    Nov 18 18:39:09 osboxes.org taglink-api[1474]: INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
    Nov 18 18:39:09 osboxes.org taglink-api[1474]: INFO:     Started reloader process [1476] using watchgod
    Nov 18 18:39:10 osboxes.org taglink-api[1474]: /opt/taglink-api/.venv-fastapi/lib64/python3.6/site-packages/jose/backends/cryptography_backend.py:4: CryptographyDeprecationWarning: Python 3.6 is no longer supported by ... future release.
    Nov 18 18:39:10 osboxes.org taglink-api[1474]: from cryptography.exceptions import InvalidSignature, InvalidTag
    Nov 18 18:39:10 osboxes.org taglink-api[1474]: INFO:     Started server process [1480]
    Nov 18 18:39:10 osboxes.org taglink-api[1474]: INFO:     Waiting for application startup.
    Nov 18 18:39:10 osboxes.org taglink-api[1474]: INFO:     Application startup complete.
    Hint: Some lines were ellipsized, use -l to show in full.
    
  4. If there are issues, or you want to see the logs, you can run:
    $ sudo journalctl -f -n1000 -u taglink-api
    
  5. If you want to stop the service, run:
    $ sudo systemctl stop taglink-api
    
  6. If you want to enable the service to run at system boot, run:
    $ sudo systemctl enable taglink-api
    

Step 6: Run the Integration Tests

Run the integration tests to verify that the API is working correctly.

  1. Ensure that the service is running:
    $ sudo systemctl start taglink-api
    
  2. On your server, change to the code directory (~/vboxshare/fastapi should be replaced with the path to your FastAPI python code):
    $ cd ~/vboxshare/fastapi
    
  3. Run the integration tests:
    $ ./integration_test.sh
    

Conclusion

You should now have a FastAPI API running as a service, meaning it can be controlled with systemctl, can be configured to start at system boot, and is resilient to crashes (the systemd configuration is set to restart the service 20 seconds after a crash).

If you want to find out about enabling utf-8 character support and preventing database connections from dropping, see my follow-on post, Creating an API with python: Part 12: Database Character Encoding and Connections.

Thanks for reading!