Building a Simple API Server
What You'll Build Today
Welcome to Day 20! Today is a massive milestone. For the last 19 days, you have been writing Python scripts—code that runs on your computer, does a job, and then stops.
But think about the apps you use every day. Instagram, Google Maps, or ChatGPT aren't just scripts running on a laptop; they are Servers. They stay awake, waiting for requests, and send back answers.
Today, you will transform your Python skills into a web service. You are going to build your first API (Application Programming Interface).
You will build a "Math & Utility Server" that includes:
* FastAPI: The modern engine that powers high-performance web servers (used by Netflix, Uber, and Microsoft).
* Endpoints (GET and POST): The specific "doors" users knock on to get data or send instructions.
* Data Validation: Automatic checking to ensure users send numbers when you ask for numbers (so your code doesn't crash).
* Automatic Documentation: A professional website that documents your code for you, automatically.
By the end of this session, your code won't just run locally; it will be ready to talk to the rest of the world.
---
The Problem
Let's imagine you wrote a brilliant Python function that predicts housing prices based on your previous days' work. Your friend, who is building a mobile app for real estate, wants to use your logic.
How do you give it to them?
Let's try to build a server using Python's built-in "socket" library, which is the raw way computers talk to each other.
Look at this code. Do not run this. Just look at the pain involved in simply trying to say "Hello" to a web browser.
# The "Old School" Painful Way
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8000))
server.listen(5)
print("Listening on port 8000...")
while True:
# 1. Accept the connection
client, addr = server.accept()
# 2. Receive the raw bytes (0s and 1s)
request = client.recv(1024).decode('utf-8')
# 3. Manually parse the text to find what the user wants
# We have to handle strings like "GET /hello HTTP/1.1" manually!
headers = request.split('\n')
first_line = headers[0].split()
if len(first_line) > 1 and first_line[1] == '/hello':
# 4. Manually construct the HTTP response headers
response = 'HTTP/1.0 200 OK\n\nHello World'
else:
response = 'HTTP/1.0 404 NOT FOUND\n\nNot Found'
# 5. Send bytes back and close
client.sendall(response.encode('utf-8'))
client.close()
Why this is painful:
* String Parsing: You have to manually chop up text to figure out what the user wants.
* Byte Handling: You have to encode and decode text constantly.
* No Validation: If the user sends bad data, you have to write dozens of if statements to catch it.
* Concurrency: If two people hit this server at once, it might freeze.
There has to be a better way. We want to write a Python function and simply say, "Hey Internet, this function is available."
Enter FastAPI.
---
Let's Build It
We are going to use a library called FastAPI. It handles all the messy socket connections, byte decoding, and error handling for us. We just write functions.
Step 1: Installation and Setup
First, we need to install FastAPI and a tool called uvicorn.
* FastAPI: The framework used to build the API.
* Uvicorn: The server implementation that runs your FastAPI application (think of it as the engine).
Open your terminal/command prompt and run:
``bash
pip install fastapi uvicorn
bashmain.pyNow, create a file named
.main.pyStep 2: The "Hello World" of APIs
In
, we will create an instance of the app and one "endpoint." An endpoint is like a specific URL (like/homeor/profile).How to run it:from fastapi import FastAPI # Create the app instanceapp = FastAPI()
# Create a route (endpoint) # This decorator tells FastAPI: "When someone visits the root URL (/), run this function."@app.get("/")
def read_root():
return {"message": "Welcome to my API!"}
You cannot run this with the "Run" button in your IDE usually. You need to run the server command in your terminal:
uvicorn main:app --reload
jsonmain*
: The name of your file (main.py).app*
: The variable name inside the file (app = FastAPI()).--reload*
: Makes the server restart automatically whenever you save code changes (great for development). The Result:http://127.0.0.1:8000Open your web browser and go to
.{"message":"Welcome to my API!"}You should see:
/items/5Step 3: Path Parameters
Sometimes we want the URL itself to contain data, like
or/user/josh. This is called a Path Parameter.main.pyAdd this to
(you don't need to stop the server if you used--reload, just save the file):http://127.0.0.1:8000/echo/AliceTry it:@app.get("/echo/{name}")def echo_name(name: str):
return {"greeting": f"Hello, {name}!"}
Go to
in your browser.{"greeting": "Hello, Alice!"}Result:
{name}FastAPI takes the
from the URL and passes it into your function as the argumentname./calculate?num1=10&num2=20Step 4: Query Parameters and Type Conversion
What if we want to pass configuration options, like
? These are called Query Parameters.{name}If you define function arguments that aren't in the URL path (like
was), FastAPI assumes they are query parameters.http://127.0.0.1:8000/add?a=10&b=50Try it:@app.get("/add")def add_numbers(a: int, b: int):
return {
"number_a": a,
"number_b": b,
"result": a + b
}
Go to
{"number_a": 10, "number_b": 50, "result": 60}Result:
The Magic:http://127.0.0.1:8000/add?a=10&b=helloTry going to
.bYou will get a clear error message saying
must be an integer. FastAPI did that validation for you automatically because you added the type hint: int.GETStep 5: POST Requests and Pydantic Models
So far, we have used
requests. These are for getting data. They send data in the URL, which is not secure for passwords or good for large text blocks.POSTTo send data to the server (like a form submission), we use
. To handle the data cleanly, we use Pydantic Models. A Pydantic model defines what the data structure should look like.http://127.0.0.1:8000/docsAdd this import at the top of your file:
from pydantic import BaseModelNow add this code:
How to test this?# 1. Define the shape of the data we expectclass Item(BaseModel):
name: str
price: float
is_offer: bool = None # Optional field
# 2. Create a POST endpoint@app.post("/items/")
def create_item(item: Item):
# We can access item.name, item.price just like a standard Python objectcalculated_tax = item.price * 0.15
return {
"item_name": item.name,
"price_with_tax": item.price + calculated_tax,
"on_offer": item.is_offer
}
You can't test POST requests easily in the browser address bar. We need a tool. Luckily, FastAPI builds one for us.
Step 6: The Automatic Documentation (Swagger UI)
This is the best part of FastAPI.
Keep your server running. Go to ./You will see a professional, interactive dashboard listing all your endpoints (
,/echo,/add,/items).main.py* Click on the POST /items/ row.
* Click Try it out.
* Edit the JSON in the box.
* Click Execute.
You will see the server response right there. This "Swagger UI" is how developers test APIs.
---
Now You Try
It's time to expand your API. Add these three new endpoints to your
file. 1. The String Reverser (GET)/reverse/{word}Create an endpoint
.[::-1]* It should take a word from the path.
* It should return a JSON object with the original word and the reversed version.
Hint:* Python strings can be reversed with. 2. The Age Validator (GET)/check-ageCreate an endpoint
.age* It should accept a query parameter
(integer).{"status": "allowed"}* If age is 18 or over, return
.{"status": "blocked"}* If under 18, return
. 3. The User Registration (POST)UserCreate a Pydantic model called
withusername(str) andemail(str)./registerCreate a POST endpoint
.User* Accept the
model.{"message": "User [username] registered with email [email]"}* Return a message saying
./chat---
Challenge Project: The Mock Chatbot
We are preparing for the next phase where we connect to real AI. Today, let's build the structure for a chat endpoint.
The Goal: Create aendpoint that accepts a user's message and returns a "fake" AI response. Requirements:ChatInputCreate a Pydantic model named containing:message*
(str)model_name*
(str) - e.g., "gpt-4" or "claude"ChatOutputCreate a Pydantic model named containing:user_message*
(str)ai_response*
(str)POSTCreate a endpoint at/chat.messageInside the function: * If
contains the word "hello", theai_responseshould be "Hi there! I am a simple bot."message* If
contains "bye", the response should be "Goodbye!"ChatOutput* Otherwise, the response should be "I don't understand, but I'm listening."
Return the . Example Input (JSON):
{
"message": "hello computer",
"model_name": "basic-bot"
}
Example Output (JSON):
json
{
"user_message": "hello computer",
"ai_response": "Hi there! I am a simple bot."
}
`
Hints:
* Remember to import
BaseModel.
* Use
if "hello" in input_data.message.lower(): for case-insensitive matching.
* Test this using the
/docs page (Swagger UI).
---
What You Learned
Today you moved from writing scripts to building Services.
* FastAPI: You initialized a web server in 3 lines of code.
* Decorators: You used
@app.get and @app.post` to route traffic.
* JSON: You learned that APIs speak in dictionaries (JSON).
* Pydantic: You learned how to enforce data rules (Schema) so your server doesn't crash on bad input.
Why This Matters:When we start using OpenAI or HuggingFace in the next few days, you will notice something: They work exactly like the API you just built. You will send them a POST request with a JSON body (the prompt), and they will return a JSON body (the answer). By building your own API today, you now understand how the entire AI ecosystem communicates.
Phase 2 Complete!You have mastered Python basics, data structures, files, and now APIs. You have the software foundations required to be an AI Engineer.
Tomorrow: We enter Phase 3. We will look at The LLM Landscape—understanding the massive AI models that are changing the world, and how we can choose the right one for our projects.