Lesson 2 - Your First API

Welcome to Your First API

In Lesson 1 you saw the big idea behind FastAPI: a typed Python function becomes a full web endpoint. Now you’ll make it real. By the end of this lesson you’ll have FastAPI installed, a working app saved in a file, a running server you can open in your browser, and an interactive documentation page that FastAPI builds for you with no extra effort. We’ll go one small step at a time, and we’ll finish with a way to test your endpoints without even starting a server — a technique we’ll use throughout the whole course.

By the end of this lesson, you will be able to:

  • Install FastAPI and its server with a single command
  • Write a minimal FastAPI app in a main.py file
  • Run that app with uvicorn and open it in your browser
  • Find and use the automatic interactive docs at /docs
  • Test an endpoint with FastAPI’s TestClient, no running server required

Let’s get something working.


Installing FastAPI

FastAPI is a Python package, so you install it with pip, Python’s package installer. We’ll pin an exact version so your results match this lesson exactly:

pip install "fastapi[standard]==0.138.1"

Two things are worth unpacking here. The ==0.138.1 part pins the version, meaning you get precisely the release this course was written against. The [standard] part is called an extra — an optional bundle of related packages that get installed alongside FastAPI. The standard extra is the one you almost always want, because it includes uvicorn, the program that actually runs your app and listens for web requests. (FastAPI defines what your API does; uvicorn is the engine that serves it.) Without an extra like this, you’d have to install a server separately.

The quotes around "fastapi[standard]==0.138.1" matter: some shells treat square brackets specially, and the quotes make sure the whole thing is passed to pip as one piece of text.

After it finishes, you can confirm the versions you have:

python -c "import fastapi, uvicorn; print(fastapi.__version__, uvicorn.__version__)"
0.138.1 0.49.0

That confirms FastAPI 0.138.1 and uvicorn 0.49.0 are ready to go.


Writing Your First App

Create a new file named main.py and type in the following. This is a complete, runnable FastAPI application:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}

Three lines of setup and one endpoint — let’s read it top to bottom.

from fastapi import FastAPI imports the FastAPI class, the central object that represents your whole application.

app = FastAPI() creates an instance of that class and names it app. This app object is your API. Every endpoint you add gets attached to it, and app is what the server will run. The name app is a convention, not a rule, but using it keeps your run command simple (you’ll see why in a moment).

@app.get("/") is a decorator — a line starting with @ that attaches extra behavior to the function right below it. This one tells FastAPI: “when a client sends a GET request to the path /, run this function.” GET is the HTTP method used for reading data, and / is the root path — the bare address of your site, like the front door. The combination of a method and a path (here, GET /) is what makes an endpoint.

def read_root(): is just an ordinary Python function. The name is up to you; FastAPI calls it whenever a request matches the decorator above it.

Finally, the function returns a plain Python dictionary, {"message": "Hello, FastAPI!"}. You don’t have to convert it to JSON yourself — FastAPI automatically turns the dictionary into a JSON response (the standard text format APIs use to send structured data) and sets the correct headers. Return a dict; the client receives JSON.

That’s the entire app. Now let’s run it.


Running It with uvicorn

From the same folder that contains main.py, start the server with:

uvicorn main:app --reload

Read main:app as module:variable. The part before the colon, main, is the file name without the .py extension. The part after the colon, app, is the variable inside that file you want to serve — the app = FastAPI() object you created. So main:app means “the app object living in main.py.” This is exactly why naming the variable app keeps things tidy: the command reads cleanly.

The --reload flag tells uvicorn to watch your files and automatically restart the server whenever you save a change. That makes development fast — edit, save, refresh the browser — but it’s meant for development only, not for a live production server.

When it starts, uvicorn prints something like this and then keeps running:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process using WatchFiles
INFO:     Application startup complete.

The address http://127.0.0.1:8000 is your app, running locally. 127.0.0.1 is a special address that always means “this same computer” (it’s often called localhost), and 8000 is the port — a numbered channel on your machine that uvicorn listens on by default. Open http://127.0.0.1:8000 in your browser and you’ll see your endpoint’s response:

{"message":"Hello, FastAPI!"}

That JSON is exactly the dictionary your read_root function returned. Your first API is live. To stop the server, press Ctrl+C in the terminal.

The interactive docs are free

While the server is running, visit http://127.0.0.1:8000/docs. FastAPI generates a complete, interactive documentation page — called Swagger UI — listing every endpoint and letting you send real requests from the browser by clicking “Try it out.” There’s also a second, cleaner docs style at /redoc, and the raw machine-readable description of your API at /openapi.json. You wrote none of these — FastAPI builds all three automatically from your code.

That /openapi.json file is worth a quick look: it’s the OpenAPI schema, a standard JSON document describing your whole API — every path, method, and response shape. Both /docs and /redoc are just pretty views of that one file. Right now its list of paths contains your single endpoint, /.


Adding a Second Endpoint (and Testing Without a Server)

Adding another endpoint is as easy as writing another decorated function. Let’s add one that reports a simple status. Update main.py so it looks like this:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}

