Skip to content
Reviewed by Bipeen Sinha Nov 04, 2026

Home › Guides › FastAPI tutorial

Tutorial · 2026

FastAPI Tutorial 2026: Build Your First Python API in 8 Steps

This FastAPI tutorial builds a working Python REST API in 8 steps: install, one route, automatic docs, Pydantic validation, typed responses, an async endpoint, tests and deployment. You need Python 3.10 or newer and roughly 90 minutes. Everything runs locally at 127.0.0.1:8000 before anything is deployed.

4 November 202611 min readBy the 360 Digital Transformation Editorial Team
Key takeaways
  • Install the server, not just the library. Since FastAPI 0.112.0, plain pip install fastapi leaves you without a web server. You want pip install "fastapi[standard]".
  • Pydantic is the actual feature. Your type hints become request validation, response filtering and OpenAPI docs at the same time. That one mechanism is what you are really learning.
  • Automatic docs land free at /docs. Swagger UI at /docs, ReDoc at /redoc, machine-readable schema at /openapi.json, all generated from your function signatures.
  • async is not a speed button. Writing async def around a blocking database driver makes things slower, not faster. The rule is in step 6.
  • Latest release is 0.162.0, dated 23 October 2026 on the project's GitHub releases page, and the PyPI listing requires Python 3.10 or newer.

Picture a two-person data team at a mid-size Indian insurer. Their claims triage model works beautifully in a notebook, and the mobile team has asked for "just an endpoint". Three weeks later there is still no endpoint, because nobody on the data side has shipped an HTTP service and the backend team has a full sprint. That is the most common reason a good model never reaches production, and it is a one-afternoon problem. We will build exactly that service, endpoint by endpoint, and containerise it.

What You Will Build In This FastAPI Tutorial

Three endpoints, in this order. GET /health, so a load balancer has something to poll. POST /claims/score, which takes a claim, validates it and returns a risk band with a reason. GET /claims/{claim_id}, which reads one scored claim back. A complete, deployable service, small enough to hold in your head.

The three numbers that pin this tutorial to today

Check these before you copy any command from an older tutorial, because two of the three changed in the last two years.

0.162.0
Latest FastAPI release, dated 23 October 2026 on the project's GitHub releases page
3.10+
Minimum Python on FastAPI's PyPI listing; 3.9 has been dropped
8000
Default port for fastapi dev, bound to 127.0.0.1 with reload on, per the CLI's own man page

Versions and defaults checked on 4 November 2026 against the FastAPI GitHub releases page, PyPI and the fastapi-cli documentation.

The build path: FastAPI for beginners, stage by stage

Work the eight stages in order. Each leaves you something you can run, which matters more than it sounds: if step 5 breaks, the damage is in step 5.

Eight stages from empty folder to deployed container

Nothing here needs a cloud account until stage 8, so you can do the first seven on a laptop with no internet.

Step 1

Virtual environment and install

Isolate the environment and pull in FastAPI with its server. You finish when fastapi --version prints a number.

Step 2

One route in one file

A single main.py with GET /health, returning JSON in your browser.

Step 3

Read the automatic docs

Open /docs and fire a real request from the browser. Now you know where your API's contract lives.

Step 4

A Pydantic request model

Describe the claim payload as a class and get a 422 that names the exact bad field.

Step 5

A typed response and a path parameter

Add response_model and GET /claims/{claim_id}, so no internal field can leak by accident.

Step 6

async, dependencies and a model call

Wire in a dependency and one real model call that waits on the network without blocking everything else.

Step 7

Tests with TestClient

Three pytest functions, no running server, green in under a second.

Step 8

Containerise and deploy

A seven-line Dockerfile and a host choice, ending in a public URL with a known cold-start cost.

Stage structure reflects the official FastAPI tutorial's ordering, checked 4 November 2026.

How FastAPI Actually Handles A Request

Most FastAPI confusion comes from not knowing which layer does what. There are four, and only one is yours. Uvicorn owns the socket and speaks HTTP. Starlette does routing and middleware. Pydantic parses and validates the body. Your function is called last, with clean Python objects, and FastAPI serialises what you return.

That order is why a bad request never reaches your code: a missing field becomes a 422 before your first line runs. You do not write that check, and you should not.

One POST request through the four layers

Note where validation sits: in front of your handler, which is why you never write an if-field-is-missing check.

