FastAPI & SQLAlchemy: Build A Robust Project (Example)
FastAPI & SQLAlchemy: Build a Robust Project (Example)
Alright guys, let’s dive into building a robust project using FastAPI and SQLAlchemy! If you’re looking to create efficient, scalable, and maintainable web applications in Python, this combo is a real winner. We’ll walk through a practical example, covering everything from setting up your project to defining database models and creating API endpoints. By the end of this guide, you’ll have a solid foundation for building your own awesome projects.
Table of Contents
- Setting Up Your Project
- Creating a Virtual Environment
- Installing Dependencies
- Project Structure
- Defining Database Models with SQLAlchemy
- Creating the
- Defining the
- Creating Pydantic Schemas
- Defining the
- Building API Endpoints with FastAPI
- Creating the
- Running the Application
- Environment Variables
- Creating the
- Loading Environment Variables
- Conclusion
Setting Up Your Project
First things first, we need to set up our project environment. This involves creating a virtual environment, installing the necessary packages, and structuring our project directory. Think of this as laying the groundwork for a skyscraper – a strong foundation is crucial! Using FastAPI with SQLAlchemy is a powerful choice, providing both speed and ORM capabilities for a seamless development experience. We will ensure that our environment is configured to fully support this synergy.
Creating a Virtual Environment
It’s always a good practice to create a virtual environment for each Python project. This helps isolate your project’s dependencies from other projects and the system-wide Python installation. To create a virtual environment, you can use
venv
:
python3 -m venv .venv
This command creates a new virtual environment in a directory named
.venv
. To activate the virtual environment, use the following command:
source .venv/bin/activate # On Linux/macOS
.venv\Scripts\activate # On Windows
Once activated, your terminal prompt will be prefixed with the name of the virtual environment, indicating that it’s active.
Installing Dependencies
Next, we need to install the required packages. For this project, we’ll need FastAPI, SQLAlchemy, a database driver (like
psycopg2
for PostgreSQL or
mysqlclient
for MySQL), and
uvicorn
as an ASGI server. Let’s get those dependencies installed and ready to go, ensuring our project has everything it needs to thrive!
pip install fastapi sqlalchemy uvicorn python-dotenv
Here’s what each package does:
-
fastapi: The web framework we’ll be using. -
sqlalchemy: The SQL toolkit and ORM. -
uvicorn: An ASGI server to run our FastAPI application. -
python-dotenv: For managing environment variables.
Project Structure
A well-structured project makes it easier to maintain and scale. Here’s a suggested project structure:
myproject/
├── app/
│ ├── __init__.py
│ ├── database.py
│ ├── models.py
│ ├── schemas.py
│ ├── main.py
├── .env
├── requirements.txt
└── README.md
-
app/: Contains the main application code. -
database.py: Handles database connection and session management. -
models.py: Defines the SQLAlchemy models. -
schemas.py: Defines the Pydantic schemas for data validation and serialization. -
main.py: The entry point of the FastAPI application. -
.env: Stores environment variables. -
requirements.txt: Lists the project dependencies. -
README.md: Provides a description of the project and instructions for running it.
Defining Database Models with SQLAlchemy
Now that our project is set up, let’s define our database models using SQLAlchemy. Models represent the tables in our database and provide a way to interact with them using Python objects.
SQLAlchemy
truly shines here, abstracting away the complexities of raw SQL and allowing us to work with our data in a more Pythonic way. We’ll define a simple
Item
model for demonstration purposes.
Creating the
database.py
File
First, let’s create the
database.py
file to handle the database connection and session management:
# app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
import os
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
This code does the following:
-
Loads environment variables from the
.envfile. -
Creates a database engine using the
DATABASE_URL. -
Creates a session factory
SessionLocal. -
Defines a base class
Basefor declarative models. -
Provides a dependency injection function
get_dbto get a database session.
Defining the
Item
Model
Next, let’s define the
Item
model in the
models.py
file:
# app/models.py
from sqlalchemy import Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from .database import Base
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, nullable=True)
status = Column(Boolean, default=False)
This code defines an
Item
model with the following columns:
-
id: An integer primary key. -
title: A string representing the item’s title. -
description: A string representing the item’s description (nullable). -
status: A boolean representing the item’s status (default isFalse).
Creating Pydantic Schemas
Pydantic schemas are used for data validation and serialization. They define the structure of the data that our API endpoints will receive and return. Using Pydantic ensures that the data conforms to our expected format, preventing errors and making our API more robust. Data validation is crucial to maintain the integrity of our application.
Defining the
Item
Schemas
Let’s create the
schemas.py
file and define the
Item
schemas:
# app/schemas.py
from pydantic import BaseModel
from typing import Optional
class ItemBase(BaseModel):
title: str
description: Optional[str] = None
class ItemCreate(ItemBase):
pass
class ItemUpdate(ItemBase):
status: Optional[bool] = None
class Item(ItemBase):
id: int
status: bool
class Config:
orm_mode = True
This code defines four schemas:
-
ItemBase: A base schema containing the common fieldstitleanddescription. -
ItemCreate: A schema for creating new items, inheriting fromItemBase. -
ItemUpdate: A schema for updating items, inheriting fromItemBaseand adding an optionalstatusfield. -
Item: A schema for representing an item with anidandstatus, including theorm_modeconfiguration to enable ORM mapping.
Building API Endpoints with FastAPI
Now comes the exciting part – building our API endpoints using FastAPI! We’ll create endpoints for creating, reading, updating, and deleting items. FastAPI’s intuitive syntax and automatic data validation make this process a breeze. We’re leveraging FastAPI to create endpoints that communicate with the database through our SQLAlchemy models.
Creating the
main.py
File
Let’s create the
main.py
file, which will contain our FastAPI application and API endpoints:
# app/main.py
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import models, schemas
from .database import engine, get_db
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
@app.post("/items/", response_model=schemas.Item)
async def create_item(item: schemas.ItemCreate, db: Session = Depends(get_db)):
db_item = models.Item(**item.dict())
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
@app.get("/items/", response_model=List[schemas.Item])
async def read_items(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
items = db.query(models.Item).offset(skip).limit(limit).all()
return items
@app.get("/items/{item_id}", response_model=schemas.Item)
async def read_item(item_id: int, db: Session = Depends(get_db)):
item = db.query(models.Item).filter(models.Item.id == item_id).first()
if item is None:
raise HTTPException(status_code=404, detail="Item not found")
return item
@app.patch("/items/{item_id}", response_model=schemas.Item)
async def update_item(item_id: int, item: schemas.ItemUpdate, db: Session = Depends(get_db)):
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
for key, value in item.dict(exclude_unset=True).items():
setattr(db_item, key, value)
db.add(db_item)
db.commit()
db.refresh(db_item)
return db_item
@app.delete("/items/{item_id}", response_model=schemas.Item)
async def delete_item(item_id: int, db: Session = Depends(get_db)):
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
if db_item is None:
raise HTTPException(status_code=404, detail="Item not found")
db.delete(db_item)
db.commit()
return db_item
This code defines the following API endpoints:
-
POST /items/: Creates a new item. -
GET /items/: Reads a list of items with pagination. -
GET /items/{item_id}: Reads a specific item by ID. -
PATCH /items/{item_id}: Updates a specific item by ID. -
DELETE /items/{item_id}: Deletes a specific item by ID.
Running the Application
To run the application, use the following command:
uvicorn app.main:app --reload
This command starts the Uvicorn server, serving the FastAPI application defined in
app/main.py
. The
--reload
flag enables automatic reloading when changes are made to the code.
Environment Variables
It’s a
best practice
to store sensitive information, such as database URLs, in environment variables rather than hardcoding them in the application. To do this, we’ll use the
python-dotenv
package. This helps keep your application configuration flexible and secure. Let’s ensure we manage our environment variables properly.
Creating the
.env
File
Create a
.env
file in the root directory of your project and add the following:
DATABASE_URL=postgresql://user:password@host:port/database
Replace
user
,
password
,
host
,
port
, and
database
with your actual database credentials.
Loading Environment Variables
In the
database.py
file, we load the environment variables using the
load_dotenv
function:
# app/database.py
from dotenv import load_dotenv
import os
load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL")
This ensures that the
DATABASE_URL
is loaded from the environment variables when the application starts.
Conclusion
And there you have it! You’ve successfully built a robust project using FastAPI and SQLAlchemy. You’ve learned how to set up your project, define database models, create Pydantic schemas, and build API endpoints. This is just the beginning, and you can expand on this foundation to create even more complex and amazing applications. Keep experimenting, keep learning, and happy coding!