@app.get("/status")
def read_status():
    return {"status": "ok"}

The new @app.get("/status") endpoint answers GET requests to /status with {"status": "ok"}. If your server is running with --reload, it picks up the change the moment you save, and visiting http://127.0.0.1:8000/status shows the new response. Two endpoints, two small functions — this is how APIs grow.

Now, opening a browser is fine for a quick check, but throughout this course we’ll verify endpoints a faster, more reliable way: with FastAPI’s TestClient. The TestClient lets you send requests to your app directly from Python code, with no server running at all. It’s perfect for checking your work and is the foundation of automated testing (a whole topic later in the course).

Here’s the pattern. You can run this in a separate script or a Python session:

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

response = client.get("/")
print(response.status_code, response.json())

We import TestClient and the app we built, then wrap the app in a client. Calling client.get("/") sends a GET request to the root endpoint and hands back a response object. From that response we read two useful things: .status_code, the numeric HTTP result code (200 means “OK, success”), and .json(), the response body parsed back into Python. Running it prints:

200 {'message': 'Hello, FastAPI!'}

Status 200 confirms the request succeeded, and the body is the dictionary your function returned. Let’s confirm the second endpoint and peek at the generated OpenAPI schema too:

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

print(client.get("/status").status_code, client.get("/status").json())

schema = client.get("/openapi.json").json()
print("paths:", list(schema["paths"].keys()))
200 {'status': 'ok'}
paths: ['/', '/status']

Both endpoints respond with status 200, and the OpenAPI schema already knows about both paths, / and /status — automatically. Whenever this course shows an endpoint’s output, this is how we produced it: real requests through TestClient, with the real status codes and JSON pasted in. You can run the same checks yourself.


Practice Exercises

Exercise 1: Install and confirm

Install FastAPI with the [standard] extra at version 0.138.1, then print the installed FastAPI and uvicorn versions to confirm it worked.

Hint

Run pip install "fastapi[standard]==0.138.1", then python -c "import fastapi, uvicorn; print(fastapi.__version__, uvicorn.__version__)". You should see 0.138.1 0.49.0. The [standard] extra is what brings in uvicorn.

Exercise 2: Decode the run command

A teammate runs uvicorn main:app --reload but their file is named server.py and their FastAPI object is called api. What command should they use instead, and what does --reload do?

Hint

The pattern is module:variable. With server.py and a variable named api, the command is uvicorn server:api --reload. The --reload flag makes uvicorn restart automatically when files change — handy for development, but not for production.

Exercise 3: Add and verify an endpoint

Add a third endpoint at /ping that returns {"ping": "pong"}, then verify it with TestClient. What status code and body should you expect?

Hint

Add @app.get("/ping") above a function returning {"ping": "pong"}. Then client.get("/ping") gives .status_code of 200 and .json() of {'ping': 'pong'}. The new path will also appear automatically in /openapi.json and on the /docs page.


Summary

You went from nothing to a running API. You installed FastAPI with pip install "fastapi[standard]==0.138.1", where the [standard] extra pulls in uvicorn, the server that runs your app. You wrote a minimal main.pyfrom fastapi import FastAPI, app = FastAPI(), and a @app.get("/") function returning a dictionary that FastAPI converts to JSON for you. You started it with uvicorn main:app --reload, opened it at http://127.0.0.1:8000, and discovered the automatic interactive docs at /docs (Swagger UI), the alternate view at /redoc, and the raw OpenAPI schema at /openapi.json. Finally, you learned to verify endpoints with TestClient — sending requests to your app from Python with no server running, reading back the status code and JSON.

Key Concepts

  • pip install "fastapi[standard]" — installs FastAPI plus the uvicorn server via the standard extra.
  • app = FastAPI() — the application object every endpoint attaches to.
  • @app.get("/") — a decorator binding a GET request on a path to a function (one endpoint).
  • uvicorn main:app --reload — run the app object from main.py; --reload auto-restarts in development.
  • Automatic docs/docs (Swagger UI), /redoc, and the /openapi.json schema, all generated for you.
  • TestClient — sends requests to your app in-process so you can verify endpoints without a server.

Why This Matters

Almost everything you build for the rest of this course starts from exactly this setup: an app, some decorated functions, a server to run them, and a quick way to check the result. Getting comfortable now with the install, the module:app run command, and TestClient verification means you’ll spend later lessons thinking about what your API does rather than how to run it. The automatic /docs page, in particular, will become your favorite tool — it grows with your code and lets you and anyone else explore the API without writing a single client.


Next Steps

Continue to Lesson 3 - Path Parameters

Capture values from the URL itself — like a task id — and let FastAPI validate them using type hints.

Back to Module Overview

Return to the Getting Started with FastAPI module overview


Continue Building Your Skills

You now have a real, running FastAPI app and a fast way to test it. Next you’ll make your endpoints dynamic — reading values straight out of the URL, like the id of a specific task, with FastAPI validating them for you from a single type hint.