The FastAPI request lifecycle for a POST to /claims/score A client POST passes through Uvicorn, the Starlette router and Pydantic validation. Valid data reaches the handler, which returns a Python object that response_model filters before it is serialised to JSON and returned. Invalid data returns a 422 without reaching the handler. 422 Unprocessable Entity Client mobile app Uvicorn ASGI server Starlette router Pydantic model validates & coerces Your handler score_claim() response_model filters output JSON response 200 OK HTTP POST ASGI scope route match invalid body valid data Python object JSON bytes to client

Layer behaviour per the official FastAPI documentation and the fastapi-cli reference, checked 4 November 2026.

Netflix is the clearest public example at scale: it open sourced Dispatch, its incident management orchestration framework, built on FastAPI, and Uber's Ludwig library uses FastAPI to spin up a REST server for model predictions. Microsoft, Uber and Netflix all appear in the testimonials on FastAPI's own README. The pattern is the one you are about to build: a Python service fronting something expensive with a typed HTTP contract.

FastAPI Tutorial Steps 1 To 3: Install, First Route, Automatic Docs

Step 1: environment and install

Here is the one thing older tutorials get wrong. FastAPI 0.112.0 split the optional dependencies out, so a bare pip install fastapi gives you the library with no server and no CLI. The [standard] extra is what pulls in fastapi-cli[standard], and that is where Uvicorn comes from.

mkdir claims-api && cd claims-api
python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install "fastapi[standard]" anthropic
fastapi --version

If fastapi: command not found comes back, you installed without the extra. Re-run the install line with the quotes exactly as written, because most shells treat the square brackets as a glob.

Step 2: Build A REST API In Python With One Route

Create main.py. Four lines is a working API.

from fastapi import FastAPI

app = FastAPI(title="Claims Triage API", version="0.1.0")

@app.get("/health")
def health() -> dict[str, str]:
    return {"status": "ok"}

Run it with the CLI rather than calling Uvicorn by hand. According to the fastapi-cli man page, fastapi dev binds 127.0.0.1 on port 8000 with reload enabled, which is exactly what you want locally and exactly what you do not want in production.

fastapi dev main.py
# then, in another terminal:
curl -s http://127.0.0.1:8000/health
# {"status":"ok"}

Step 3: the automatic docs are the point

Open http://127.0.0.1:8000/docs. Swagger UI is there, /health is listed, and a Try it out button sends a real request. Nothing generated that page except your function signature. The official FastAPI reference confirms three default paths, worth memorising because they are the first thing you send a frontend developer.

Path What it serves Who actually uses it
/docs Swagger UI, interactive You, while building; QA, while poking
/redoc ReDoc, read-only reference The frontend or mobile developer consuming you
/openapi.json The raw OpenAPI schema Client code generators, API gateways, contract tests

That third row is the one people miss. Point a generator at /openapi.json and your mobile team gets a typed client for free, which removes an entire category of argument about field names. It is also what you upload to a managed gateway later, a layer covered in our AI gateway guide.

Steps 4 And 5: Pydantic Model Validation And Typed Responses

This is what makes FastAPI worth learning. Describe your data once as a Python class, and that single declaration becomes the request parser, the validator, the error messages, the response filter and the documentation.

One compatibility fact first. The official FastAPI migration guide records that the pydantic.v1 shim added in 0.119.0 was removed again in 0.128.0, so current releases expect Pydantic v2 models. If a 2023 blog post is giving you orm_mode or @validator errors, that is why.

Step 4: a request model that rejects bad input

from datetime import date
from typing import Literal
from pydantic import BaseModel, Field

class ClaimIn(BaseModel):
    policy_no: str = Field(min_length=6, max_length=20)
    amount_inr: float = Field(gt=0, le=5_000_000)
    incident_date: date
    category: Literal["motor", "health", "property"]
    description: str = Field(default="", max_length=500)

Those constraints are not decoration. gt=0 rejects a zero-rupee claim at the edge, Literal keeps a category typo out of your scoring logic, and date means "2026-10-14" arrives as a real date object you can subtract from today. Now the handler:

@app.post("/claims/score")
def score_claim(claim: ClaimIn):
    days_old = (date.today() - claim.incident_date).days
    score = 0.0
    if claim.amount_inr > 200_000:
        score += 0.4
    if days_old > 90:
        score += 0.3
    if claim.category == "motor" and claim.amount_inr > 500_000:
        score += 0.2
    band = "high" if score >= 0.6 else "medium" if score >= 0.3 else "low"
    return {"band": band, "score": round(score, 2), "days_old": days_old}

Send it something wrong on purpose. This is the output that convinces people:

curl -s -X POST http://127.0.0.1:8000/claims/score \
  -H "Content-Type: application/json" \
  -d '{"policy_no":"AB1","amount_inr":-5,"incident_date":"14-10-2026","category":"marine"}'
{"detail":[
 {"type":"string_too_short","loc":["body","policy_no"],
  "msg":"String should have at least 6 characters"},
 {"type":"greater_than","loc":["body","amount_inr"],
  "msg":"Input should be greater than 0"},
 {"type":"date_from_datetime_parsing","loc":["body","incident_date"],
  "msg":"Input should be a valid date or datetime"},
 {"type":"literal_error","loc":["body","category"],
  "msg":"Input should be 'motor', 'health' or 'property'"}]}

Four errors, every one pointing at a field path, in a single 422 response. You wrote none of that. A frontend developer can build error handling directly against that structure, and the loc array maps straight onto form fields.

Step 5: a typed response and a path parameter

Returning a bare dict works, but it means nothing stops you from accidentally including an internal field. Declare what goes out:

class ClaimOut(BaseModel):
    claim_id: str
    band: Literal["low", "medium", "high"]
    score: float
    reason: str

CLAIMS: dict[str, ClaimOut] = {}   # a real service uses Postgres here

@app.post("/claims/score", response_model=ClaimOut, status_code=201)
def score_claim(claim: ClaimIn) -> ClaimOut:
    ...  # scoring logic from step 4
    out = ClaimOut(claim_id=f"CLM-{len(CLAIMS) + 1:05d}", band=band,
                   score=round(score, 2),
                   reason=f"{days_old} days old, Rs {claim.amount_inr:,.0f}")
    CLAIMS[out.claim_id] = out
    return out

@app.get("/claims/{claim_id}", response_model=ClaimOut)
def get_claim(claim_id: str) -> ClaimOut:
    if claim_id not in CLAIMS:
        raise HTTPException(status_code=404, detail="Claim not found")
    return CLAIMS[claim_id]

Add HTTPException to your fastapi imports. Two things happened quietly. Anything you return that is not declared on ClaimOut is stripped before it leaves the process, which is a real data leak control rather than a style preference. And claim_id is extracted and typed from the URL with no parsing code.

The dict-as-database is the one shortcut here: restart the process and every scored claim is gone. Turning it into a real schema with fact and dimension tables is the modelling work a live Microsoft Fabric data engineering program spends its time on.

Also read: What is a REST API, in plain English, if the difference between a path parameter and a query parameter is still fuzzy.

Step 6: async, Dependencies And A Real Model Call

Back to the insurer. The triage score is rule-based, and the claims team wants a one-line explanation for the adjuster. That means calling a model over the network, which means waiting, which is the first time async earns its keep.

Here is the rule, and most beginners get it backwards. Use async def when the function awaits something; plain def when it does not. FastAPI runs a plain def handler in a threadpool, so blocking work there is safe. An async def handler calling a blocking library freezes the event loop, and throughput collapses under load while your CPU sits idle. Unsure? Use def.

import os
from functools import lru_cache
import anthropic
from fastapi import Depends

@lru_cache
def get_client() -> anthropic.AsyncAnthropic:
    return anthropic.AsyncAnthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

@app.post("/claims/explain")
async def explain(claim: ClaimIn,
                  client: anthropic.AsyncAnthropic = Depends(get_client)) -> dict[str, str]:
    msg = await client.messages.create(
        model="claude-opus-5-5",
        max_tokens=300,
        system="You are a claims adjuster. Two sentences, no speculation.",
        messages=[{"role": "user", "content": claim.model_dump_json()}],
    )
    text = next(b.text for b in msg.content if b.type == "text")
    return {"explanation": text}

Two patterns there are worth keeping permanently. Depends(get_client) is dependency injection: the client is built once, cached, and handed to any endpoint that asks, and a test can swap it for a fake via app.dependency_overrides. And msg.content is a list of typed blocks, not a string, so check .type before reading .text. Serving a model behind a typed contract like this, with retries and cost controls on top, is the core of an AI Engineer course covering RAG and AI agents; the deployment and monitoring half of the job is the MLOps Engineer course mapped to AI-300.

What usually goes wrong here
  • You put the API key in the source file. Then you push it. Read it from the environment, keep a .env out of git, and if a key has ever touched a commit, rotate it rather than deleting the line.
  • You call a blocking SDK inside async def. Symptom: fine with one user, terrible with twenty, CPU near zero. Either use the async client, as above, or change the handler to plain def.
  • You build the client inside the handler. A new HTTPS connection pool per request adds latency you will spend a day hunting. That is what the cached dependency prevents.
  • You return the model's text straight to the user. For a regulated workflow like claims, that output needs a human gate before anyone sees it, which is a design decision, not a code one.

