Building a Simple CRUD API with FastAPI

Introduction

FastAPI is a modern, high-performance Python framework for building APIs. It is fast, easy to use, and scalable, making it an excellent choice for web development. In this post, we will build a CRUD API using FastAPI.

🚀 What you'll learn:

  • Setting up FastAPI
  • Creating API endpoints
  • Implementing CRUD operations (Create, Read, Update, Delete)
  • Running and testing the API using Swagger UI

🔗 New to FastAPI? Check out FastAPI's official documentation.


Step 1: Install Dependencies

1.1 Create a Virtual Environment

First, create a virtual environment to manage dependencies:

# Create a virtual environment  
python -m venv venv  

# Activate it (Linux/macOS)  
source venv/bin/activate  

# Activate it (Windows)  
venv\Scripts\activate

👉 Why use a virtual environment? It helps isolate dependencies, preventing conflicts between different projects.

1.2 Install Required Packages

Now install FastAPI along with Uvicorn:

pip install fastapi uvicorn

Explanation of Packages:

  • 🐍 fastapi → Web framework for building APIs
  • 🚀 uvicorn → ASGI (Asynchronous Server Gateway Interface) server to run FastAPI

Step 2: Create a Simple FastAPI App

2.1 Creating the FastAPI App

Create a new file main.py and add the following code:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Welcome to FastAPI!"}

2.2 Running the API Server

Start the server using Uvicorn. The --reload flag auto-updates the server when you save code changes:

uvicorn main:app --reload

Now open http://127.0.0.1:8000 in your browser. You should see:

{"message": "Welcome to FastAPI!"}

You can also test it via Swagger UI at http://127.0.0.1:8000/docs.

Swagger UI


Step 3: Define a Data Model

3.1 What is CRUD?

CRUD stands for Create, Read, Update, and Delete, the four basic operations performed on data. Here’s what each operation does:

  • Create: Add a new record (e.g., a new user)
  • Read: Retrieve existing records (e.g., get user details)
  • Update: Modify an existing record (e.g., change user email)
  • Delete: Remove a record (e.g., delete a user account)

Now let's define a data model for our users.

3.2 Creating the User Schema

To handle data properly, create a schemas.py file:

from pydantic import BaseModel, EmailStr
from typing import Optional

class UserBase(BaseModel):
    name: str
    email: EmailStr

class UserCreate(UserBase):
    pass

class UserUpdate(BaseModel):
    name: Optional[str] = None
    email: Optional[EmailStr] = None

🔹 Why Pydantic?

  • 🛡️ Ensures data validation automatically
  • ❌ Returns meaningful error messages for invalid data

3.3 Example Validation Error

Sending an invalid email:

{
  "name": "John Doe",
  "email": "not-an-email"
}

Response:

{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

Step 4: Create CRUD Endpoints

4.1 Implementing CRUD Operations

Now, update main.py to include CRUD operations:

from fastapi import FastAPI, HTTPException
from schemas import UserCreate, UserUpdate

app = FastAPI()

# Temporary storage (for demonstration purposes only, use a database in production)
users = []

@app.post("/users/")
def create_user(user: UserCreate):
    """Creates a new user and stores it in memory."""
    user_id = len(users)
    user_dict = user.model_dump()
    user_dict["id"] = user_id
    users.append(user_dict)
    return {"message": "User created successfully", "user": user_dict}

@app.get("/users/")
def read_users():
    """Retrieves all users."""
    return users

@app.get("/users/{user_id}")
def read_user(user_id: int):
    """Fetches a user by their ID. Returns 404 if the user is not found."""
    if user_id >= len(users) or user_id < 0:
        raise HTTPException(status_code=404, detail="User not found")
    return users[user_id]

@app.put("/users/{user_id}")
def update_user(user_id: int, user: UserUpdate):
    """Updates a user's name and/or email. Returns 404 if the user is not found."""
    if user_id >= len(users) or user_id < 0:
        raise HTTPException(status_code=404, detail="User not found")

    if user.name:
        users[user_id]["name"] = user.name
    if user.email:
        users[user_id]["email"] = user.email

    return {"message": "User updated successfully", "user": users[user_id]}

@app.delete("/users/{user_id}")
def delete_user(user_id: int):
    """Deletes a user by ID. Returns 404 if the user is not found."""
    if user_id >= len(users) or user_id < 0:
        raise HTTPException(status_code=404, detail="User not found")

    users.pop(user_id)
    return {"message": "User deleted successfully"}

🔹 Limitations:

⚠️ In-Memory Storage: Data resets on server restart (use a database like PostgreSQL for production).

🛡️ Security Note: Add authentication and error handling for production use.


Step 5: Test Your API

📌 Open http://127.0.0.1:8000/docs and test the endpoints using Swagger UI.

📌 API Endpoints Summary:

Method Endpoint Description
POST /users/ Create a new user
GET /users/ Get all users
GET /users/{id} Get a specific user
PUT /users/{id} Update a user
DELETE /users/{id} Delete a user

Swagger UI


Project Resources


Conclusion

You have successfully built a simple CRUD API with FastAPI! 🎉

🔹 Next steps:

  • Connect to a database (PostgreSQL)
  • Store sensitive data in .env files
  • Implement authentication

Stay tuned for the next post! 🚀

📚 Further Reading: