Building REST APIs using FastAPI, SQLAlchemy & Uvicorn

Suman Das
12 min readOct 1, 2021
FastAPI with SQLAlchemy

FastAPI

FastAPI is a modern, fast (high-performance), web framework that enables developers to build APIs with Python 3.6+ based on standard Python type hints. We are going to use a Python package called Pydantic, which enforces type hints at runtime. It provides user-friendly errors, allowing us to catch any invalid data.

The key features of FastAPI are:

  • Fast: Very high performance, on par with NodeJS and Go.
  • Fast to code: It allows for significant increases in development speed.
  • Easy: Designed to be easy to use and learn. Less time reading docs.
  • Short: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs.
  • Robust: Get production-ready code. With automatic interactive documentation.
  • Standards-based: It’s based on the open standards for APIs, OpenAPI and JSON Schema.

Swagger

Swagger is a set of open-source tools built around the OpenAPI Specification that can help to design, build, document, and consume REST APIs. The major Swagger tools include:

  • Swagger Editor — browser-based editor where you can write OpenAPI specs
  • Swagger UI — renders OpenAPI specs as interactive API documentation
  • Swagger Codegen — generates server stubs and client libraries from an OpenAPI spec

SQLAlchemy

SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL.

It provides a full suite of well-known enterprise-level persistence patterns, designed for efficient and high-performing database access, adapted into a simple and Pythonic domain language.

Uvicorn

Uvicorn is a lightning-fast ASGI server implementation, using uvloop and httptools. It supports HTTP/1.1 and WebSockets. Support for HTTP/2 is planned.

The aim of this tutorial is to work with FastAPI that helps us to create a production environment-ready Python application along with Swagger UI without a hitch. We will learn to build Rest APIs using Python 3, FastAPI and SQLAlchemy, and share the API using Swagger UI.

Creating the Item and Store REST API

We are going to create REST API providing access to item and store resources. Here’s the API design for the same:

API Design

Following are the steps required to create the sample FastAPI-based API for an Item and Store management application:

  1. Setup and Installation
  2. Configure Database
  3. Create Database Models
  4. Create Schemas
  5. Application Entry Point
  6. Swagger UI
  7. Conclusion

Prerequisites

We require Python 3 with Pipenv and Git installed. Pipenv is a package and a virtual environment manager which uses PIP under the hood. It provides more advanced features like version locking and dependency isolation between projects.

1. Setup and Installation

Once the prerequisites are in place we can begin creating our application.

a) Create a Sample Item Management Flask Application
To begin with our application, create a folder called python-sample-fastapi-application in any directory on the disk for our project.

$ cd /path/to/my/workspace/
$ mkdir python-sample-fastapi-application
$ cd python-sample-fastapi-application

Navigate to the project folder.

b) Activate Virtual Environment

Once we are inside the project folder, execute the following commands to activate the VirtualEnv.

pipenv shell --python 3.8

The virtual environment will now be activated, which will provide the required project isolation and version locking.

c) Install Dependencies

Next, install all the required dependencies using Pipenv as shown.

pipenv install fastapi==0.68.1
pipenv install uvicorn==0.15.0
pipenv install sqlalchemy==1.4.23

After we execute the above commands, the required dependencies will be installed.

We can see now two files, which have been created inside our project folder, namely, Pipfile and Pipfile.lock.

  • Pipfile contains all the names of the dependencies we just installed.
  • Pipfile.lock is intended to specify, based on the dependencies present in Pipfile, which specific version of those should be used, avoiding the risks of automatically upgrading dependencies that depend upon each other and breaking your project dependency tree.

Note: Here, we have installed all the dependencies with specific versions, which worked on my machine while writing this tutorial. If we don’t specify any version then the latest version of that dependency will be installed, which might not be compatible with other dependencies.

Now, let’s start with writing some code for our application.

2. Configure Database

To start with our application , let’s configure the database first. FastAPI supports multiple databases, such as :

  • PostgreSQL
  • MySQL
  • SQLite
  • Oracle
  • Microsoft SQL Server, and so on.

For our application, we’ll use SQLite, because it uses a single file and Python has integrated support. FastAPI works with any database and any style of library to talk to the database. A common pattern is to use an “ORM”: an “object-relational mapping” library. ORM is a technique that lets us query and manipulate data from a database using an object-oriented paradigm. ORMs can be thought of as a translator converting our code from one form to another. With an ORM, we normally create a class that represents a table in a SQL database and each attribute of the class represents a column, with a name and a type. In this tutorial we will use SQLAlchemy ORM framework.

