%%{init: {'theme': 'handDrawn'}}%%
sequenceDiagram
participant C as Your Script (Client)
participant S as FastAPI Server
C->>S: HTTP Request: GET /users/123
Note over S: Server runs get_user(123)
S->>C: HTTP Response: {"name": "Alice"}
1 The Web as Remote Function Calls
If you’ve ever written a Python script that calls a function, processes data, and prints results, you already know more than you think. Building a web API is essentially the same workflow, except the function call travels over the internet to reach someone else’s computer (or your own, running as a server simultaneously).
We’ll anchor this journey by building toward a practical LLM-powered chat application—something that actually streams responses from an AI like a real product. But every skill you learn here applies equally to building inventory systems, data dashboards, or mobile app backends.
Let’s start by rewiring how you think about calling functions.
1.1 What If You Could Call a Function on Someone Else’s Computer?
Picture yourself at your desk, running Python in a notebook or script. When you write:
user = get_user(user_id=123)Python jumps straight into get_user, grabs the data, and hands it back to you instantly. Everything happens in one place: your computer.
Now imagine you want your friend’s computer (or a server in the cloud) to run that get_user lookup for you. You can’t just type the function name and expect their machine to hear you. You need a agreed-upon protocol for asking, and a format for the answer.
That protocol is HTTP, and that request-and-answer dance is the heartbeat of every FastAPI application.
Think of it like this: instead of calling a function directly, you’re sending a polite letter asking another computer to run a function for you. That letter includes: - The address (the URL, like /users/123) - The action (GET me the data, or POST this new data) - Any arguments (packed as JSON, the internet’s version of a Python dictionary)
When the other computer finishes, it sends a letter back containing your result, or a note (status code) explaining why it couldn’t help .
1.2 The HTTP Vocab
GET and POST are the two actions you’ll use most: - GET is for reading or fetching data - e.g. like opening a file or querying a dictionary. - Use GET when you want data but aren’t changing anything on the server. - POST is for actions or creating things - e.g. appending to a list, processing a payment, sending a prompt to an LLM, etc.
Status codes are the server’s way of communicating what happened: - 200 means “Success!” - 404 means “Not Found” - 422 means “You sent me bad data”
JSON is simply the wire format that lets Python dictionaries travel between computers. When you return {"message": "hello"} from FastAPI, it automatically becomes JSON bytes over the wire, and the client’s Python code receives it as {"message": "hello"} again. No manual translation required.
1.3 FastAPI App in 5 Minutes
We’ll create the smallest possible web application: just enough to see that “remote function call” in action.
1.3.1 Setting Up
You’ll need two Python packages: fastapi for the framework, and uvicorn for the server engine that listens for requests and pass them to your code (Think of it as the receptionist who answers the phone and routes calls to your FastAPI app).
pip install fastapi uvicorn[standard]Create a file named main.py and add exactly this:
from fastapi import FastAPI
# This creates your app instance—think of it as the switchboard
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello, world!"}That @app.get("/") line is doing something powerful: it’s mapping the URL address / to your Python function read_root. When someone (a browser, a script, or later your React frontend) sends a GET request to that address, FastAPI runs your function and ships back the dictionary as JSON.
1.3.2 Running the Development Server
We’ve just built a simple FastAPI app. To receive requests from other computers, we need to run it on a server. For development purposes, we don’t need to set up a full production server; instead, we can use Uvicorn, which is perfect for testing the app locally. In this case, “other computers” can be your own browser or tools like curl that send HTTP requests to your local machine.
In your terminal, navigate to the directory containing main.py, and run:
uvicorn main:app --reloadYou should see a message telling you the server is running at http://127.0.0.1:8000. Open your browser and visit that address, or use curl:
curl http://localhost:8000/The --reload flag means “watch my code and restart if I make changes”. (Try it out by changing the message in read_root and saving the file—you’ll see the server restart automatically.)
You’ll receive {"message":"Hello, world!"}: As you visit the root URL (http://localhost:8000/), your browser sends an HTTP GET request to the server. FastAPI sees that request, matches it to the read_root function, executes it, and sends back the return value as JSON.
1.3.3 Interactive Documentation
While your server is running, visit http://localhost:8000/docs in your browser. You’ll see a beautiful, interactive dashboard called Swagger UI.
This page is auto-generated from your Python code. It lists every endpoint you’ve defined (right now, just our root path), shows exactly what JSON structure it expects and returns, and even lets you click “Try it out” to fire test requests without leaving the browser.
1.4 Hands-On: Make the Web Server Talk Back
Let’s create two tiny endpoints you’ll actually use in real projects: a health check and an echo service.
Health check: Every production service needs a simple “are you awake?” endpoint that monitoring tools can ping. Add this to your
main.py:@app.get("/health") def health_check(): return {"status": "ok"}Save the file (Uvicorn will reload automatically), then visit
http://localhost:8000/health. You should see your status dictionary.Echo endpoint: This will be our playground for sending data (
POST) to the server and getting it back. Add this tomain.py:from pydantic import BaseModel class Message(BaseModel): text: str sender: str @app.post("/echo") def echo_message(message: Message): return { "received": message.text, "from": message.sender, "response": f"Echo: {message.text}" }BaseModelis a way to define the expected structure of the incoming JSON. FastAPI will automatically validate the incoming JSON: if someone sends malformed data, they’ll get a 422 error.
Test it in your Swagger UI at /docs: 1. Click the /echo endpoint, then “Try it out” 2. Paste this JSON into the request body: json { "text": "Hello from the internet", "sender": "Student" } 3. Click Execute. You should see your echoed response. You’ve just built an API that accepts structured input and returns structured output.
1.5 Recap: Terms You Learned
| Web Term | What it actually means in Python |
|---|---|
| Endpoint | The specific URL address that triggers your function—like a named entry point for remote calls |
| Request | The “letter” sent by the client containing the HTTP method, headers, and JSON body (arguments) |
| Response | The dictionary (or data) your function returns, wrapped with a status code (the exit code of the operation) |
| GET | The “read-only” method for fetching data—safe, like accessing a dictionary key |
| POST | The “action” method for sending data to be processed—like calling a function that appends to a list |
| JSON | The wire format that lets Python dictionaries travel between computers; think of it as pickle but human-readable and universal |
| Status Code | The numeric result of the operation: 200 for success, 404 for “not found,” 422 for “bad arguments” |