Hello FastAPI

Dipak MaliDipak Mali
5 min read

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!

0
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.