Skip to main content

Command Palette

Search for a command to run...

Building a CRUD API with FastAPI:

From an In-Memory Prototype to a Working Backend

Updated
6 min readView as Markdown
Building a CRUD API with FastAPI:
A
I am a philosopher-writer and technical researcher working across applied philosophy, interdisciplinary analysis, and modern software technology. As a developer, I focus on backend engineering and data-intensive systems—working with Python, APIs, relational databases, and AI-enabled applications, with a ongoing emphasis on database architectures and distributed systems.

Introduction

When building a backend application, it can be useful to start with the simplest working version before introducing databases, authentication, and more complex architecture. In this tutorial, I begin with an in-memory FastAPI application to implement basic CRUD operations. This makes it easier to understand the request-response cycle, Pydantic models, path parameters, HTTP status codes, and error handling before moving on to persistent storage such as PostgreSQL. In that regard this is part of the series and it focuses on explaining how i am building the first version of that API step by step. By the end, the API will support:

POST    /assessments

GET     /assessments

GET     /assessments/{assessment_id}

PATCH   /assessments/{assessment_id}

DELETE  /assessments/{assessment_id}

The data will initially be stored in a Python list.

1. Create the FastAPI application

Start by importing FastAPI and creating an application instance.

from fastapi import FastAPI

app = FastAPI()

The app object represents the FastAPI application.

FastAPI uses Python decorators to connect HTTP requests to Python functions.

For example:

@app.get("/health")

async def health_check():

return {"status": "OK"}

Here:

@app.get("/health")

means:

When the server receives a GET request for /health, run the function below it.

We can make the health endpoint slightly more descriptive:

@app.get("/health")

async def health_check():

return {

"status": "OK",

"service": "Responsibility Lens API"

}

If the application is running correctly, visiting:

GET /health

returns:

{

"status": "OK",

"service": "Responsibility Lens API"

}

This gives a simple way to confirm that the API is available.

2. Create temporary in-memory storage

The second step is that before adding a database, I used a Python list:

assessments = []

Each assessment created by the API will be stored in this list.

Hypothetically from :

Please note that the phrase "move to", was used figuratively.

HTTP request

"move to"

FastAPI route

"move to"

Python logic

"move to"

assessments list

"move to"

HTTP response

This is useful for learning and early prototyping, but it has an important limitation: The data disappears whenever the application restarts, which means that every time you run the process you must be aware of that limitation. Later, this list will be replaced with PostgreSQL to rework that , denoting the significance of adding PostgreSQL.

3. Define the request model with Pydantic

The third step is to define an assessment that needs three pieces of information:

system_name

organisation

description

Instead of accepting arbitrary JSON, FastAPI can use a Pydantic model to define the expected request structure.

from pydantic import BaseModel

Then create the model:

class AssessmentCreate(BaseModel):

system_name: str

organisation: str

description: str

Now FastAPI expects a request such as:

{

"system_name": "AI Credit Review System",

"organisation": "Example Bank",

"description": "An AI-assisted system used to support credit decisions."

}

If required fields are missing or contain the wrong data type, FastAPI automatically rejects the request. This is one of the reasons Pydantic is useful in FastAPI applications: it creates a clear contract between the client and the backend.

4. Create an assessment

The forth step is to create an assesment and by that we create the first POST endpoint.

@app.post("/assessments")

async def create_assessment(assessment: AssessmentCreate):

assessment_data = assessment.model_dump()

assessment_data["id"] = len(assessments) + 1

assessments.append(assessment_data)

return assessment_data

There are several things happening here.

Receiving the request assessment:

AssessmentCreate tells FastAPI: Parse the incoming request body using the AssessmentCreate model.

So FastAPI first validates the JSON before our function runs.

4b.Converting the Pydantic object

The validated JSON object from FastAPi is a Pydantic model. To turn it into a normal Python dictionary we can use the model_dump() method:

assessment_data = assessment.model_dump()

For example:

AssessmentCreate(

system_name="AI Credit Review System",

organisation="Example Bank",

description="AI-assisted credit review."

)

becomes approximately:

{

"system_name": "AI Credit Review System",

"organisation": "Example Bank",

"description": "AI-assisted credit review."

}

4c. Creating an ID

For the next step because I am still working with the prototype, and yet to have add a database yet, I generated a simple identifier:

assessment_data["id"] = len(assessments) + 1

If the list is empty:

len(assessments) = 0

the first record receives:

id = 1

This is sufficient for the prototype, although it is important to note that with a real database, persistent identifiers should be generated.

4d. Saving the assessment

The record is then added to the list:

assessments.append(assessment_data)

and returned to the client: return assessment_data

5.Return the correct HTTP status code

The next step is to create a resource that should normally return:

201 Created rather than the default: 200 OK

This is becuase FastAPI provides HTTP status constants:

from fastapi import status

We can then update the route:

@app.post(

"/assessments",

status_code=status.HTTP_201_CREATED

async def create_assessment(assessment: AssessmentCreate):

assessment_data = assessment.model_dump()

assessment_data["id"] = len(assessments) + 1

assessments.append(assessment_data)

return assessment_data

Using: status.HTTP_201_CREATED is more readable than writing: status_code=201

Both work, but the named constant communicates the meaning of the status code.

6. Retrieve all assessments

At the stage, the next endpoint returns everything stored in the list.

@app.get("/assessments")

async def assessments_list():

return assessments

If two assessments have been created, the response could look like:

[

{

"system_name": "AI Credit Review System",

"organisation": "Example Bank",

"description": "AI-assisted credit review system.",

"id": 1

},

{

"system_name": "Clinical Decision Support",

"organisation": "Example Hospital",

"description": "AI-assisted clinical recommendation system.",

"id": 2

}

]

The application flow is now:

POST /assessments

create assessment then store in list

GET /assessments

read list then return assessments

That brings an end to the Part One of Building a CRUD API with FastAPI: From an In-Memory Prototype to a Working Backend.

In this part, we set up the FastAPI application, created our assessment model, and implemented the first CRUD operations for creating and retrieving assessments. In Part Two, we’ll complete the CRUD cycle by implementing update and delete operations, while looking more closely at partial updates, path parameters, and error handling.

Backend Engineering with FastAPI: From Prototype to Production

Part 1 of 1

A practical series on building backend systems with Python and FastAPI, following an application from a simple in-memory prototype through validation, testing, databases, authentication, deployment, and production architecture.

More from this blog