{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreiamuhlr3rwxyeuayvq5jbtzzwqzjylha3znxy2vov63uhanzyephm",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mpdpbct4t2p2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreie32tt537bq3plmfvgoj3qou7keop5x2q5cb4zvaqa6z56x66zi2q"
    },
    "mimeType": "image/webp",
    "size": 104836
  },
  "path": "/mihirmohapatra/fastapi-crash-course-build-a-crud-rest-api-in-40-lines-of-python-2m0",
  "publishedAt": "2026-06-28T09:38:51.000Z",
  "site": "https://dev.to",
  "tags": [
    "python",
    "fastapi",
    "webdev",
    "beginners",
    "View the full source on GitHub",
    "github.com/MihirMohapatra/fast-api-example",
    "@Valid",
    "@RequestBody",
    "@app.get",
    "@app.post",
    "@app.put",
    "@app.delete",
    "@Data",
    "@ResponseBody"
  ],
  "textContent": "> I spend most of my days writing Java and Kotlin for FinTech systems at scale. But every now and then, Python pulls me back in — and FastAPI is exactly the reason why. This is a hands-on crash course based on a project I published on GitHub.\n\n**👉 View the full source on GitHub**\n\n##  Why FastAPI?\n\nComing from a Spring Boot background, my first instinct was scepticism. _Another Python framework?_ But FastAPI is genuinely different:\n\n  * **Automatic interactive docs** via Swagger UI — no extra config needed\n  * **Request validation** powered by Pydantic — think of it as `@Valid` + `@RequestBody` baked in\n  * **Type hints as the source of truth** — Python's type system does the heavy lifting\n  * **Async-ready** from the ground up — ASGI all the way\n\n\n\nThe best part? You can build a fully working REST API in under 50 lines.\n\n##  What We're Building\n\nA **Tea CRUD API** — simple, but covers every REST pattern:\n\nMethod | Endpoint | What It Does\n---|---|---\nGET | `/` | Health / welcome message\nGET | `/teas` | List all teas\nPOST | `/teas` | Add a new tea\nPUT | `/teas/{tea_id}` | Update an existing tea\nDELETE | `/teas/{tea_id}` | Remove a tea\n\nNo database — we're using an in-memory list to keep the focus on FastAPI itself.\n\n##  Prerequisites\n\n  * Python 3.10+\n  * `pip`\n\n\n\nVerify:\n\n\n\n    python --version\n    pip --version\n\n\n##  Setup\n\n**Create and activate a virtual environment:**\n\n\n\n    # Windows (PowerShell)\n    python -m venv venv\n    .\\venv\\Scripts\\Activate\n\n    # macOS / Linux\n    python3 -m venv venv\n    source venv/bin/activate\n\n\n**Install dependencies:**\n\n\n\n    pip install \"fastapi[standard]\"\n\n\nThis single command installs FastAPI, Uvicorn (the ASGI server), and all optional extras — all you need to get running.\n\n##  The Full Code\n\nCreate a file called `main.py` and drop this in:\n\n\n\n    from fastapi import FastAPI\n    from pydantic import BaseModel\n    from typing import List\n\n    app = FastAPI()\n\n    class Tea(BaseModel):\n        id: int\n        name: str\n        origin: str\n\n    teas: List[Tea] = []\n\n    @app.get(\"/\")\n    def read_root():\n        return {\"message\": \"Welcome to chai code\"}\n\n    @app.get(\"/teas\")\n    def get_teas():\n        return teas\n\n    @app.post(\"/teas\")\n    def add_teas(tea: Tea):\n        teas.append(tea)\n        return tea\n\n    @app.put(\"/teas/{tea_id}\")\n    def update_tea(tea_id: int, update_tea: Tea):\n        for index, tea in enumerate(teas):\n            if tea.id == tea_id:\n                teas[index] = update_tea\n                return update_tea\n        return {\"error\": \"Tea not found\"}\n\n    @app.delete(\"/teas/{tea_id}\")\n    def delete_tea(tea_id: int):\n        for index, tea in enumerate(teas):\n            if tea.id == tea_id:\n                deleted = teas.pop(index)\n                return deleted\n        return {\"error\": \"Tea not found\"}\n\n\nThat's it. 43 lines. Let's break it down.\n\n##  Code Walkthrough\n\n###  1. The App Instance\n\n\n    app = FastAPI()\n\n\nThis is your application object — equivalent to `new SpringApplication()` in Spring Boot, but in one line with no annotations, XML, or config files.\n\n###  2. The Pydantic Model\n\n\n    class Tea(BaseModel):\n        id: int\n        name: str\n        origin: str\n\n\n`BaseModel` from Pydantic is the magic here. Any class that extends it automatically gets:\n\n  * **Request body parsing** — FastAPI reads the incoming JSON and maps it to this class\n  * **Type validation** — if `id` isn't an integer, FastAPI returns a `422 Unprocessable Entity` with a clear error message\n  * **Serialization** — returning a `Tea` instance automatically serializes it to JSON\n\n\n\nFor Java developers: think `@Data` + `@Valid` + `Jackson` — except it's one import and zero annotations on your fields.\n\n###  3. The In-Memory Store\n\n\n    teas: List[Tea] = []\n\n\nJust a Python list typed with `List[Tea]`. It acts as our database for this example. Doesn't survive restarts — but that's intentional for a learning project. You'd swap this with SQLAlchemy + PostgreSQL in a production setup.\n\n###  4. GET — List All Teas\n\n\n    @app.get(\"/teas\")\n    def get_teas():\n        return teas\n\n\nReturn the list directly. FastAPI serializes `List[Tea]` to a JSON array automatically. No `ResponseEntity`, no `@ResponseBody`, no boilerplate.\n\n###  5. POST — Add a Tea\n\n\n    @app.post(\"/teas\")\n    def add_teas(tea: Tea):\n        teas.append(tea)\n        return tea\n\n\nThe function parameter `tea: Tea` tells FastAPI: _\"expect a JSON body matching the`Tea` schema.\"_ Pydantic validates it. If validation fails, FastAPI returns a detailed 422 error before your function is even called.\n\n###  6. PUT — Update by ID\n\n\n    @app.put(\"/teas/{tea_id}\")\n    def update_tea(tea_id: int, update_tea: Tea):\n        for index, tea in enumerate(teas):\n            if tea.id == tea_id:\n                teas[index] = update_tea\n                return update_tea\n        return {\"error\": \"Tea not found\"}\n\n\n`{tea_id}` in the path is a **path parameter** — FastAPI extracts it and coerces it to `int` automatically. You get both a path param (`tea_id`) and a request body (`update_tea`) from a single function signature. Clean.\n\n###  7. DELETE — Remove by ID\n\n\n    @app.delete(\"/teas/{tea_id}\")\n    def delete_tea(tea_id: int):\n        for index, tea in enumerate(teas):\n            if tea.id == tea_id:\n                deleted = teas.pop(index)\n                return deleted\n        return {\"error\": \"Tea not found\"}\n\n\nLinear scan through our in-memory list — fine for learning, but you'd index by ID with a dict or use a database in production.\n\n##  Run It\n\n\n    python -m uvicorn main:app --reload\n\n\nExpected output:\n\n\n\n    INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)\n    INFO:     Started reloader process\n\n\nThe `--reload` flag means the server restarts automatically on every file save — great for development.\n\n##  Interactive API Docs (Free!)\n\nThis is FastAPI's killer feature. Open your browser:\n\nURL | What You Get\n---|---\n`http://127.0.0.1:8000/docs` | Swagger UI — test every endpoint interactively\n`http://127.0.0.1:8000/redoc` | ReDoc — clean, readable API reference\n\nNo extra setup. No Postman required for a quick test. The docs are generated directly from your type hints and Pydantic models.\n\n##  Quick Test via Swagger UI\n\n  1. Go to `http://127.0.0.1:8000/docs`\n  2. Click **POST /teas** → **Try it out**\n  3. Paste this body:\n\n\n\n\n    {\n      \"id\": 1,\n      \"name\": \"Assam Tea\",\n      \"origin\": \"India\"\n    }\n\n\n  1. Click **Execute**\n  2. Now hit **GET /teas** — you'll see your tea in the list\n\n\n\n##  What's Next (Production Roadmap)\n\nThis in-memory app is intentionally minimal. Here's how you'd evolve it toward production:\n\nConcern | Tool\n---|---\nPersistent storage | SQLAlchemy + PostgreSQL or SQLite\nMigrations | Alembic\nAuth | JWT via `python-jose` + `passlib`\nDependency Injection | FastAPI's built-in `Depends()` system\nEnvironment config | `pydantic-settings`\nContainerization | Docker + `uvicorn` in production mode\nTesting |  `pytest` + `httpx` (async test client)\nAsync DB |  `asyncpg` + SQLAlchemy async sessions\n\n##  Java/Kotlin Developer Take\n\nHaving spent years with Spring Boot, here's my honest comparison:\n\n**FastAPI wins on:**\n\n  * Speed of prototyping — no boilerplate ceremony\n  * Auto-generated docs that are actually useful\n  * Cleaner validation without annotation overload\n\n\n\n**Spring Boot still wins on:**\n\n  * Enterprise ecosystem maturity\n  * Structured DI with complex bean graphs\n  * Mature monitoring (Actuator, Micrometer)\n\n\n\nFastAPI fills a real gap: it's the right tool when you want Python's data ecosystem (ML, data pipelines) behind a production-quality HTTP API. The two aren't in competition — they're tools for different contexts.\n\n##  Source Code\n\nEverything in this post is available here:\n\n**github.com/MihirMohapatra/fast-api-example**\n\nDrop a ⭐ if it helped you get started.\n\n_Building toward senior remote roles at US/EU companies. Follow along as I explore FastAPI, Rust, Go, and AI infrastructure — writing from the perspective of a practising backend engineer, not a tutorial blog._",
  "title": "FastAPI Crash Course: Build a CRUD REST API in ~40 Lines of Python"
}