Building a CRUD API with FastAPI:
From an In-Memory Prototype to a Working Backend

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.


