← Back to FastAPI
Lesson 1.1 · Getting Started

What is an API, and Why FastAPI?

Learn what an API is, how a request and a response look, and why FastAPI is a good way to build one in Python. We build the same small API twice - by hand and with FastAPI - and send both the same requests.

Beginner25 min

What you will be able to do

  • Explain what an API is, using an everyday example
  • Name the parts of an HTTP request and response
  • Read JSON and say why APIs use it
  • Call an API from the terminal (curl) and from Python (httpx)
  • Say what FastAPI does for you, with a real side-by-side comparison
  • Know what FastAPI is built on: Starlette, Pydantic and Uvicorn

The idea, in plain English

Programs often need data or actions from other programs. A weather app asks a weather service for today’s forecast. A shop’s website asks the payment company to take a payment. A mobile app asks your server for the user’s orders. The way one program asks another is called an API - an Application Programming Interface.

Most web APIs work over HTTP, the same system your browser uses for web pages. The program that asks is the client. The program that answers is the server. The client sends a request ("give me the price of item 2"); the server sends back a response, usually as JSON - text that both programs can read.

FastAPI is a Python library for building the server side of an API. You write normal Python functions with type hints; FastAPI turns them into web endpoints, checks the incoming data against your type hints, and writes interactive documentation for you. In this lesson we see exactly what that means, by building one small API twice: once by hand with only Python’s standard library, and once with FastAPI.

Everything in this course was run with FastAPI 0.143.0 on Python 3.11, with Starlette 1.7.0, Pydantic 2.14.0 and Uvicorn 0.54.0. All outputs are copied from real runs.

Worked example: A "price of an item" API, written with only the standard library (23 lines) and with FastAPI (8 lines), tested with the same three requests.

request flowOne request, one responsestep 1 / 3

1 - The client sends a request

A request has a method (GET = "give me"), a path (/price/1), and headers with extra information.

method
GET
path
/price/1
header
Host: localhost:8111
body
none (GET)

A real request to our FastAPI price API, step by step. The lines with > and < are exactly what curl sent and received.

Words you will see in this course

Web APIs have a small set of words. Learn these first - every lesson uses them.

Small dictionary
APIApplication Programming Interface: a way for one program to ask another for data or actions.
ClientThe program that asks: a browser, a mobile app, curl, another server.
ServerThe program that answers. With FastAPI, that is your Python code.
HTTPThe rules for sending requests and responses on the web.
RequestWhat the client sends: a method, a path, headers, and sometimes a body.
ResponseWhat the server sends back: a status code, headers, and a body.
EndpointOne address the API answers, like GET /price/{item_id}.
JSONA text format for data: {"item_id": 1, "price": 40.0}. Easy for programs and people to read.
Status codeA number that says how it went: 200 OK, 404 Not Found, 422 bad input.

An everyday example: a restaurant

In a restaurant you do not walk into the kitchen. You look at the menu, tell the waiter what you want, and the waiter brings it back. The menu says what you can ask for. The waiter carries requests and answers. The kitchen does the work.

An API is the menu and the waiter for a program. The endpoints are the menu items ("GET /price/{item_id}"). HTTP is how the order travels. Your Python function is the kitchen. And just like a good waiter says "sorry, we have no dish number 9", a good API answers clearly when a request cannot be served - with a status code like 404.

What travels: a real request and response

We sent one request to the FastAPI version of our price API with curl -v, which prints everything it sends and receives. Lines starting with > are the request; lines starting with < are the response.

The request: the method GET, the path /price/1, the protocol HTTP/1.1, then headers. The response: the status line HTTP/1.1 200 OK, then headers - content-type: application/json tells the client the body is JSON - then the body itself, {"item_id":1,"price":40.0}.