Step 7: Test It Before You Trust It

FastAPI ships a test client that runs your app in-process. No server, no port, no sleep statements. Install pytest, create test_main.py, and you have a safety net in ten lines.

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)

def test_health():
    assert client.get("/health").json() == {"status": "ok"}

def test_rejects_negative_amount():
    r = client.post("/claims/score", json={
        "policy_no": "POL123456", "amount_inr": -1,
        "incident_date": "2026-09-01", "category": "motor"})
    assert r.status_code == 422
    assert r.json()["detail"][0]["loc"] == ["body", "amount_inr"]

def test_high_band_for_large_old_claim():
    r = client.post("/claims/score", json={
        "policy_no": "POL123456", "amount_inr": 600_000,
        "incident_date": "2026-05-01", "category": "motor"})
    assert r.status_code == 201
    assert r.json()["band"] == "high"

Run pytest -q for three passes in well under a second. The middle test pays for itself: it pins the error contract, so the day someone loosens a constraint a test fails instead of a mobile app. Our CI/CD walkthrough has the workflow file that runs it on every push.

Step 8: Deploy A FastAPI App Without Surprises

Local to public is where the weekend disappears, almost always for the same two reasons: the app is bound to 127.0.0.1 inside a container, so nothing outside can reach it, and reload is left on in production. Both are one flag.

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["fastapi", "run", "main.py", "--host", "0.0.0.0", "--port", "8000"]

Note fastapi run, not fastapi dev, and --host 0.0.0.0 so the process listens on every interface inside the container. Build and check it locally before you push anywhere:

docker build -t claims-api .
docker run --rm -p 8000:8000 -e ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY claims-api
curl -s localhost:8000/health

Also read: Docker tutorial for beginners: containerise a Python API in 7 steps, which goes layer by layer through the same Dockerfile.

Where to put it

Option Free tier reality Pick it when
Render free web service Spins down after 15 minutes without traffic and takes roughly a minute to wake; 750 free instance hours a month per workspace, per pricing trackers checked 4 November 2026 A portfolio project or a demo where a cold start is only mildly embarrassing
Render Starter instance Listed at $7 a month, which removes the spin-down Anything another team depends on, including an interview demo you will link for months
A VM you manage (EC2, Azure VM) Costs whatever the instance costs, plus your time on TLS, systemd and patching You need a VPC, a private database, or compliance sign-off
Container service (ECS, Container Apps) Pay per running container; scales to zero on some plans Traffic is spiky and you already have the cloud account

Confirm those numbers on the provider's own page before planning around them, because free tiers move and trackers lag. My default is the free tier plus a README note about the cold start, then the $7 instance the moment someone other than me is clicking. Once a VPC and a load balancer are involved you are doing cloud architecture rather than Python, which is where the AWS Solutions Architect and DevOps course and its Azure AZ-305 and AZ-400 counterpart pick up; doing it inside a client's network against their sign-off process is the day job of a forward deployed engineer.

Errors you will actually hit

What you see Real cause Fix
fastapi: command not found Installed without the [standard] extra, so no CLI and no Uvicorn pip install "fastapi[standard]", quotes included
Container runs, nothing reachable Bound to 127.0.0.1 inside the container Add --host 0.0.0.0 to the fastapi run command
422 on a request you think is valid Field name, type or enum mismatch; read the loc array Compare the payload against /docs, which shows the exact schema
405 Method Not Allowed @app.get on an endpoint you are POSTing to Match the decorator to the verb
Fast alone, slow under load, idle CPU Blocking call inside an async def handler Switch the handler to def, or use the async client
/docs renders blank Swagger UI loads assets from a CDN your network blocks Self-host the static assets, or test from an unrestricted machine
orm_mode or @validator errors Pydantic v1 code on a current FastAPI; the v1 shim was removed in 0.128.0 Migrate the model to Pydantic v2 syntax
Live course

From a working endpoint to a production AI service, built live with you

Live online·Sat–Sun, 8–11 PM IST·100+ hrs·Rs 24,999

The AI Engineer course teaches you to build agents that plan, use tools and act, certified on both Microsoft Copilot Studio and Claude Code, the two stacks job postings are actually naming. The API layer in this tutorial is step one of what you build and ship over 16 weeks.

Explore the course
AI Engineer Course: Generative AI, RAG & AI Agents (Microsoft + Claude Certified)

Where FastAPI Is The Wrong Answer

