Hello FastAPI


FastAPI is a modern Python framework that makes building APIs quick and painless. In this post, we’ll create a Patient Management System API to handle patient records—think creating, reading, updating, and deleting (CRUD) operations, plus some cool extras like sorting. Let’s jump into the code and see how it works.
The Code Setup
First, we need some imports and a FastAPI app:
from fastapi import FastAPI, Path, HTTPException, Query
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field, computed_field
from typing import Annotated, Literal, Optional
import json
app = FastAPI()
This sets up FastAPI and pulls in tools for handling requests, validating data, and working with JSON.
Defining Patient Data
We’ll use Pydantic to define what a patient record looks like. Here’s the Patient
model:
class Patient(BaseModel):
id: Annotated[str, Field(..., description='ID of the patient', examples=['P001'])]
name: Annotated[str, Field(..., description='Name of the patient')]
city: Annotated[str, Field(..., description='City where the patient is living')]
age: Annotated[int, Field(..., gt=0, lt=120, description='Age of the patient')]
gender: Annotated[Literal['male', 'female', 'others'], Field(..., description='Gender of the patient')]
height: Annotated[float, Field(..., gt=0, description='Height of the patient in mtrs')]
weight: Annotated[float, Field(..., gt=0, description='Weight of the patient in kgs')]
@computed_field
@property
def bmi(self) -> float:
bmi = round(self.weight / (self.height ** 2), 2)
return bmi
@computed_field
@property
def verdict(self) -> str:
if self.bmi < 18.5:
return 'Underweight'
elif self.bmi < 25:
return 'Normal'
elif self.bmi < 30:
return 'Overweight' # Fixed from 'Normal' for clarity
else:
return 'Obese'
This defines fields like id
, name
, age
, etc., with rules (e.g., age must be between 0 and 120). The bmi
and verdict
are calculated automatically—pretty handy!
We also have a PatientUpdate
model for partial updates:
class PatientUpdate(BaseModel):
name: Annotated[Optional[str], Field(default=None)]
city: Annotated[Optional[str], Field(default=None)]
age: Annotated[Optional[int], Field(default=None, gt=0)]
gender: Annotated[Optional[Literal['male', 'female']], Field(default=None)]
height: Annotated[Optional[float], Field(default=None, gt=0)]
weight: Annotated[Optional[float], Field(default=None, gt=0)]
Storing Data
We’ll use a JSON file as a simple database. Here are the helper functions:
def load_data():
with open('patients.json', 'r') as f:
data = json.load(f)
return data
def save_data(data):
with open('patients.json', 'w') as f:
json.dump(data, f)
Create a patients.json
file with an empty dictionary ({}
) to start.
Building the API Endpoints
Now, let’s create the endpoints!
Welcome Message
@app.get("/")
def hello():
return {'message': 'Patient Management System API'}
Hit http://localhost:8000/
and you’ll see a friendly greeting.
About the API
@app.get('/about')
def about():
return {'message': 'A fully functional API to manage your patient records'}
Visit /about
for a quick description.
View All Patients
@app.get('/view')
def view():
data = load_data()
return data
This returns all patient records from patients.json
.
View One Patient
@app.get('/patient/{patient_id}')
def view_patient(patient_id: str = Path(..., description='ID of the patient in the DB', example='P001')):
data = load_data()
if patient_id in data:
return data[patient_id]
raise HTTPException(status_code=404, detail='Patient not found')
Try /patient/P001
to get a specific patient. If the ID doesn’t exist, it throws a 404 error.
Sort Patients
@app.get('/sort')
def sort_patients(sort_by: str = Query(..., description='Sort on the basis of height, weight or bmi'), order: str = Query('asc', description='sort in asc or desc order')):
valid_fields = ['height', 'weight', 'bmi']
if sort_by not in valid_fields:
raise HTTPException(status_code=400, detail=f'Invalid field select from {valid_fields}')
if order not in ['asc', 'desc']:
raise HTTPException(status_code=400, detail='Invalid order select between asc and desc')
data = load_data()
sort_order = True if order == 'desc' else False
sorted_data = sorted(data.values(), key=lambda x: x.get(sort_by, 0), reverse=sort_order)
return sorted_data
Go to /sort?sort_by=bmi&order=desc
to sort patients by BMI in descending order. It checks for valid inputs and throws errors if they’re wrong.
Create a Patient
@app.post('/create')
def create_patient(patient: Patient):
data = load_data()
if patient.id in data:
raise HTTPException(status_code=400, detail='Patient already exists')
data[patient.id] = patient.model_dump(exclude=['id'])
save_data(data)
return JSONResponse(status_code=201, content={'message': 'patient created successfully'})
Send a POST request to /create
with a JSON body like:
{
"id": "P001",
"name": "John Doe",
"city": "New York",
"age": 30,
"gender": "male",
"height": 1.75,
"weight": 70
}
It adds the patient if the ID is unique.
Update a Patient
@app.put('/edit/{patient_id}')
def update_patient(patient_id: str, patient_update: PatientUpdate):
data = load_data()
if patient_id not in data:
raise HTTPException(status_code=404, detail='Patient not found')
existing_patient_info = data[patient_id]
updated_patient_info = patient_update.model_dump(exclude_unset=True)
for key, value in updated_patient_info.items():
existing_patient_info[key] = value
existing_patient_info['id'] = patient_id
patient_pydantic_obj = Patient(**existing_patient_info)
existing_patient_info = patient_pydantic_obj.model_dump(exclude='id')
data[patient_id] = existing_patient_info
save_data(data)
return JSONResponse(status_code=200, content={'message': 'patient updated'})
Send a PUT request to /edit/P001
with:
{
"name": "John Smith",
"weight": 75
}
It updates only the fields you send and recalculates bmi
and verdict
.
Delete a Patient
@app.delete('/delete/{patient_id}')
def delete_patient(patient_id: str):
data = load_data()
if patient_id not in data:
raise HTTPException(status_code=404, detail='Patient not found')
del data[patient_id]
save_data(data)
return JSONResponse(status_code=200, content={'message': 'patient deleted'})
Hit /delete/P001
with a DELETE request to remove that patient.
Running the API
Install FastAPI and Uvicorn:
pip install fastapi uvicorn
Save the code in main.py
and run:
uvicorn main:app --reload
Open http://localhost:8000/docs
in your browser to see the interactive API docs—FastAPI generates this for free!
Testing It Out
Use a tool like Postman or curl
to test. For example:
curl -X POST "http://localhost:8000/create" -H "Content-Type: application/json" -d '{"id": "P002", "name": "Jane Doe", "city": "LA", "age": 25, "gender": "female", "height": 1.65, "weight": 55}'
Check /view
to see the new patient.
What’s Cool About This?
Easy Validation: Pydantic checks your data for you.
Auto Docs: The
/docs
page is a game-changer.Error Handling:
HTTPException
makes errors simple to manage.Computed Fields: BMI and verdict update automatically.
That’s it! You’ve got a fully functional API with FastAPI. Play around with it, add more features, or hook it up to a real database. Happy coding!
Subscribe to my newsletter
Read articles from Dipak Mali directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Dipak Mali
Dipak Mali
Hello, I'm Dipak, a Junior Software Engineer with a strong foundation in React JS, Python, and Java. I’m a working professional focused on developing efficient software solutions in the dynamic adtech industry. Beyond core coding, I specialize in RESTful API design, web development, and have hands-on experience with technologies like Spring Boot, MySQL, Docker, and AWS cloud services. I thrive on creating scalable, user-centric applications and enjoy tackling complex problem-solving challenges. Eager to collaborate, I’m passionate about innovation, continuous learning, and making a meaningful impact through technology.