Lesson 9 - Extra - Intro to FastAPI
9.1. Intro
9.1.1. Some online resources
- FastAPI documentation
- Pydantic documentation
- freeCodeCamp: FastAPI Course for Beginners
- Python FastAPI Tutorial: Build a REST API in 15 Minutes
- Unlocking the Power of NoSQL: FastAPI with MongoDB
- Deploy a Serverless FastAPI App with Neon Postgres and AWS App Runner at any scale
- How to Use FastAPI for Machine Learning
9.1.2. What is FastAPI?
- FastAPI is a modern, high-performance Python web framework for building APIs quickly and efficiently. It is built on top of Starlette for web routing and ASGI support, and Pydantic for data validation and parsing.
- FastAPI is designed to be easy to use while offering great performance. It supports asynchronous programming, making it suitable for high-concurrency applications.
- FastAPI automatically generates interactive API documentation in two formats: ReDoc and OpenAPI/Swagger, allowing developers and clients to easily explore and test API endpoints.
- Although it does not have built-in support for specific databases like MongoDB or Redis, FastAPI works seamlessly with any database through third-party libraries, including SQL databases (via SQLAlchemy) and NoSQL databases (such as MongoDB).
- It is based on Python 3.7+ and utilizes Python’s type hints to offer automatic validation, serialization, and documentation.
Key Features:
- High Performance: Comparable to frameworks like Node.js and Go.
- Automatic Documentation: FastAPI automatically generates interactive API docs using Swagger UI and ReDoc.
- Asynchronous Support: FastAPI supports asynchronous request handling via
async
andawait
. - Data Validation: Built on Pydantic, making request validation and parsing easy.
- Type Hints: Leverages Python’s type hints for better code completion and editor support.
9.2. Setting Up FastAPI
As always, we first need to setup the environment. In a new directory.
1
python3 -m venv venv
9.2.1. Installation
Let us then install the necessary dependencies with the following command. We should also add them to the requirements.txt
file.
1
2
pip install fastapi uvicorn
pip freeze > requirements.txt
- fastapi: The main FastAPI framework.
- uvicorn: ASGI server to run the FastAPI app.
this is the basic folder structure for this lesson
1
2
3
.
├── app.py
└── requirements.txt
9.2.2. Basic FastAPI App
Let us make a basic FastAPI app that will return the message “Hello World!”.
1
2
3
4
5
6
7
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello World!"}
9.2.3. Run the app
We are going to use uvicorn
to run the FastAPI app. This will start the server and listen for requests on port 8000.
1
uvicorn app:app --reload --port 8000
You can also ommit --port 8000
, because the default port is 8000.
We will perform a simple test with the browser.
Open your browser at http://localhost:8000
. The app should be running. We should have a JSON response displated:
1
{"message": "Hello World!"}
9.2.4. Creating Basic API Routes (GET, POST, PUT, DELETE)
Let’s explore the basic HTTP methods: GET
, POST
, PUT
, and DELETE
. With these we are following the so called CRUD pattern, that is:
Action | Method | Description |
---|---|---|
Create | POST | Create a new item. |
Read | GET | Get an item. |
Update | PUT | Update an item. |
Delete | DELETE | Delete an item. |
First, let’s add the pydantic import and the basic Item
model. Item will be our data model type.
1
2
3
4
5
6
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
GET Method
- We can get an item using the
GET
method. In this case we will use theitem_id
as the parameter to get a specific item.
1
2
3
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
If we wish to get all items we can use the GET
method without any parameters.
1
2
3
@app.get("/items/")
def read_items():
return [{"item_id": "Foo"}, {"item_id": "Bar"}]
POST Method
1
2
3
@app.post("/items/")
def create_item(item: Item):
return {"item_name": item.name, "item_price": item.price}
PUT Method
1
2
3
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_id": item_id, "updated_item": item}
DELETE Method
1
2
3
@app.delete("/items/{item_id}")
def delete_item(item_id: int):
return {"message": f"Item {item_id} deleted."}
9.2.5. Request and Response Models
FastAPI automatically handles request validation using Pydantic models. These models can also be used to define the response structure.
1
2
3
4
5
6
7
8
9
10
11
12
13
from fastapi import FastAPI
from pydantic import BaseModel
class User(BaseModel):
username: str
email: str
full_name: str = None
app = FastAPI()
@app.post("/users/")
def create_user(user: User):
return {"username": user.username, "email": user.email}
This will automatically validate the incoming request body to ensure it matches the model, rejecting requests with invalid data.
9.2.6. Dependency Injection
FastAPI provides a way to handle dependencies, such as database connections, authentication, and logging, using dependency injection.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from fastapi import Depends, FastAPI
app = FastAPI()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
items = db.query(Item).offset(skip).limit(limit).all()
return items
In this example, get_db
is a dependency that can be injected into route handlers using the Depends
function. FastAPI will ensure that the dependency is initialized and cleaned up automatically.
9.2.7. Automatic Documentation
FastAPI automatically generates interactive API documentation. You can access the documentation via:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
These tools allow you to test API endpoints directly from the browser.
9.2.8. Handling Errors
FastAPI makes it easy to handle errors and send appropriate HTTP responses.
1
2
3
4
5
6
7
from fastapi import HTTPException
@app.get("/items/{item_id}")
def read_item(item_id: int):
if item_id not in [1, 2, 3]:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id}
If you try to access an invalid item_id
, FastAPI will return a 404 error with the custom message “Item not found”.
9.2.9. Middleware
You can also add middleware to perform actions on every request and response, like logging or adding custom headers. Middleware is a function that runs before or after the request is processed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class SimpleMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers["X-Custom-Header"] = "Custom value"
return response
app.add_middleware(SimpleMiddleware)
@app.get("/")
def read_root():
return {"message": "Hello with middleware!"}
9.3. An Example App with Database
In this example, we will create an app that allows users to create, read, update, and delete items in a database.
9.3.1. Create Environment and Install Dependencies
1
2
3
python3 -m venv venv
pip install pip install fastapi uvicorn sqlalchemy sqlite3
pip freeze > requirements.txt
9.3.2. Setting Up SQLite with FastAPI and SQLAlchemy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
# Define the SQLite database URL
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
# Create the SQLite engine
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
# Create a session local class for handling database sessions
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Create a base class for models
Base = declarative_base()
# Create the FastAPI app
app = FastAPI()
# Define a database model
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String, index=True)
# Create the database tables
Base.metadata.create_all(bind=engine)
# Dependency to get the database session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
9.3.3. CRUD Functionality Implementation
- Create Item
We will use the POST
method to create a new item in the database.
1
2
3
4
5
6
7
@app.post("/items/")
def create_item(name: str, description: str, db: Session = Depends(get_db)):
new_item = Item(name=name, description=description)
db.add(new_item)
db.commit()
db.refresh(new_item)
return new_item
- Read Items (with pagination)
The GET
method will retrieve all items from the database with support for pagination.
1
2
3
4
@app.get("/items/")
def read_items(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
items = db.query(Item).offset(skip).limit(limit).all()
return items
- Read Single Item by ID
We will retrieve an item by its ID using the GET
method.
1
2
3
4
5
6
@app.get("/items/{item_id}")
def read_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(Item).filter(Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
return item
- Update Item
We can update an existing item using the PUT
method.
1
2
3
4
5
6
7
8
9
10
11
@app.put("/items/{item_id}")
def update_item(item_id: int, name: str, description: str, db: Session = Depends(get_db)):
item = db.query(Item).filter(Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
item.name = name
item.description = description
db.commit()
db.refresh(item)
return item
- Delete Item
To delete an item by its ID, we use the DELETE
method.
1
2
3
4
5
6
7
8
9
@app.delete("/items/{item_id}")
def delete_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(Item).filter(Item.id == item_id).first()
if not item:
raise HTTPException(status_code=404, detail="Item not found")
db.delete(item)
db.commit()
return {"message": f"Item {item_id} deleted"}
9.3.4. Running the App
To run the FastAPI application, use the following command:
1
uvicorn app:app --reload
This will start the application at http://127.0.0.1:8000/
or http://localhost:8000/
, and you can interact with the API using the automatically generated documentation at http://127.0.0.1:8000/docs
or http://localhost:8000/docs
.