14.3 Building a CRUD API with SQLAlchemy
In this section, we will learn how to build a simple CRUD (Create, Read, Update, Delete) API using SQLAlchemy to interact with a PostgreSQL database. We'll use FastAPI, a modern web framework for building APIs in Python, alongside SQLAlchemy for managing the database operations.
The goal is to create an API that allows clients to:
- Create new records in the database.
- Read existing records from the database.
- Update existing records.
- Delete records.
14.3.1 Setting Up the Environment
First, you'll need to install the required libraries:
- FastAPI: A Python web framework for building APIs.
- SQLAlchemy: For ORM database management.
- Uvicorn: An ASGI server to run FastAPI.
- Pydantic: A library used by FastAPI to validate data.
Install Dependencies:
pip install fastapi uvicorn sqlalchemy psycopg2
These dependencies include:
- FastAPI: Web framework for building the CRUD API.
- Uvicorn: A fast ASGI server for serving the FastAPI application.
- SQLAlchemy: Database ORM for managing models and queries.
- psycopg2: PostgreSQL adapter for Python, used by SQLAlchemy.
14.3.2 Setting Up the Project Structure
Let's create a simple folder structure for our project:
crud_api_project/
│
├── app/
│ ├── __init__.py
│ ├── database.py
│ ├── models.py
│ ├── crud.py
│ └── main.py
│
└── requirements.txt
14.3.3 Connecting to the PostgreSQL Database
First, configure the database connection in the database.py
file using SQLAlchemy.
app/database.py
:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# PostgreSQL database connection URL
SQLALCHEMY_DATABASE_URL = "postgresql://testuser:password@localhost/testdb"
# Create the SQLAlchemy engine
engine = create_engine(SQLALCHEMY_DATABASE_URL)
# SessionLocal class to create a new session for each request
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Base class for creating models
Base = declarative_base()
# Dependency for creating a new session
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
In this file:
create_engine()
connects to the PostgreSQL database.SessionLocal()
creates a new session for each API request.get_db()
is a dependency function to manage the session lifecycle in API requests.
14.3.4 Defining the SQLAlchemy Models
Now, define the models representing the database schema in models.py
. We'll create a simple User model with columns like id, name, and email.
app/models.py
:
from sqlalchemy import Column, Integer, String
from .database import Base
# Define the User model
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
In this file:
- The User class defines the database schema for the users table.
- The table contains columns for id, name, and email.
14.3.5 Creating the CRUD Operations
Next, we create functions to handle CRUD operations using SQLAlchemy. This code will be written in crud.py
and will be used by the API routes to interact with the database.
app/crud.py
:
from sqlalchemy.orm import Session
from . import models
# Create a new user
def create_user(db: Session, name: str, email: str):
db_user = models.User(name=name, email=email)
db.add(db_user)
db.commit()
db.refresh(db_user)
return db_user
# Get a user by ID
def get_user(db: Session, user_id: int):
return db.query(models.User).filter(models.User.id == user_id).first()
# Get all users
def get_users(db: Session):
return db.query(models.User).all()
# Update a user's data
def update_user(db: Session, user_id: int, name: str, email: str):
user = db.query(models.User).filter(models.User.id == user_id).first()
if user:
user.name = name
user.email = email
db.commit()
db.refresh(user)
return user
# Delete a user by ID
def delete_user(db: Session, user_id: int):
user = db.query(models.User).filter(models.User.id == user_id).first()
if user:
db.delete(user)
db.commit()
return user
This file defines the core CRUD operations:
- Create a new user.
- Read a specific user or list all users.
- Update an existing user.
- Delete a user.
14.3.6 Creating Pydantic Schemas for Validation
In FastAPI, Pydantic models are used to define the structure and validate the input data for API requests. We will create two Pydantic models: one for the input when creating or updating a user and another for the output when returning user data.
app/schemas.py
:
from pydantic import BaseModel
# User schema for input validation
class UserCreate(BaseModel):
name: str
email: str
# User schema for output
class User(BaseModel):
id: int
name: str
email: str
class Config:
orm_mode = True
In this file:
UserCreate
is used for validating user creation input.User
is used for serializing the output data (ensuring compatibility with SQLAlchemy ORM models).
14.3.7 Creating the API Endpoints
Now, we'll create API endpoints for each CRUD operation in main.py
using FastAPI.
app/main.py
:
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import engine, get_db
# Create the FastAPI app
app = FastAPI()
# Create database tables on startup
models.Base.metadata.create_all(bind=engine)
# Create a user
@app.post("/users/", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.create_user(db=db, name=user.name, email=user.email)
return db_user
# Get a user by ID
@app.get("/users/{user_id}", response_model=schemas.User)
def read_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.get_user(db=db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
# Get all users
@app.get("/users/", response_model=list[schemas.User])
def read_users(db: Session = Depends(get_db)):
return crud.get_users(db=db)
# Update a user
@app.put("/users/{user_id}", response_model=schemas.User)
def update_user(user_id: int, user: schemas.UserCreate, db: Session = Depends(get_db)):
db_user = crud.update_user(db=db, user_id=user_id, name=user.name, email=user.email)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
# Delete a user
@app.delete("/users/{user_id}", response_model=schemas.User)
def delete_user(user_id: int, db: Session = Depends(get_db)):
db_user = crud.delete_user(db=db, user_id=user_id)
if db_user is None:
raise HTTPException(status_code=404, detail="User not found")
return db_user
In this file:
/users/
: POST route to create a new user./users/{user_id}
: GET route to retrieve a user by ID./users/
: GET route to retrieve all users./users/{user_id}
: PUT route to update a user by ID./users/{user_id}
: DELETE route to delete a user by ID.
14.3.8 Running the API
To run the FastAPI app, use Uvicorn as the ASGI server.
uvicorn app.main:app --reload
You can now interact with the API by visiting http://localhost:8000 and using tools like cURL, Postman, or the interactive Swagger UI provided by FastAPI at http://localhost:8000/docs.
**14.3.9 Testing the API
**
You can test the API using the following cURL commands or Postman requests.
1. Create a User (POST):
curl -X POST "http://localhost:8000/users/" -H "Content-Type: application/json" -d '{"name": "John", "email": "john@example.com"}'
2. Get a User (GET):
curl "http://localhost:8000/users/1"
3. Get All Users (GET):
curl "http://localhost:8000/users/"
4. Update a User (PUT):
curl -X PUT "http://localhost:8000/users/1" -H "Content-Type: application/json" -d '{"name": "John Doe", "email": "johndoe@example.com"}'
5. Delete a User (DELETE):
curl -X DELETE "http://localhost:8000/users/1"
14.3.10 Summary
In this section, we built a CRUD API using FastAPI and SQLAlchemy with PostgreSQL as the database. We covered:
- Creating models using SQLAlchemy.
- Writing CRUD operations to interact with the database.
- Using Pydantic for request validation and response serialization.
- Creating API endpoints for creating, reading, updating, and deleting data.
This simple CRUD API demonstrates how to efficiently manage database records using SQLAlchemy in combination with FastAPI.