Skip to content

2.3.3 Project: Web API Development

Web API request-response architecture diagram

This project takes Python from scripts to the server side. You will use FastAPI to wrap functionality into interfaces that other programs can call, understand how APIs connect models, applications, and users, and lay the groundwork for later AI application development.

  • Understand what an API is and why AI engineers need to know how to write APIs
  • Learn how to use the FastAPI framework to build a Web API
  • Master the basic design principles of RESTful APIs
  • Build an AI service interface that can be called by other programs

Why do AI engineers need to know how to write APIs?

Section titled “Why do AI engineers need to know how to write APIs?”

You trained a great AI model — then what?

Training the model is only the first step. To let others use your model, you need to wrap it as an API service:

Your AI model → wrapped as an API → called by mobile apps / websites / other programs
Examples:
- ChatGPT model → provide service through an API → called by various apps
- Image recognition model → through an API → users upload images and get recognition results
- Recommendation algorithm → through an API → e-commerce websites show recommended products

So APIs are the bridge between AI models and the real world.


API (Application Programming Interface) = Application Programming Interface.

Simply put, an API is a “conversation window” between programs.

Going to a restaurant to eat is just like making an API call:

You (client) → tell the server (API) "one bowl of beef noodles" (request)
The server → passes it to the kitchen (server)
The kitchen → makes the noodles
The server → brings the noodles to you (response)

You do not need to know how the kitchen makes the noodles. You only need to know how to order (send a request) and how to receive the food (get a response).

ConceptDescriptionAnalogy
URL (endpoint)The address of the APIRestaurant address
HTTP methodType of operationOrdering / returning a dish / adding a dish
Request bodyThe data you sendThe dish you want
ResponseThe returned resultThe dish served to you
Status codeWhether the operation succeeded200 = success, 404 = no such dish
MethodUseExample
GETRetrieve dataGet task list
POSTCreate dataAdd a new task
PUTUpdate data (entirely)Modify all task information
DELETEDelete dataDelete a task

Terminal window
pip install fastapi uvicorn
LibraryPurpose
fastapiWeb framework for writing APIs
uvicornASGI server for running FastAPI apps

Create the file main.py:

from fastapi import FastAPI
# Create the app instance
app = FastAPI(title="My First API", version="1.0")
# Define an endpoint
@app.get("/")
def root():
return {"message": "Hello, World!", "status": "running"}
@app.get("/hello/{name}")
def hello(name: str):
return {"message": f"Hello, {name}!", "name": name}

Start the server:

Terminal window
uvicorn main:app --reload
  • main = file name (main.py)
  • app = FastAPI instance name
  • --reload = automatically restart after code changes (for development)

Open your browser and visit:

  • http://127.0.0.1:8000 → see Hello World
  • http://127.0.0.1:8000/hello/Xiaoming → see a personalized greeting
  • http://127.0.0.1:8000/docsautomatically generated interactive API documentation!

Let’s turn the task manager from the previous command line project into a Web API:

