DEV Community

Cover image for FastAPI for AI Engineers - Part 2: Building Your First CRUD API
Ananya S
Ananya S

Posted on

FastAPI for AI Engineers - Part 2: Building Your First CRUD API

In the previous article, we explored why FastAPI has become one of the most popular backend frameworks for modern AI applications.

If you haven't read the previous post, check it out: https://dev.to/zeroshotanu/fastapi-for-ai-engineers-part-1-why-every-ai-backend-is-moving-toward-fastapi-45fg

Now it's time to build something practical.

Most backend applications revolve around four basic operations:

  • Create
  • Read
  • Update
  • Delete

Together, these operations are known as CRUD.

Whether you're building:

  • a social media application,
  • an e-commerce platform,
  • a chatbot,
  • or an AI agent,

CRUD operations are the foundation of backend development.

In this article, we'll build a simple Student Management API while learning:

  • Path Parameters
  • Query Parameters
  • GET Requests
  • POST Requests
  • PUT Requests
  • DELETE Requests

Creating Sample Data

Let's start with a small dataset.

from fastapi import FastAPI

app = FastAPI()

students = [
    {
        "id": 1,
        "name": "Ananya",
        "department": "CSE",
        "cgpa": 8.9
    },
    {
        "id": 2,
        "name": "Rahul",
        "department": "ECE",
        "cgpa": 8.4
    },
    {
        "id": 3,
        "name": "Priya",
        "department": "IT",
        "cgpa": 9.1
    }
]
Enter fullscreen mode Exit fullscreen mode

Run the application:

uvicorn main:app --reload
Enter fullscreen mode Exit fullscreen mode

Open Swagger UI:

http://127.0.0.1:8000/docs
Enter fullscreen mode Exit fullscreen mode

Path Parameters

A path parameter is part of the URL itself.

/student/2
Enter fullscreen mode Exit fullscreen mode

Here, 2 is the path parameter.
Think of path parameters as:

"I know exactly which resource I want."

Examples:

/users/10
/products/25
/orders/1001
/student/2
Enter fullscreen mode Exit fullscreen mode

Let's fetch a specific student using their ID.

@app.get("/student/{id}")
def get_student_info(id: int):

    for user in students:
        if user["id"] == id:
            return user

    return {"message": "Student not found"}
Enter fullscreen mode Exit fullscreen mode

Request:

/student/2
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "id": 2,
    "name": "Rahul",
    "department": "ECE",
    "cgpa": 8.4
}
Enter fullscreen mode Exit fullscreen mode

Query Parameters

A query parameter appears after the ? in a URL.

/student?department="CSE"
Enter fullscreen mode Exit fullscreen mode

They are commonly used for:

  • filtering
  • searching
  • sorting
  • pagination

Let's implement the same endpoint using a query parameter.

@app.get("/students")
def get_students(department: str):

    filtered_students = []

    for student in students:
        if student["department"] == department:
            filtered_students.append(student)

    return filtered_students
Enter fullscreen mode Exit fullscreen mode

Request:

/student?department="CSE"
Enter fullscreen mode Exit fullscreen mode

Response:

{
    "id": 1,
    "name": "Ananya",
    "department": "CSE",
    "cgpa": 8.9
}
Enter fullscreen mode Exit fullscreen mode

All students in CSE department would be filtered.
Query parameters are often optional and are used to modify, filter, or search results.

Path vs Query Parameters

Path Parameter Query Parameter
Part of URL path Appears after ?
Identifies a resource Filters or searches
/student/1 /student?id=1

GET Request

GET requests are used to retrieve data.

@app.get("/students")
def get_all_students():
    return students
Enter fullscreen mode Exit fullscreen mode

Response:

[
    {
        "id": 1,
        "name": "Ananya",
        "department": "CSE",
        "cgpa": 8.9
    },
    {
        "id": 2,
        "name": "Rahul",
        "department": "ECE",
        "cgpa": 8.4
    },
    {
        "id": 3,
        "name": "Priya",
        "department": "IT",
        "cgpa": 9.1
    }
]
Enter fullscreen mode Exit fullscreen mode

Request Bodies with Pydantic

When users send data to our API, FastAPI needs a way to validate that the incoming data has the correct structure.

This is where Pydantic comes in.

Pydantic allows us to define the expected shape of incoming data using Python classes.

For example, every student should have:

  • an ID
  • a name
  • a department
  • a CGPA

We can define this structure using a Pydantic model.

from pydantic import BaseModel

class Student(BaseModel):
    id: int
    name: str
    department: str
    cgpa: float
Enter fullscreen mode Exit fullscreen mode

Now FastAPI automatically validates incoming requests.

For example, this request is valid:

{
"id": 4,
"name": "Karthik",
"department": "AI",
"cgpa": 8.8
}

But if someone sends:

{
"id": "four",
"name": "Karthik"
}

FastAPI will automatically return a validation error because:

id should be an integer
required fields are missing

This saves us from writing validation code manually.
We'll explore Pydantic, validation, optional fields, custom validators, and advanced request handling in a dedicated article later in this series.

POST Request

POST requests are used to create new resources.

from pydantic import BaseModel

class Student(BaseModel):
    id: int
    name: str
    department: str
    cgpa: float
Enter fullscreen mode Exit fullscreen mode
@app.post("/student")
def add_student(student: Student):

    students.append(student.dict())

    return {
        "message": "Student added successfully",
        "student": student
    }
Enter fullscreen mode Exit fullscreen mode

Request Body:

{
    "id": 4,
    "name": "Karthik",
    "department": "AI",
    "cgpa": 8.8
}
Enter fullscreen mode Exit fullscreen mode

PUT Request

PUT requests are used to update existing resources.

@app.put("/student/{id}")
def update_student(id: int, updated_student: Student):

    for index, user in enumerate(students):

        if user["id"] == id:

            students[index] = updated_student.dict()

            return {
                "message": "Student updated successfully",
                "student": updated_student
            }

    return {"message": "Student not found"}
Enter fullscreen mode Exit fullscreen mode

Request:

PUT /student/2
Enter fullscreen mode Exit fullscreen mode

DELETE Request

DELETE requests are used to remove resources.

@app.delete("/student/{id}")
def delete_student(id: int):

    for index, user in enumerate(students):

        if user["id"] == id:

            deleted_student = students.pop(index)

            return {
                "message": "Student deleted successfully",
                "student": deleted_student
            }

    return {"message": "Student not found"}
Enter fullscreen mode Exit fullscreen mode

Request:

DELETE /student/3
Enter fullscreen mode Exit fullscreen mode

CRUD Summary

Operation HTTP Method
Create POST
Read GET
Update PUT
Delete DELETE

CRUD operations form the foundation of almost every backend application you'll build.


What's Next?

Right now, our data exists only in memory.

If the server restarts, everything disappears.

In the next article, we'll connect FastAPI with SQLite and MySQL so our application can store data permanently, just like real-world production systems.

Top comments (0)