To integrate database with our application, create a file db.py with the following content.

db.py

The above code does the following:

  • First, we have imported packages which are required to create SQLAlchemy engine and database session to connect to the SQLite database. The database file will be created in the same directory with the name data.db.
  • Then, we created the SQLAlchemy engine using the database created above.
    Note: connect_args={"check_same_thread":False} is only required for SQLite
  • SessionLocal class represents database session. The class itself is not a database session yet. But, once we create an instance of the SessionLocal class, this instance will be the actual database session. To create the SessionLocal class, we used the function sessionmaker from sqlachemy.orm .
  • Finally, we used the function declarative_base() that returns a class to create Base class. Later, we will inherit from this class to create each of the database models or classes(the ORM models).
  • We also defined a function called get_db() , which can used to create independent database session for each request. We will use the same session throughout the request and then close it after the request is finished. In the function get_db , yield is used to create a database session for each request. Close it after finishing the request.

3. Create Database Models

Next, we will create database models for our data storage and organization. For our application, we need to create two database models Item, Store and its repositories. We will be using db.py , which we created earlier in (Step 2) to create our SQLAlchemy models. It provides a class called Base that is a declarative base, which can be used to declare our models.

Create the sql_app package and add two files named models.py and repositories.py. We will add all the database entities in models.py and it’s corresponding repository in repositories.py.

3.1 models.py

The models.py file should contain the following content:

models.py

The above code in models.py does the following:

  • We started off by creating the Item Model class in line 6.
  • In line 7, we declared the table name items where this model will be mapped to.
  • From line 9 to 13, we defined the table columns along with their data types. We use Column from SQLAlchemy as the default value. Here, store_id acts as Foreign key reference for Stores .
  • From line 14 to 15, we added some helper methods to print the object at runtime.
  • In line 17, we declared the Store Model class and in line 18 we declared the table name stores where this model will be mapped to.
  • From line 19 to 20, we defined the stores table columns along with their data types.
  • In line 21 we define the relationship provided by SQLAlchemy ORM. This will become, more or less, a “magic” attribute that will contain the values from other tables related to this one.
  • From line 23 to 24, we added some helper methods to print the object at runtime.

3.2 repositories.py

The repositories.py file contains some reusable functions to interact with the data in the database. It has the following content:

repositories.py

The above code in repositories.py does the following:

  • We started off by creating the ItemRepo class in line 7 and StoreRepo class in line 38 .
  • From line 9 to 25, we defined some helper methods, which we can use to perform CRUD operations on Item database model.
  • From line 40 to 63, we defined some helper methods, which we can use to perform CRUD operations on Store database model.

4. Create Schemas

Let’s add a file schemas.py inside the package sql_app . This file will contain the Pydantic models for our SQLAlchemy models. These Pydantic models define more or less a schema (a valid data shape).

Based on the official documentation,

Pydantic is primarily a parsing library, not a validation library. Validation is a means to an end: building a model which conforms to the types and constraints provided. In other words, pydantic guarantees the types and constraints of the output model, not the input data.

All the data validations are performed under the hood by Pydantic.

The schemas.py file should contain the following content:

schemas.py

First, we need to import BaseModel from pydantic and then use it to create subclasses defining the schema, or data shapes, we want to receive. The above code in schemas.py does the following:

  • We started off by creating ItemBase Pydantic model(schema) in line 6and StoreBase Pydantic model(schema) in line 24. These classes contain the common attributes, which we need while creating or reading data. When a model attribute has a default value or is not required, then we can make that attribute optional. Here, we have used None as the default value for description in line 9 .
  • Then we added ItemCreate and StoreCreate classes, which inherit from ItemBase and StoreBase, respectively. Thus, they will have all the attributes of the Parent class, plus any additional data (attributes) needed for creation.
  • Finally, we created Pydantic models (schemas) Item and Store that will be used to read the data from the database and returning it from the API. In the Pydantic models for reading, Item and Store, we added an internal Config class. This Config class is used to provide configurations to Pydantic. In the Config class, we set the attribute orm_mode = True.

