Rest Api
Rest Api
Get unlimited access to all of Medium for less than $1/week. Become a member
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
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 1/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
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.
Easy: Designed to be easy to use and learn. Less time reading docs.
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 2/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
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.
API Design
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 3/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
Following are the steps required to create the sample FastAPI-based API for
an Item and Store management application:
2. Configure Database
4. Create Schemas
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.
$ cd /path/to/my/workspace/
$ mkdir python-sample-fastapi-application
$ cd python-sample-fastapi-application
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 4/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
Once we are inside the project folder, execute the following commands to
activate the VirtualEnv.
The virtual environment will now be activated, which will provide the
required project isolation and version locking.
c) Install Dependencies
We can see now two files, which have been created inside our project folder,
namely, Pipfile and Pipfile.lock .
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.
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 5/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
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
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.
class, this instance will be the actual database session. To create the
SessionLocal class, we used the function sessionmaker from
sqlachemy.orm .
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 7/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
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
models.py
In line 7 , we declared the table name items where this model will be
mapped to.
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 8/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
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 .
3.2 repositories.py
1
2 from sqlalchemy.orm import Session
3
4 from . import models, schemas
5
6
7 class ItemRepo:
8
9 async def create(db: Session, item: schemas.ItemCreate):
10 db_item = models.Item(name=item.name,price=item.price,description=item.descrip
11 db.add(db_item)
12 db.commit()
13 db.refresh(db_item)
14 return db_item
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 9/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
15
repositories.py
16 def fetch_by_id(db: Session,_id):
17 return db.query(models.Item).filter(models.Item.id == _id).first()
The
18
above code in repositories.py does the following:
19 def fetch_by_name(db: Session,name):
20 We started
return off by creating the ItemRepo class in line
db.query(models.Item).filter(models.Item.name 7 and StoreRepo
== name).first()
21 class in line 38 .
22 def fetch_all(db: Session, skip: int = 0, limit: int = 100):
23 From return to 25 , we defined some helper methods, which we can use
line 9db.query(models.Item).offset(skip).limit(limit).all()
24
to perform CRUD operations on Item database model.
25 async def delete(db: Session,item_id):
26 db_item= db.query(models.Item).filter_by(id=item_id).first()
27
From db.delete(db_item)
line 40 to 63 , we defined some helper methods, which we can
28 use todb.commit()
perform CRUD operations on Store database model.
29
4. Create Schemas
30
Let’s
31 add a file
async schemas.py
def update(db: inside the package
Session,item_data): sql_app . This file will contain
32 updated_item = db.merge(item_data)
the Pydantic models for our SQLAlchemy models. These Pydantic models
33 db.commit()
define more or less a schema (a valid data shape).
34 return updated_item
35
Based
36 on the official documentation,
37
Pydantic
38 is StoreRepo:
class primarily a parsing library, not a validation library. Validation is a
means
39 to an end: building a model which conforms to the types and constraints
40 async def create(db: Session, store: schemas.StoreCreate):
provided. In other words, pydantic guarantees the types and constraints of the
41 db_store = models.Store(name=store.name)
output
42 model, not the input data.
db.add(db_store)
43 db.commit()
All
44 the data validations are performed under the hood by Pydantic.
db.refresh(db_store)
45 return db_store
The
46 schemas.py file should contain the following content:
47 def fetch_by_id(db: Session,_id:int):
48 return db.query(models.Store).filter(models.Store.id == _id).first()
1 from typing import List, Optional
49
2
50 def fetch_by_name(db: Session,name:str):
3 from pydantic import BaseModel
51 return db.query(models.Store).filter(models.Store.name == name).first()
4
52
5
53 def fetch_all(db: Session, skip: int = 0, limit: int = 100):
6 class ItemBase(BaseModel):
54 return db.query(models.Store).offset(skip).limit(limit).all()
7 name: str
55
8 price : float
56 async def delete(db: Session,_id:int):
9 description: Optional[str] = None
57 db_store= db.query(models.Store).filter_by(id=_id).first()
10 store_id: int
58 db.delete(db_store)
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 10/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
11 schemas.py
59 db.commit()
12
13 class ItemCreate(ItemBase):
First, we need to import BaseModel from pydantic and then use it to create
14 pass
subclasses
15 defining the schema, or data shapes, we want to receive. The
above
16 code in schemas.py does the following:
17 class Item(ItemBase):
18 id: int
We started off by creating ItemBase Pydantic model(schema) in line
19
20
6 andclass
StoreBase
Config:
Pydantic model(schema) in line 24 . These classes
21 contain the common
orm_mode = True attributes, which we need while creating or reading
22
data. When a model attribute has a default value or is not required, then
23
we can make that attribute optional. Here, we have used None as the
24 class StoreBase(BaseModel):
25 default value
name: str for description in line 9 .
26
27 Then
classwe added ItemCreate and
StoreCreate(StoreBase): StoreCreate classes, which inherit from
28 ItemBase
pass and StoreBase , respectively. Thus, they will have all the
29
attributes of the Parent class, plus any additional data (attributes)
30 class Store(StoreBase):
31 needed
id: for
int creation.
32 items: List[Item] = []
33 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 .
And,
13 now
app =in the file main.py let's
FastAPI(title="Sample integrate
FastAPI and use all the other parts we
Application",
14 description="Sample FastAPI Application with Swagger and Sqlalchemy",
created in the above steps. The above code within main.py does the
15 version="1.0.0",)
following:
16
17 models.Base.metadata.create_all(bind=engine)
5.118Create FastAPI Instance
19 @app.exception_handler(Exception)
In20linedef13validation_exception_handler(request,
we defined a variable app , which err): will be an “instance” of the
21 base_error_message = f"Failed to execute: {request.method}: {request.url}"
class FastAPI . This will be the main point of interaction for our APIs.
22 return JSONResponse(status_code=400, content={"message": f"{base_error_message}.
23
5.224Create the Database Tables
@app.post('/items', tags=["Item"],response_model=schemas.Item,status_code=201)
25 async def create_item(item_request: schemas.ItemCreate, db: Session = Depends(get_db)
In26line we
17""" create all the tables in the database during the application
startup
27 using the
Create an SQLAlchemy models
Item and store it in the defined
database in step 3.
28 """
From
37 def 19 to 133 contains
lineget_all_items(name: various=REST
Optional[str] endpoints
None,db: Session = available to
Depends(get_db)):
38 """
consumers on resource Item and Store.
39 Get all the Items stored in database
40 """
Let’s
41
check some sample REST endpoints we have defined in our application.
if name:
42 items =[]
In43line 24 we can see
db_item that we have defined an endpoint operation
= ItemRepo.fetch_by_name(db,name)
decorator
44 to create an Item. This API will be used by the consumers to
items.append(db_item)
45 return items
create an Item with given details. The @app.post("/items") tells FastAPI that
46 else:
the47function right
return below is in charge of handling requests that go to the path
ItemRepo.fetch_all(db)
/items
48 using a post operation. This is a decorator related to an endpoint
49
operation, or an endpoint operation decorator.
50 @app.get('/items/{item_id}', tags=["Item"],response_model=schemas.Item)
51 def get_item(item_id: int,db: Session = Depends(get_db)):
From line 25 to 34 we defined the endpoint operation function or the
52 """
function
53 that goes
Get the below
Item thegiven
with the endpoint operation
ID provided by Userdecorator. This function
stored in database
54 """
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 12/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
54 """
will be called by FastAPI whenever it receives a request to the specified URL
55 db_item = ItemRepo.fetch_by_id(db,item_id)
( /items
56 ) using a POST
if db_item operation. In this case, it is an
is None: async function. async
and
57 await is raise
used HTTPException(status_code=404,
to support concurrency and improvenot
detail="Item performance.
found with the For
given ID
58 return db_item
this endpoint we expect the client to send the request as request body.
59
60 @app.delete('/items/{item_id}', tags=["Item"])
A request
61
body is data sent by the client to our API. And response body is
async def delete_item(item_id: int,db: Session = Depends(get_db)):
the62data that
""" our API sends back to the client. To declare a request body, we
will
63 use Pydantic
Delete themodels defined
Item with inIDstep
the given 3, with
provided all their
by User storedpower and benefits.
in database
64 """
We65also have
db_item = ItemRepo.fetch_by_id(db,item_id)
used normal functions for other endpoints, as we can see in
66 if db_item is None:
line 37 instead of using async def.
67 raise HTTPException(status_code=404, detail="Item not found with the given ID
68 await ItemRepo.delete(db,item_id)
Note:
69 If you don’t
return know
"Item the successfully!"
deleted difference between normal functions and async
functions
70 and when to use them, check out Concurrency and async/await in
71 @app.put('/items/{item_id}', tags=["Item"],response_model=schemas.Item)
the FastAPI documentation.
72 async def update_item(item_id: int,item_request: schemas.Item, db: Session = Depends(
73 """
In74line 50 we have used path parameters with type. The value of the path
Update an Item stored in the database
parameter
75 """item_id will be passed to our function as the argument item_id .
76 db_item = ItemRepo.fetch_by_id(db, item_id)
Here,
77 we just explored the capability of using both
if db_item: normal and async
78 update_item_encoded = jsonable_encoder(item_request)
functions in FastAPI.
79 db_item.name = update_item_encoded['name']
80 db_item.price = update_item_encoded['price']
Similarly,
81
we db_item.description
have defined other REST endpoints for our application.
= update_item_encoded['description']
82 db_item.store_id = update_item_encoded['store_id']
5.583Start the Application
return await ItemRepo.update(db=db, item_data=db_item)
84 else:
Till
85now we have
raise written all the code required
HTTPException(status_code=400, for our application
detail="Item not found withtothe
run.
given ID
86
Now, if we try to run the application using the python command, it won’t
87
run.
88
To@app.post('/stores',
run it, we need atags=["Store"],response_model=schemas.Store,status_code=201)
server program.
89 async def create_store(store_request: schemas.StoreCreate, db: Session = Depends(get_
FastAPI
90 is"""
the framework that we have used to build our API, and Uvicorn is
the91serverCreate
that we willand
a Store usesave
to serve thedatabase
it in the requests. We have already installed
92 """
Uvicorn. That will be our server.
93 db_store = StoreRepo.fetch_by_name(db, name=store_request.name)
94 print(db_store)
Finally, in line 137 we configure the application to run at port=9000 using
95 if db_store:
Uvicorn
96 ASGIraise
server.
HTTPException(status_code=400, detail="Store already exists!")
97
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 13/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
Our
98 application is ready
return await now. We can start
StoreRepo.create(db=db, the application by executing the
store=store_request)
99
below command:
100 @app.get('/stores', tags=["Store"],response_model=List[schemas.Store])
101 def get_all_stores(name: Optional[str] = None,db: Session = Depends(get_db)):
102 """
python Get
103 main.py
all the Stores stored in database
104 """
105 if name:
We
106are done with
storesall
=[]the coding part and it’s testing time. Once the
application
107 isdb_store
started= successfully, we can navigate to
StoreRepo.fetch_by_name(db,name)
108 print(db_store)
http://localhost:9000/docs . The system will bring up a page that looks
109 stores.append(db_store)
something
110 like this:stores
return
111 else:
112 return StoreRepo.fetch_all(db)
113
114 @app.get('/stores/{store_id}', tags=["Store"],response_model=schemas.Store)
115 def get_store(store_id: int,db: Session = Depends(get_db)):
116 """
117 Get the Store with the given ID provided by User stored in database
118 """
119 db_store = StoreRepo.fetch_by_id(db,store_id)
120 if db_store is None:
121 raise HTTPException(status_code=404, detail="Store not found with the given I
122 return db_store
123
124 @app.delete('/stores/{store_id}', tags=["Store"])
125 async def delete_store(store_id: int,db: Session = Depends(get_db)):
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 .
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 14/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
Create-Store
Stores
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 15/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
Item-Created
Now if we look at the Stores again, it will contain the Item we created above.
Stores-with-Items
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:
We can see that the attributes of Item in the API documentation are exactly
the ones that we declared for our Pydantic model.
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 17/32
5/11/23, 5:04 PM Building REST APIs using FastAPI, SQLAlchemy & Uvicorn | by Suman Das | Medium
These JSON Schemas will also be used in the API documentation inside each
path operation that needs them:
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:
https://dassum.medium.com/building-rest-apis-using-fastapi-sqlalchemy-uvicorn-8a163ccf3aa1 18/32