FastAPI gives you no admin interface, no ORM, no auth system and no migrations. Building a content-heavy product with user accounts, roles and a back office? Django ships most of that on day one. Reaching for FastAPI there means rebuilding Django badly over six months.

It is not the fastest thing available either: if your bottleneck is raw throughput on CPU-bound work, Python is the constraint and no framework fixes it. FastAPI is right when you have Python logic worth exposing, a contract worth enforcing and consumers who need documentation. And the OpenAPI generation is excellent until you need an unusual response shape, at which point you are reading Starlette source. Budget an afternoon for your first awkward case.

What To Learn Immediately After This FastAPI Tutorial

Three things next, in this order. Swap the dict for Postgres with SQLModel or SQLAlchemy, because persistence changes how you structure everything. Add authentication: an API key header for service-to-service calls, OAuth with JWTs for user-facing ones. Then add structured logging and a request ID, so the 2am page is traceable. Skip two things tutorials push too early: you do not need Kubernetes for one container, and you do not need GraphQL because someone mentioned it in a meeting.

Most FastAPI for beginners material stops at the hello-world route, which is why so many people can start a server and still cannot ship a service. If I were on that insurer's data team on Monday, I would spend one evening on steps 1 to 7, push the container on day two with the dict still in place, and hand the mobile team a /docs link the same week. Ship the ugly version that validates its input, then replace the storage; waiting until the design is right is how three weeks became three months. For the wider map, the full certifications overview lays out the Microsoft and Claude tracks and the CCDV-F developer prep course covers building against the Claude API. Also read: Python interview questions with model answers. When you want the next layer built with you rather than read about, sit in on a free webinar, then the AI Engineer course is where this endpoint becomes a real service.

Frequently asked questions

Is this FastAPI tutorial suitable for complete beginners?

Yes, with one prerequisite: you should be comfortable writing a Python function and running a terminal command. No prior web development, HTTP or async experience is needed. Run the code at every step rather than reading ahead, because the error messages are part of the lesson.

How do I install FastAPI and run it for the first time?

Create a virtual environment, then run pip install "fastapi[standard]" with the quotes. The plain pip install fastapi has not included a web server since version 0.112.0. Then run fastapi dev main.py, which the CLI's man page documents as binding 127.0.0.1 on port 8000 with reload enabled.

Should I use async def or def for my FastAPI endpoints?

Use async def only when the function body awaits something, such as an async HTTP client or an async database driver. Use plain def otherwise; FastAPI runs those in a threadpool, so blocking work is safe there. A blocking call inside async def stalls the event loop and throughput falls sharply under concurrency.

Which Python version does FastAPI need in 2026?

FastAPI's PyPI listing requires Python 3.10 or newer; 3.9 has been dropped. The latest release is 0.162.0, dated 23 October 2026 on the project's GitHub releases page. Python 3.12 is a safe default and is what the step 8 Dockerfile uses.

Why am I getting a 422 error when my JSON looks correct?

A 422 is Pydantic rejecting the body before your handler runs, and the response says where. Read the loc array in detail for the exact field path and msg for the reason. Usual culprits: a date in the wrong format, a string where a number is expected, or a value outside a Literal.

How do I deploy a FastAPI app for free?

Containerise it with the step 8 Dockerfile and push it to a host with a free tier. Render's free web services spin down after 15 minutes without traffic and take roughly a minute to wake, with 750 free instance hours a month per workspace according to pricing trackers checked in November 2026. Verify current terms on the provider's own page, and keep --host 0.0.0.0 or nothing will reach your container.

Do I need FastAPI to become an AI engineer?

Not specifically, but you need the skill it teaches: putting a typed, documented, tested HTTP interface in front of Python logic. Almost every RAG service, agent backend and model endpoint ships that way, and FastAPI is the most common choice for it in Indian postings for those roles.

About this guide. 360 Digital Transformation is an Anthropic Partner and a Microsoft Authorized Training Partner. Other certification bodies, vendors and employers named here are not affiliated with us. Tools and versions change quickly; commands and figures cited were checked on 4 November 2026.

Learn it live with 360 Digital Transformation

Turn this guide into a skill employers can see

Live weekend cohortEMI available

AI Engineer Course: Generative AI, RAG & AI Agents (Microsoft + Claude Certified)

Rs. 27,999Rs. 37,239 Save 24%

Tools and platforms you work with

  • Python
  • Claude API
  • FAISS
  • ChromaDB
  • LangChain
  • LangGraph
  • CrewAI
  • AutoGen (AG2)
Add bonus modules
See the syllabus and batch dates

Other live courses on this career path