5. Application Entry Point

Now, let us create our application entry point. In the root directory of the project, create a file named main.py with the following content:

main.py

And, now in the file main.py let's integrate and use all the other parts we created in the above steps. The above code within main.py does the following:

5.1 Create FastAPI Instance

In line 13 we defined a variable app , which will be an “instance” of the class FastAPI. This will be the main point of interaction for our APIs.

5.2 Create the Database Tables

In line 17 we create all the tables in the database during the application startup using the SQLAlchemy models defined in step 3.

5.3 Add Exception Handler

From line 19 to 22 , we defined a global exception Handler for our application.

5.4 Add REST Endpoints

From line 19 to 133 contains various REST endpoints available to consumers on resource Item and Store.

Let’s check some sample REST endpoints we have defined in our application.

In line 24 we can see that we have defined an endpoint operation decorator to create an Item. This API will be used by the consumers to create an Item with given details. The @app.post("/items") tells FastAPI that the function right below is in charge of handling requests that go to the path /items using a post operation. This is a decorator related to an endpoint operation, or an endpoint operation decorator.

From line 25 to 34 we defined the endpoint operation function or the function that goes below the endpoint operation decorator. This function will be called by FastAPI whenever it receives a request to the specified URL (/items) using a POST operation. In this case, it is an async function. async and await is used to support concurrency and improve performance. For this endpoint we expect the client to send the request as request body.

A request body is data sent by the client to our API. And response body is the data that our API sends back to the client. To declare a request body, we will use Pydantic models defined in step 3, with all their power and benefits.

We also have used normal functions for other endpoints, as we can see in line 37instead of using async def.

Note: If you don’t know the difference between normal functions and async functions and when to use them, check out Concurrency and async/await in the FastAPI documentation.

In line 50we have used path parameters with type. The value of the path parameter item_id will be passed to our function as the argument item_id.

Here, we just explored the capability of using both normal and async functions in FastAPI.

Similarly, we have defined other REST endpoints for our application.

5.5 Start the Application

Till now we have written all the code required for our application to run. Now, if we try to run the application using the python command, it won’t run. To run it, we need a server program.

FastAPI is the framework that we have used to build our API, and Uvicorn is the server that we will use to serve the requests. We have already installed Uvicorn. That will be our server.

Finally, in line 137we configure the application to run at port=9000 using Uvicorn ASGI server.

Our application is ready now. We can start the application by executing the below command:

python main.py

We are done with all the coding part and it’s testing time. Once the application is started successfully, we can navigate to http://localhost:9000/docs. The system will bring up a page that looks something like this:

FastAPI-Swagger-UI

6. Swagger UI

FastAPI provides automatically generated documentation interfaces for our APIs, which we can interact with through a web interface. We can see that when we navigate to /docs .

Let’s test our application now to ensure everything is working fine.

Let’s start with creating a Store.

Create-Store

Let’s check the Store we created.

Stores

Let’s add an Item to the Store we created above.

Item-Created

Now if we look at the Stores again, it will contain the Item we created above.

Stores-with-Items

Similarly, we can explore other REST APIs.

Because FastAPI is built on top of the OpenAPI standard, it also provides an alternative API documentation using ReDoc, which we can access at http://localhost:9000/redoc:

FastAPI-ReDoc

The JSON Schemas of our Pydantic models, which we defined in (step 3 ) will be part of the OpenAPI generated for our application and will be shown in the interactive API documentation:

Automatic Documentation with Pydantic

We can see that the attributes of Item in the API documentation are exactly the ones that we declared for our Pydantic model.

These JSON Schemas will also be used in the API documentation inside each path operation that needs them:

Automatic JSON Schema

Swagger UI also helps the developers with API testing, either in case of any issues reported or while adding new API’s to the existing application.

7. Conclusion

In this tutorial, we saw how easy it is to create a comprehensive REST API using FastAPI. FastAPI uses the best practices by default while providing the best developer experience as possible. Here, we learned how to:

  • Use path parameters to get a unique URL path per item
  • Receive JSON data in requests using Pydantic
  • Use API best practices like validation, serialization, and documentation
  • Integrate FastAPI with SQLAlchemy
  • Using both normal and async ways of handling requests.
  • Using Swagger in FastAPI

If you would like to refer to the full code, do check:

References & Useful Readings

--

--