How to Create a RESTful API with Python and FastAPI
Introduction
FastAPI is a Python web framework for building APIs & web applications. It supports modern Python features like asynchronous processing & type hints, making it fast and efficient. In addition, it uses the Asynchronous Server Gateway Interface (ASGI) standard for asynchronous, concurrent client connectivity, and it can work with WSGI if needed.
API is an acronym for Application Programming Interface. It acts as a mediator, allowing applications to talk to each other, becoming a medium between applications to exchange data or trigger action. While an API can communicate over many different protocols, this article refers to Web APIs that communicate over HTTP protocol.
REST (Representational State Transfer) is a set of architectural constraints used to build an API, not a protocol or a standard. It does not dictate exactly how to create an API. Instead, it introduces best practices known as constraints. A RESTful API is an API that complies with the REST architecture.
Any request made to a REST API consists of four essential parts: method, endpoint, headers, and body.
HTTP method describes action or operation.
- POST - Create a resource
- GET - Retrieve a resource
- PUT - Update a resource
- DELETE - Delete a resource
Endpoint contains a URI (uniform resource identifier) used to locate the resource on the web.
Headers contain authentication information such as the API key.
Body contains the data or any additional information.
This article demonstrates the steps to build a basic RESTful API using Python and FastAPI. The presented application allows users to access and perform CRUD operations on objects stored in the database. Following is the table for reference showing the endpoint structure of the given application.
|table|100| |thead| |tr| |th|25|Endpoint| |th|25|Method| |th|50|Description| |tbody| |tr| |td|/students| |td|GET| |td|List all student objects| |tr| |td|/students| |td|POST| |td|Add new student object| |tr| |td|/students/{student_id}| |td|GET| |td|Return a student object| |tr| |td|/students/{student_id}| |td|PUT| |td|Update a student object| |tr| |td|/students/{student_id}| |td|DELETE| |td|Delete a student object|
Prerequisites
- Deploy a fresh Ubuntu 20.04 Server
- Create non-root sudo user
Set Up the FastAPI Environment
Initialize the project directory.
Create a folder for your project.
$ mkdir ~/restful_demo
Open the project directory.
$ cd ~/restful_demo
Create a virtual environment.
A virtual environment is a Python tool for dependency management and project isolation. Each project can have any package installed locally in an isolated directory.
Install the Python virtual environment package.
$ sudo apt update $ sudo apt install python3-venv
Create a Python virtual environment for your project.
$ python3 -m venv venv
Enter the virtual environment.
$ source venv/bin/activate
Exit the virtual environment.
(venv) $ deactivate
Install the dependencies.
Install the
wheel
Python package.(venv) $ pip install wheel
Install the
fastapi
Python package.(venv) $ pip install fastapi[all]
Create an Endpoint to List All Students
Create a new file named app.py
.
$ nano app.py
Import the FastAPI
class from the fastapi
library and assign an instance to a variable named app
.
from fastapi import FastAPI
app = FastAPI()
Typically, a REST API uses a database server to store data, such as PostgreSQL or MongoDB. As database choice is dependent on user preference, this article has demonstrated the steps using a Python list as an in-memory database. You can implement it with any database using the same principles.
Create an in-memory database by assigning a list of dummy student objects to a variable named students
.
students = [
{'name': 'Student 1', 'age': 20},
{'name': 'Student 2', 'age': 18},
{'name': 'Student 3', 'age': 16}
]
Create a route to handle GET requests on endpoint /students
.
@app.get('/students')
def user_list():
return {'students': students}
@app.get('/students')
decorator tells FastAPI to execute user_list
function, which returns all the student objects stored in the database. This route enables users to list all the available student objects by sending a GET request on the /students
endpoint.
Save the file using Ctrl + X then Enter.
Deploy the FastAPI application using uvicorn
.
(venv) $ uvicorn app:app --debug
Send a test request in a new terminal.
curl http://127.0.0.1:8000/students
Expected Output:
{"students":[{"name":"Student 1","age":20},{"name":"Student 2","age":18},{"name":"Student 3","age":16}]}
Stop the uvicorn
server using Ctrl + C.
Add Filter Function in List Endpoint
This section demonstrates the steps to update the list endpoint and add functionality to filter the output based on the range of age provided by the user.
Open the FastAPI application created in the previous section.
$ nano app.py
Import Optional
class from typing
library.
from typing import Optional
Place the given line below the first line where you imported the FastAPI
class.
Update the user_list
function to accept min/max parameters and rearrange the code inside the function to return the filtered list.
@app.get('/students')
def user_list(min: Optional[int] = None, max: Optional[int] = None):
if min and max:
filtered_students = list(
filter(lambda student: max >= student['age'] >= min, students)
)
return {'students': filtered_students}
return {'students': students}
The modifications made to the list endpoint enable users to pass a range of ages using min and max parameters to filter the list of student objects. The user_list
function takes two optional parameters: min and max. The in-built Python function filter
iterates over each student object in the database, checking if they are between the requested range. The endpoint returns all student objects stored in the database if min/max parameters are missing.
Save the file using Ctrl + X then Enter.
Deploy the FastAPI application using uvicorn
.
(venv) $ uvicorn app:app --debug
Send a test request in a new terminal.
curl "http://127.0.0.1:8000/students?min=16&max=18"
Expected Output:
{"students":[{"name":"Student 2","age":18},{"name":"Student 3","age":16}]}
Stop the uvicorn
server using Ctrl + C.
Create a Function to Check if Student Object Exists
This section demonstrates the steps to create a function named user_check
, which checks if the requested user exists in the database.
Open the FastAPI application.
$ nano app.py
Import the HTTPException
class from fastapi
library.
from fastapi import FastAPI, HTTPException
Edit the line where you imported FastAPI
class to import another class.
Create a new function named student_check
which takes student_id
parameter.
def student_check(student_id):
if not students[student_id]:
raise HTTPException(status_code=404, detail='Student Not Found')
In the following steps, routes to perform operations on a single student object use the student_check
function to validate if the student_id
provided exists in the database.
Create an Endpoint to Fetch Single Student Object
This section demonstrates the steps to add a new endpoint enabling users to fetch a single student object from the database.
Open the FastAPI application.
$ nano app.py
Create a route to handle GET requests on endpoint /students/{student_id}
.
@app.get('/students/{student_id}')
def user_detail(student_id: int):
student_check(student_id)
return {'student': students[student_id]}
@app.get('/students/{student_id}')
decorator tells FastAPI to execute user_detail
function, which returns all the student object requested by the user. This route enables users to fetch a single student object by sending a GET request on the /students/{student_id}
endpoint. If the student_id
provided by the user is not present in the index of the students
list (the in-memory database), then the function returns HTTP Exception with a 404 error code.
Save the file using Ctrl + X then Enter.
Deploy the FastAPI application using uvicorn
.
(venv) $ uvicorn app:app --debug
Send a test request in a new terminal.
curl http://127.0.0.1:8000/students/0
Expected Output:
{"student":{"name":"Student 1","age":20}}
Stop the uvicorn
server using Ctrl + C.
Create a Model to Handle POST/UPDATE Request Body
This section demonstrates the steps to add a new class to define the schema for handling the request body for POST/UPDATE requests.
Open the FastAPI application created in the previous section.
$ nano app.py
Import BaseModel
class from pydantic
library.
from pydantic import BaseModel
Place the given line below the second line where you imported the Optional
class.
Inherit a new class named Student
from BaseModel
and add the required properties.
class Student(BaseModel):
name: str
age: int
In the following steps, routes to add/update a student object use this schema to validate the values provided by the user.
Create an Endpoint to Add New Student Object
This section demonstrates the steps to add a new endpoint enabling users to add a new student object to the database.
Open the FastAPI application.
$ nano app.py
Create a route to handle POST requests on endpoint /students/{student_id}
.
@app.post('/students')
def user_add(student: Student):
students.append(student)
return {'student': students[-1]}
@app.post('/students')
decorator tells FastAPI to execute the user_add
function, which adds the provided student object to the database after validating it using the Student
schema and returns the newly added student object. This route enables users to add a new student object in the database by sending a POST request on the /students
endpoint with data containing the student object to add.
Save the file using Ctrl + X then Enter.
Deploy the FastAPI application using uvicorn
.
(venv) $ uvicorn app:app --debug
Send a test request in a new terminal.
curl -X 'POST' http://127.0.0.1:8000/students -H 'Content-Type: application/json' -d '{"name":"New Student", "age": 24}'
Expected Output:
{"student":{"name":"New Student","age":24}}
Stop the uvicorn
server using Ctrl + C.
Create an Endpoint to Update a Student Object
This section demonstrates the steps to add a new endpoint enabling users to update an existing student object in the database.
Open the FastAPI application.
$ nano app.py
Create a route to handle PUT requests on endpoint /students/{student_id}
.
@app.put('/students/{student_id}')
def user_update(student: Student, student_id: int):
student_check(student_id)
students[student_id].update(student)
return {'student': students[student_id]}
@app.put('/students/{student_id}')
decorator tells FastAPI to execute user_update
function, which updates the student object in the database by using provided student_id
as index after validating it using the Student
schema and returns the newly updated student object. This route enables users to update an existing student object by sending a PUT request on the /students/{student_id}
endpoint with data containing the student object to update. If the student_id
provided by the user is not present in the index of the students
list (the in-memory database), then the function returns HTTP Exception with a 404 error code.
Save the file using Ctrl + X then Enter.
Deploy the FastAPI application using uvicorn
.
(venv) $ uvicorn app:app --debug
Send a test request in a new terminal.
curl -X 'PUT' http://127.0.0.1:8000/students/0 -H 'Content-Type: application/json' -d '{"name":"Student X", "age": 18}'
Expected Output:
{"student":{"name":"Student X","age":18}}
Stop the uvicorn
server using Ctrl + C.
Create an Endpoint to Delete a Student Object
This section demonstrates the steps to add a new endpoint enabling users to delete a student object from the database.
Open the FastAPI application.
$ nano app.py
Create a route to handle DELETE requests on endpoint /students/{student_id}
.
@app.delete('/students/{student_id}')
def user_delete(student_id: int):
student_check(student_id)
del students[student_id]
return {'students': students}
@app.delete('/students/{student_id}')
decorator tells FastAPI to execute user_delete
function, which deleted the request student object in the database by using provided student_id
as index. This route enables users to delete a student object from the database by sending a DELETE request on the /students/{student_id}
endpoint. If the student_id
provided by the user is not present in the index of the students
list (the in-memory database), then the function returns HTTP Exception with a 404 error code.
Save the file using Ctrl + X then Enter.
Deploy the FastAPI application using uvicorn
.
(venv) $ uvicorn app:app --debug
Send a test request in a new terminal.
curl -X 'DELETE' http://127.0.0.1:8000/students/0
Expected Output:
{"students":[{"name":"Student 2","age":18},{"name":"Student 3","age":16}]}
Stop the uvicorn
server using Ctrl + C.
Final Code
Here is the final demonstration application code. You can use it as a reference for troubleshooting.
from fastapi import FastAPI, HTTPException
from typing import Optional
from pydantic import BaseModel
app = FastAPI()
students = [
{'name': 'Student 1', 'age': 20},
{'name': 'Student 2', 'age': 18},
{'name': 'Student 3', 'age': 16}
]
class Student(BaseModel):
name: str
age: int
@app.get('/students')
def user_list(min: Optional[int] = None, max: Optional[int] = None):
if min and max:
filtered_students = list(
filter(lambda student: max >= student['age'] >= min, students)
)
return {'students': filtered_students}
return {'students': students}
@app.get('/students/{student_id}')
def user_detail(student_id: int):
student_check(student_id)
return {'student': students[student_id]}
@app.post('/students')
def user_add(student: Student):
students.append(student)
return {'student': students[-1]}
@app.put('/students/{student_id}')
def user_update(student: Student, student_id: int):
student_check(student_id)
students[student_id].update(student)
return {'student': students[student_id]}
@app.delete('/students/{student_id}')
def user_delete(student_id: int):
student_check(student_id)
del students[student_id]
return {'students': students}
def student_check(student_id):
if not students[student_id]:
raise HTTPException(status_code=404, detail='Student Not Found')
Conclusion
In this article, you learned the basics of REST API and how to create one yourself using Python and FastAPI. To deploy your FastAPI applications to production, follow the steps to deploy FastAPI Applications with Gunicorn and Nginx on Ubuntu 20.04. For more information related to FastAPI, visit the official FastAPI website.