First FastAPI app
First FastAPI app
Enough theory. This lesson is where you finally write FastAPI code.
The goal here is to get the smallest possible API up and running, see real JSON come out of a real HTTP server, and meet the most surprising feature FastAPI ships with — an interactive documentation page that gets built for your API automatically, just by virtue of existing. You will not write the documentation. You will not configure it. It will simply be there, and you will be able to click buttons in it to call your own endpoints.
This is one of those lessons where the wow comes faster than the typing. Let’s go.
Setup
Create a new folder anywhere outside your other projects:
fastapi-hello/
└── main.py
One file. That’s the whole project for now.
Install FastAPI and the server that runs it:
pip install fastapi uvicorn
A quick explanation of those two:
- FastAPI is the framework — the library you’ll import in your code to define routes, request bodies, and response models.
- Uvicorn is the actual HTTP server that runs your FastAPI app. FastAPI by itself just describes what the API is; uvicorn handles the actual job of listening on a port, accepting connections, and shuttling requests in and out.
In Flask you had this kind of bundling hidden — flask run did both jobs. FastAPI separates them cleanly. You write FastAPI; uvicorn runs it. (In production you’d use Gunicorn behind uvicorn workers, but that’s for later.)
The smallest possible FastAPI app
Open main.py and type this:
# main.py
from fastapi import FastAPI
app = FastAPI(title="My first FastAPI app")
@app.get("/")
def read_root():
return {"message": "Hello from FastAPI"}
Six meaningful lines. Compare it mentally to what you wrote in Flask:
# Flask, for reference
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def read_root():
return jsonify(message="Hello from Flask")
Look at three differences:
-
@app.get("/")instead of@app.route("/"). FastAPI gives you method-specific decorators:@app.get,@app.post,@app.put,@app.patch,@app.delete. The method is in the decorator itself. No moremethods=["GET", "POST"]. -
No
jsonify. Returning a Python dict from a FastAPI view is the JSON response. FastAPI handles the conversion automatically. -
title="My first FastAPI app"in the constructor. That’s metadata for the auto-generated docs. We’ll see what it does in a moment.
That’s the entire app. It does one thing: respond to GET / with a JSON message.
Run it
From the project folder:
uvicorn main:app --reload
A quick breakdown of that command:
main— the name of the Python file (without.py).app— the name of theFastAPI()object inside that file.--reload— uvicorn will watch the file and restart automatically when you save changes. Use this in development; never in production.
You’ll see output like:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process
INFO: Started server process
INFO: Waiting for application startup.
INFO: Application startup complete.
The API is now serving on port 8000. Open a browser and visit http://127.0.0.1:8000/.
You should see, in your browser’s window:
{"message":"Hello from FastAPI"}
That’s a real HTTP server, returning real JSON, in response to a real request. You can also curl it:
curl http://127.0.0.1:8000/
{"message":"Hello from FastAPI"}
Same result. The browser is just one kind of client. Any client that can speak HTTP can call this endpoint.
The magic moment: /docs
Now do this. Visit:
http://127.0.0.1:8000/docs
A whole interactive documentation page loads. It lists every endpoint your API exposes (currently just one), shows the URL, the method, and a green “Try it out” button. Click the endpoint, click “Try it out”, click “Execute” — your API gets called, and the response shows up right there on the page.
This is called Swagger UI, and FastAPI generated it from your code without you doing anything. You wrote one route. FastAPI noticed. The docs appeared.
Try the alternate docs page too:
http://127.0.0.1:8000/redoc
That’s ReDoc — the same API documentation, in a different visual style. Some developers prefer the cleaner reading layout. Same content, different presentation. Both are generated from the same source.
Both pages are powered by something called an OpenAPI schema — a structured JSON document describing every endpoint, parameter, and response shape in your API. FastAPI builds it automatically as you write code, and exposes it at:
http://127.0.0.1:8000/openapi.json
Open that URL in a browser and you’ll see a large blob of JSON describing your API. Tools like Postman, Insomnia, code generators for client SDKs, and AI agents can read this file and instantly know how to call your API. That is the real superpower — your API isn’t just documented, it’s machine-readable.
You did not write any of this. None of it. You wrote one function.
Add a second route
Let’s prove the magic generalises. Add a second endpoint to main.py:
# main.py
from fastapi import FastAPI
app = FastAPI(title="My first FastAPI app")
@app.get("/")
def read_root():
return {"message": "Hello from FastAPI"}
@app.get("/about")
def about():
return {
"app": "My first FastAPI app",
"version": "0.1.0",
"description": "A tiny demo to learn the basics."
}
Save the file. Because you ran uvicorn with --reload, the server picks up the change automatically. You don’t need to restart anything.
Now refresh http://127.0.0.1:8000/docs in your browser. The new /about endpoint is already listed. Click it. Try it. Same dance — the response appears right there.
Visit http://127.0.0.1:8000/about directly:
{"app":"My first FastAPI app","version":"0.1.0","description":"A tiny demo to learn the basics."}
Notice the response is on one line, no indentation. JSON over the wire is compact. The browser will probably show it with a built-in formatter — most modern browsers prettify JSON automatically. Either way, the actual bytes coming from the server are the dense version.
What FastAPI did automatically
Take a step back and notice everything you got for free in this tiny app:
- JSON conversion. Python dict in, JSON out. No
jsonify, nojson.dumps, no explicitContent-Typeheaders. - HTTP status codes.
200 OKfor successful responses,404 Not Foundfor missing routes,405 Method Not Allowedif you POST to a GET endpoint. All set sensibly. - OpenAPI schema generation. Every route you add gets indexed and described in the schema.
- Interactive Swagger UI. Browse your API, try every endpoint, see every response — all in the browser.
- Alternate ReDoc UI. A different, cleaner doc layout.
- Auto-reload during development. Save the file, the server restarts.
You wrote: two functions, two decorators, one FastAPI() call. Everything else is the framework’s gift.
Try the unhappy paths
Two more requests to send, so you understand what FastAPI does when things go wrong.
Visit a URL that doesn’t exist:
curl http://127.0.0.1:8000/nope
Response:
{"detail":"Not Found"}
Status code: 404. Even errors are JSON. Your client never gets back HTML by mistake.
Use the wrong method:
curl -X POST http://127.0.0.1:8000/
Response:
{"detail":"Method Not Allowed"}
Status code: 405. The endpoint exists, but POST isn’t allowed on it — we only registered a GET handler.
These behaviours come straight from the REST conventions we discussed in Lesson 1. FastAPI implements them without you having to think about it.
Stop the server
When you’re done experimenting, stop uvicorn with Ctrl+C in the terminal where it’s running. The server shuts down cleanly. Run it again whenever you want to come back.
What this lesson didn’t cover
This was a deliberately tiny app. A few things we’ll get to soon:
- Dynamic URLs like
/tasks/42where42could be any number — that’s Lesson 4. - POST endpoints that accept JSON bodies — Lesson 5.
- Pydantic models for typed inputs and outputs — also Lesson 5.
- Validation — what happens when someone sends a malformed body — Lesson 6.
- Separate input/output schemas so you don’t leak password hashes in responses — Lesson 7.
- A real database, real CRUD, real error handling — Lesson 8’s mini project.
Each new piece will slot into the structure you already have here: a FastAPI app, a few decorated functions, and the OpenAPI docs that grow with you.
Common pitfalls
A few things to watch for:
- Forgot
--reload? No worries, but you’ll have to stop and restart uvicorn every time you editmain.py. With--reloadsaves are picked up automatically. Address already in use? Another process is on port 8000. Either find and kill it, or run uvicorn on a different port:uvicorn main:app --reload --port 8001.- The
/docspage is blank. Check the terminal — there’s probably an error in yourmain.py. Save again to retrigger the reload. - Returned a dict but got a 500? The dict contains something that can’t be JSON-serialised (a
datetime, a database row object, a set). For now stick to dicts, lists, strings, numbers, and booleans. We’ll learn how to serialise complex types in Lesson 7.
Summary
- A FastAPI app is a
FastAPI()instance plus decorated functions. @app.get("/...")registers a GET endpoint. Variants exist for every HTTP method.- Return a Python dict; FastAPI converts it to JSON automatically.
- Run the app with
uvicorn main:app --reloadduring development. - FastAPI generates an OpenAPI schema for free, and exposes interactive docs at
/docs(Swagger UI) and/redoc. - Errors are JSON too —
404and405come back as{"detail": "..."}.
Outcome
You’ve shipped a working FastAPI app. It’s tiny, but it’s a real HTTP server returning real JSON, and you’ve seen the auto-generated docs that come free with every FastAPI project. Next lesson, we go beyond hello-world — adding multiple routes, multiple methods, and seeing what it feels like to build the skeleton of a real resource-style API.