Starting Web Development with FastAPI

FastAPI is a modern, fast (high-performance) web framework for building APIs with Python 3.7+ based on standard Python type hints. It is designed to be easy to use and to offer high performance, comparable to Node.js and Go.

This comprehensive guide will take you through the steps of setting up a web development environment with FastAPI, building endpoints, handling requests and responses, integrating a database, adding authentication and authorization, and deploying your application.

Table of Contents

  1. What is FastAPI?
  2. Setting Up the Environment
  3. Creating a Basic FastAPI Application
  4. Handling Requests and Responses
  5. Database Integration with SQLAlchemy
  6. Authentication and Authorization
  7. Error Handling and Validation
  8. Testing Your API
  9. Deploying Your Application

What is FastAPI?

FastAPI is a web framework for building APIs with Python 3.7+ that is fast to code and run. It leverages Python’s type hints for data validation, serialization, and documentation generation.

Key Features:

  • High Performance: FastAPI is built on Starlette and Pydantic, ensuring high performance and automatic validation.
  • Ease of Use: It is designed to be easy to use and intuitive, reducing development time.
  • Interactive Documentation: FastAPI generates interactive API documentation with Swagger UI and ReDoc.
  • Standards-based: It is based on OpenAPI and JSON Schema standards, making it highly interoperable.

Setting Up the Environment

To start developing with FastAPI, you need to set up your development environment. Follow these steps:

Install Python

Ensure you have Python installed on your system. You can download it from the official Python website.

Create a Virtual Environment

It’s good practice to create a virtual environment for your projects to manage dependencies. Run the following commands:

python -m venv venv
source venv/bin/activate # On Windows, use `venv\Scripts\activate`

Install FastAPI and Uvicorn
Use pip to install FastAPI and Uvicorn (an ASGI server for running FastAPI applications):
pip install fastapi uvicorn

3. Creating a Basic FastAPI Application

Let’s start by creating a basic FastAPI application. Create a file named main.py and add the following code:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"Hello": "World"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}

if __name__ == '__main__':
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)

In this example, we define a basic FastAPI application with two endpoints: the root endpoint (“/”) and an endpoint to retrieve an item by its ID (“/items/{item_id}”).

4. Handling Requests and Responses

Handling requests and responses is a fundamental part of web development. FastAPI makes this process straightforward with its use of Python type hints.

Handling GET Requests
GET requests are used to retrieve data from the server. The / and /items/{item_id} endpoints in the example above demonstrate handling GET requests.

Handling POST Requests
POST requests are used to create new resources. Here’s how you can add an endpoint for creating items:

from pydantic import BaseModel

class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None

@app.post("/items/")
def create_item(item: Item):
return {"item": item}

In this example, we define an Item model using Pydantic, which FastAPI uses for data validation and serialization.

Handling PUT and DELETE Requests
You can handle PUT and DELETE requests similarly. Here’s how you can add endpoints for updating and deleting items:

@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {"item_id": item_id, "item": item}

@app.delete("/items/{item_id}")
def delete_item(item_id: int):
return {"item_id": item_id}

5. Database Integration with SQLAlchemy

For more complex applications, you’ll need a database to store your data. SQLAlchemy is a popular ORM (Object-Relational Mapping) tool for Python that works well with FastAPI.

Install SQLAlchemy
Install SQLAlchemy and databases (a SQLAlchemy wrapper for asynchronous support):
pip install sqlalchemy databases[sqlite]

Set Up the Database
Modify main.py to integrate SQLAlchemy:

from sqlalchemy import create_engine, Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "sqlite:///./test.db"

database = databases.Database(DATABASE_URL)
metadata = sqlalchemy.MetaData()

Base = declarative_base()

class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String, index=True)
price = Column(Float, index=True)
tax = Column(Float, index=True)

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

Base.metadata.create_all(bind=engine)

Create CRUD Operations
Create a file named crud.py and add the following CRUD operations:

from sqlalchemy.orm import Session
from . import models, schemas

def get_item(db: Session, item_id: int):
return db.query(models.Item).filter(models.Item.id == item_id).first()

def get_items(db: Session, skip: int = 0, limit: int = 10):
return db.query(models.Item).offset(skip).limit(limit).all()

def create_item(db: Session, item: schemas.ItemCreate):
db_item = models.Item(name=item.name, description=item.description, price=item.price, tax=item.tax)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item