curl -v shows the whole conversation
$ curl -v localhost:8111/price/1 > GET /price/1 HTTP/1.1 > Host: localhost:8111 > User-Agent: curl/8.7.1 > Accept: */* > < HTTP/1.1 200 OK < date: Sat, 10 Oct 2026 20:13:24 GMT < server: uvicorn < content-length: 26 < content-type: application/json < {"item_id":1,"price":40.0}
The parts of a request and a response
MethodWhat to do: GET (read), POST (create), PUT, PATCH, DELETE. Lesson 1.3.
PathWhich thing: /price/1. Can hold values, like the 1.
HeadersExtra information: who is asking, what format, how long the body is.
BodyThe data itself - in a POST request, or in the response.
Status codeThe result: 2xx success, 4xx the client’s mistake, 5xx the server’s mistake.

Example 1 - the API by hand

To see what FastAPI does for you, first look at an API without it. This version uses only Python’s standard library. It has one endpoint, GET /price/{item_id}, that returns the price of an item.

Look at how much work is ours: split the path into parts, check it is "price", turn the id into a number ourselves (and answer 400 if it is not), look up the item, turn the dict into JSON, and write the status and headers by hand. Every new endpoint would need all of this again.

Example 1 - by_hand.py (23 lines)
import json from http.server import BaseHTTPRequestHandler, HTTPServer PRICES = {1: 40.0, 2: 15.5} # item id -> price of one class Handler(BaseHTTPRequestHandler): def do_GET(self): parts = self.path.split("?")[0].strip("/").split("/") # "/price/1" -> ["price", "1"] if len(parts) != 2 or parts[0] != "price": return self.send_json(404, {"error": "not found"}) try: item_id = int(parts[1]) # check the type ourselves except ValueError: return self.send_json(400, {"error": "item_id must be a number"}) if item_id not in PRICES: return self.send_json(404, {"error": "no such item"}) self.send_json(200, {"item_id": item_id, "price": PRICES[item_id]}) def send_json(self, status, body): data = json.dumps(body).encode() self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(data))) self.end_headers() self.wfile.write(data) HTTPServer(("127.0.0.1", 8110), Handler).serve_forever()

Example 2 - the same API with FastAPI

Now the same endpoint with FastAPI. Read it line by line. FastAPI() creates the app. @app.get("/price/{item_id}") says: when a GET request comes to /price/something, call the function below, and put the "something" into item_id. The type hint item_id: int says it must be a whole number - FastAPI checks that for us.

If the item does not exist, we raise HTTPException with status 404. Otherwise we return a normal Python dict - FastAPI turns it into JSON. There is no path splitting, no number conversion, no headers by hand.

Example 2 - with_fastapi.py (8 lines)
from fastapi import FastAPI, HTTPException app = FastAPI() PRICES = {1: 40.0, 2: 15.5} # item id -> price of one @app.get("/price/{item_id}") def price(item_id: int): # the type hint is the check if item_id not in PRICES: raise HTTPException(status_code=404, detail="no such item") return {"item_id": item_id, "price": PRICES[item_id]}
Run both (two terminals)
$ python by_hand.py $ fastapi dev with_fastapi.py --port 8111 # Lesson 1.2 explains this command

Same requests, two APIs

We sent the same three requests to both. A good request: both answer 200 with the price. An unknown item: both answer 404.

The interesting one is /price/abc - "abc" is not a number. The hand-written API answers with the message we wrote ourselves. FastAPI answers on its own, with status 422 and a detailed error: the type of problem (int_parsing), where it is (loc: path, item_id), a message in plain words, and the bad input ("abc"). We did not write a single line for that.

And /docs: the hand-written API has no such page (404). FastAPI serves interactive documentation of every endpoint, built from your code. Lesson 1.2 opens it.

Output - curl, by hand vs FastAPI ([status code] at the end)
== /price/1 by hand: {"item_id": 1, "price": 40.0} [200] fastapi: {"item_id":1,"price":40.0} [200] == /price/abc by hand: {"error": "item_id must be a number"} [400] fastapi: {"detail":[{"type":"int_parsing","loc":["path","item_id"],"msg":"Input should be a valid integer, unable to parse string as an integer","input":"abc"}]} [422] == /price/9 by hand: {"error": "no such item"} [404] fastapi: {"detail":"no such item"} [404] == docs by hand /docs [404] fastapi /docs [200]
What FastAPI did for us
RoutingMatched /price/{item_id} and called our function.
Type conversionTurned "1" from the URL into the integer 1.
ValidationRejected "abc" with a clear 422 error - no code from us.
JSONTurned our dict into a JSON response with the right headers.
DocumentationBuilt /docs from the code.
Size8 lines instead of 23 - and the gap grows with every endpoint.

APIs are for programs: calling it from Python

A browser can open an API, but APIs are mainly for programs. Here a small Python program asks our API for the price of item 2, reads the JSON into a normal Python dict, and uses it - just as a shop app might.

We use httpx, an HTTP client for Python. It is installed together with fastapi[standard], so you already have it after Lesson 1.2.

Example 3 - client.py
import httpx # an HTTP client for Python (installed with fastapi[standard]) response = httpx.get("http://127.0.0.1:8111/price/2") print("status:", response.status_code) print("content-type:", response.headers["content-type"]) data = response.json() # JSON text -> a Python dict print("data:", data, "| type:", type(data).__name__) print("price of 3:", data["price"] * 3)
Output
status: 200 content-type: application/json data: {'item_id': 2, 'price': 15.5} | type: dict price of 3: 46.5

What FastAPI is made of

FastAPI does not do everything alone. It joins three well-known pieces. Starlette handles the web part: receiving requests, routing, sending responses. Pydantic handles data: checking types and values (the 422 error came from Pydantic). Uvicorn is the server program that listens on a port and passes requests to your app.

You will see all three names in error messages and logs - the "server: uvicorn" header above, and Pydantic’s "Input should be a valid integer" message. Knowing who does what helps you read them.

FastAPI’s building blocks (versions used in this course)
FastAPI 0.143.0Your app: routes, type hints, dependencies, docs.
Starlette 1.7.0The web toolkit underneath: requests, responses, routing.
Pydantic 2.14.0Data checking: types, values, error messages.
Uvicorn 0.54.0The server program that runs your app on a port.

When FastAPI is a good choice

FastAPI is built for APIs: programs that send and receive JSON. It fits well when you want your data checked automatically, documentation that stays up to date, and modern Python with type hints. It also supports async code (Module 7) for APIs that wait on other services.

It is less of a fit for a website that renders many HTML pages with forms, sessions and an admin panel - a full framework like Django gives you more of that ready-made. Many teams use both: Django or a frontend framework for pages, FastAPI for the API.

APIs at a glance

Create the app

One object for the whole API.

app = FastAPI()
An endpoint

A method and a path, on a function.

@app.get("/price/{item_id}")
Typed input

The type hint is the check.

def price(item_id: int):
An error

A status code and a message.

raise HTTPException(status_code=404, detail="no such item")
Return data

A dict becomes JSON.

return {"item_id": item_id, "price": 40.0}
See everything

Request and response in the terminal.

curl -v localhost:8111/price/1
Call from Python

JSON -> dict.

httpx.get(url).json()

Try it yourself

The code does not change. Swap the content string and the program does something else entirely.

Read the error

“Open http://localhost:8111/price/1.5 in your browser. Which field of the error tells you what went wrong?”

Add an item

“Add item 3 with price 99.0 to PRICES in both versions. Which one needed more changes to support a new endpoint, like /price/{item_id}/double?”

curl -v

“Run curl -v on /price/9. Find the status line and the content-type header in the output.”

Use the data

“Change client.py to print the total price of item 1 and item 2 together.”

What usually goes wrong

Thinking an API is a web page

A web page is for people; an API is for programs. It answers with data (JSON), not with a page to look at.

Ignoring the status code

A client must check the status before using the body. A 404 body is an error message, not a price.

✗ price = httpx.get(url).json()["price"]
✓ response = httpx.get(url)
if response.status_code == 200:
    price = response.json()["price"]
Checking types by hand

With FastAPI the type hint does it. Converting with int() yourself repeats work and gives worse error messages.

✗ def price(item_id):
    item_id = int(item_id)
✓ def price(item_id: int):

Key points

  • An API lets programs talk: a client sends a request, a server sends a response.
  • A request has a method, a path, headers and maybe a body; a response has a status code, headers and a body.
  • Web APIs usually speak JSON, which becomes a dict in Python.
  • FastAPI turns typed Python functions into endpoints, checks input, builds JSON and writes docs.
  • Our FastAPI version was 8 lines; by hand it was 23 - and it had no docs and weaker errors.
  • FastAPI = Starlette (web) + Pydantic (data) + Uvicorn (server).

Quick check before you move on

What is an API, in one sentence?
A way for one program to ask another program for data or actions.
What did FastAPI answer for /price/abc, and why?
422 with a detailed error, because the type hint item_id: int says it must be a whole number.
What are the three parts of a response?
A status code, headers, and a body.
What does each of Starlette, Pydantic and Uvicorn do?
Starlette handles requests and routing, Pydantic checks data, Uvicorn is the server that runs the app.

Interview questions

What is FastAPI, and what are its main advantages?

A Python framework for building APIs from type-hinted functions. It validates input and serializes output with Pydantic, generates OpenAPI documentation automatically, supports async, and keeps code short - built on Starlette and run by an ASGI server such as Uvicorn.

What is the difference between 400 and 422 in FastAPI?

422 Unprocessable Entity is what FastAPI returns automatically when request data fails validation. 400 Bad Request is a general client error you might raise yourself for other problems.

What are Starlette and Uvicorn, and how do they relate to FastAPI?

Starlette is the ASGI web toolkit FastAPI is built on (routing, requests, responses). Uvicorn is an ASGI server that runs the app and handles the network side.

Quiz

  1. 1.

    Which status code group means "the client made a mistake"?

  2. 2.

    What does content-type: application/json tell the client?

  3. 3.

    In our example, what turned "1" from the URL into the number 1?

  4. 4.

    Name one thing FastAPI gave us that the hand-written API did not have.

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...