"""
Task Management API
Run: uvicorn main:app --reload
Docs: http://127.0.0.1:8000/docs
"""
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from datetime import datetime
# Create the app
app = FastAPI(
title="Task Management API",
description="A simple RESTful task management interface",
version="1.0"
)
# ---------- Data models ----------
class TaskCreate(BaseModel):
"""Request body for creating a task"""
title: str
priority: str = "medium"
class TaskUpdate(BaseModel):
"""Request body for updating a task"""
title: str | None = None
priority: str | None = None
done: bool | None = None
class Task(BaseModel):
"""Full task data"""
id: int
title: str
priority: str
done: bool
created_at: str
# ---------- Mock database (in-memory storage) ----------
tasks_db: list[dict] = []
next_id: int = 1
# ---------- API endpoints ----------
@app.get("/")
def root():
"""API homepage"""
return {
"name": "Task Management API",
"version": "1.0",
"endpoints": {
"View all tasks": "GET /tasks",
"Create a task": "POST /tasks",
"View a single task": "GET /tasks/{task_id}",
"Update a task": "PUT /tasks/{task_id}",
"Delete a task": "DELETE /tasks/{task_id}",
"API documentation": "GET /docs"
}
}
@app.get("/tasks")
def get_tasks(done: bool | None = None):
"""
Get all tasks.
Optional parameter:
- done: filter completed (true) or incomplete (false) tasks
"""
if done is not None:
filtered = [t for t in tasks_db if t["done"] == done]
return {"count": len(filtered), "tasks": filtered}
return {"count": len(tasks_db), "tasks": tasks_db}
@app.post("/tasks", status_code=201)
def create_task(task: TaskCreate):
"""Create a new task"""
global next_id
new_task = {
"id": next_id,
"title": task.title,
"priority": task.priority,
"done": False,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
tasks_db.append(new_task)
next_id += 1
return {"message": "Task created successfully", "task": new_task}
@app.get("/tasks/{task_id}")
def get_task(task_id: int):
"""Get a task by its ID"""
for task in tasks_db:
if task["id"] == task_id:
return task
# Task not found, return 404
raise HTTPException(status_code=404, detail=f"Task {task_id} does not exist")
@app.put("/tasks/{task_id}")
def update_task(task_id: int, task_update: TaskUpdate):
"""Update a task"""
for task in tasks_db:
if task["id"] == task_id:
if task_update.title is not None:
task["title"] = task_update.title
if task_update.priority is not None:
task["priority"] = task_update.priority
if task_update.done is not None:
task["done"] = task_update.done
return {"message": "Update successful", "task": task}
raise HTTPException(status_code=404, detail=f"Task {task_id} does not exist")
@app.delete("/tasks/{task_id}")
def delete_task(task_id: int):
"""Delete a task"""
for i, task in enumerate(tasks_db):
if task["id"] == task_id:
removed = tasks_db.pop(i)
return {"message": "Delete successful", "task": removed}
raise HTTPException(status_code=404, detail=f"Task {task_id} does not exist")
@app.get("/stats")
def get_stats():
"""Get task statistics"""
total = len(tasks_db)
done = sum(1 for t in tasks_db if t["done"])
return {
"total": total,
"done": done,
"pending": total - done,
"completion_rate": f"{done/total:.1%}" if total > 0 else "0%"
}
Terminal window
# Start the server
uvicorn main:app --reload

Then open http://127.0.0.1:8000/docs, and you can test all APIs directly in the browser.

You can also test with the command line:

Terminal window
# Create a task
curl -X POST http://127.0.0.1:8000/tasks \
-H "Content-Type: application/json" \
-d '{"title": "Learn FastAPI", "priority": "high"}'
# View all tasks
curl http://127.0.0.1:8000/tasks
# Complete a task
curl -X PUT http://127.0.0.1:8000/tasks/1 \
-H "Content-Type: application/json" \
-d '{"done": true}'
# Delete a task
curl -X DELETE http://127.0.0.1:8000/tasks/1

Or use Python’s requests library:

import requests
BASE_URL = "http://127.0.0.1:8000"
# Create a task
resp = requests.post(f"{BASE_URL}/tasks", json={"title": "Learn Python", "priority": "high"})
print(resp.json())
# Get all tasks
resp = requests.get(f"{BASE_URL}/tasks")
print(resp.json())

FastAPI uses Pydantic to validate request data — you define the data model, and FastAPI checks it automatically:

from pydantic import BaseModel, Field
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=100, description="Task title")
priority: str = Field(default="medium", description="Priority: high/medium/low")
# If the user sends invalid data
# POST /tasks {"title": ""} → automatically returns a 422 error (title too short)
# POST /tasks {} → automatically returns a 422 error (missing title)
# POST /tasks {"title": "OK"} → success, priority uses the default value "medium"

You do not need to write validation code by hand — Pydantic and FastAPI handle it for you.


Right now the data is stored in memory, so it disappears when the server restarts. Change it to save data in a JSON file.

@app.get("/tasks/search")
def search_tasks(keyword: str):
"""Search tasks by keyword"""
keyword_lower = keyword.lower()
return [task for task in tasks if keyword_lower in task.title.lower()]

When there are many tasks, support paginated responses:

GET /tasks?page=1&size=10

Preview for later: create a /predict endpoint that accepts text input and returns sentiment analysis results.

Project reference and review notes
  1. Add persistence by loading tasks at startup and saving after each create, update, or delete. Keeping the stored JSON shape close to the response shape makes debugging simpler.
  2. Add /tasks/search with case-insensitive substring matching on the title.
  3. Add pagination by accepting page and size, then slicing the task list before returning the response.
  4. Treat the AI endpoint as an extension only after the CRUD API is stable. Reuse the same validation and error-handling style so the new route behaves predictably.
  5. Self-check: open /docs, test each endpoint with curl, and confirm that invalid payloads produce FastAPI’s automatic 422 validation errors.

  • The API can start and be accessed normally
  • Full CRUD (create, read, update, delete) operations are implemented
  • Pydantic is used for data validation
  • Proper error handling is in place (HTTPException)
  • The automatically generated API docs (/docs) work correctly
  • All endpoints can be tested with curl or requests
VersionGoalDelivery focus
Basic versionMake the minimal loop run end to endCan input, process, and output, and keep a set of examples
Standard versionShape it into a presentable projectAdd configuration, logs, error handling, a README, and screenshots
Challenge versionApproach portfolio qualityAdd evaluation, comparison experiments, failure sample analysis, and next-step roadmap

It is recommended to finish the basic version first. Do not chase a huge, all-in-one project at the beginning. Each time you upgrade to a new version, write down in the README what new capability was added, how it was verified, and what problems remain.

Keep this page’s proof of learning as a small evidence card:

Project Goal
CLI, scraper, API, AI API call, or integrated Python workshop target
Run Command
exact command used to start the project
Artifact
output file, API response, JSON record, screenshot, or README note
Failure Check
dependency, network, parsing, route, input validation, or API-key issue
Expected Output
reproducible mini project folder with run result and one failure case