def update_item(db: Session, item_id: int, item: schemas.ItemUpdate):
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
db_item.name = item.name
db_item.description = item.description
db_item.price = item.price
db_item.tax = item.tax
db.commit()
db.refresh(db_item)
return db_item

def delete_item(db: Session, item_id: int):
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
db.delete(db_item)
db.commit()
return db_item

Update Main Application
Update main.py to use the database and CRUD operations:

from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine

models.Base.metadata.create_all(bind=engine)

app = FastAPI()

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

@app.post("/items/", response_model=schemas.Item)
def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)):
return crud.create_item(db=db, item=item)

@app.get("/items/{item_id}", response_model=schemas.Item)
def read_item(item_id: int, db: Session = Depends(get_db)):
db_item = crud.get_item(db=db, item_id=item_id)
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
return db_item

@app.put("/items/{item_id}", response_model=schemas.Item)
def update_item(item_id: int, item: schemas.ItemUpdate, db: Session = Depends(get_db)):
return crud.update_item(db=db, item_id=item_id, item=item)

@app.delete("/items/{item_id}", response_model=schemas.Item)
def delete_item(item_id: int, db: Session = Depends(get_db)):
return crud.delete_item(db=db, item_id=item_id)

6. Authentication and Authorization

To secure your API, you need to implement authentication and authorization. FastAPI provides several tools to help with this, including OAuth2 and JWT.

Install Authentication Libraries
Install the necessary libraries for JWT authentication:
pip install pyjwt passlib[bcrypt]

Add Authentication
Modify main.py to include JWT authentication:

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta

# to get a string like this run: openssl rand -hex 32
SECRET_KEY = "09a7ffba7346b97f6d9f5e07b2f65c3e"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt

@app.post("/token", response_model=Token)
def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me", response_model=User)
def read_users_me(current_user: User = Depends(get_current_user)):
return current_user

def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=username)
if user is None:
raise credentials_exception
return user

7. Error Handling and Validation

Proper error handling and validation are crucial for building robust APIs. FastAPI provides tools to handle errors and validate data.

Error Handling
You can customize error responses by defining error handlers. Modify main.py to include error handlers:

from fastapi.responses import JSONResponse

@app.exception_handler(HTTPException)
def http_exception_handler(request, exc):
return JSONResponse(
status_code=exc.status_code,
content={"message": exc.detail},
)

Data Validation
Use Pydantic models for data validation and serialization:

from pydantic import BaseModel, Field

class ItemCreate(BaseModel):
name: str = Field(..., max_length=50)
description: str = Field(None, max_length=300)
price: float = Field(..., gt=0)
tax: float = Field(None, ge=0)

@app.post("/items/", response_model=Item)
def create_item(item: ItemCreate, db: Session = Depends(get_db)):
return crud.create_item(db=db, item=item)

8. Testing Your API

Testing is essential to ensure your API works as expected. Use pytest and FastAPI’s built-in testing capabilities for writing and running tests.

Install pytest

Install pytest:
pip install pytest

Write Tests
Create a file named test_main.py and add the following tests:

from fastapi.testclient import TestClient
from .main import app

client = TestClient(app)

def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"Hello": "World"}

def test_create_item():
response = client.post(
"/items/",
json={"name": "Test Item", "description": "A test item", "price": 10.5, "tax": 1.0},
)
assert response.status_code == 201
assert response.json()["name"] == "Test Item"
assert response.json()["description"] == "A test item"
assert response.json()["price"] == 10.5
assert response.json()["tax"] == 1.0

Run Tests
Run your tests using pytest:
pytest

9. Deploying Your Application

Once your application is ready, you can deploy it to a hosting service like Heroku, AWS, or DigitalOcean.

Deploy to Heroku

Install the Heroku CLI and log in:

curl https://cli-assets.heroku.com/install.sh | sh
heroku login

Create a Procfile with the following content:
web: uvicorn main:app --host=0.0.0.0 --port=${PORT:-8000}

Initialize a Git repository, commit your code, and create a Heroku app:

git init
heroku create

Deploy your application:

git add .
git commit -m "Initial commit"
git push heroku master

Open your deployed application:
heroku open

FastAPI provides a robust yet simple framework for building high-performance APIs with Python. By following the steps in this guide, you can set up your development environment, create a basic FastAPI application, handle requests and responses, integrate with a database, add authentication and authorization, handle errors and validation, test your API, and deploy your application.

FastAPI’s ease of use, performance, and interactive documentation make it a great choice for both beginners and experienced developers looking to build web services quickly and efficiently.