{"post":{"slug":"how-to-build-an-api-in-python-2026","title":"How to build an API in Python in 2026: FastAPI, Pydantic, and deployment","excerpt":"FastAPI remains the go-to for Python APIs in 2026, with Pydantic v2 validation and async-first design. This guide covers structure, auth, and real deployment steps.","category":"code","date":"March 28, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">F</span>astAPI has been the dominant Python API framework since 2022, and in 2026 it's still the right default — Pydantic v2 made it significantly faster, and the async-first design fits modern infrastructure. This guide builds a production-grade API from scratch, including project layout, async database access, auth, and deployment.\n\n## What changed in 2026\n\n- **Pydantic v2** (shipped 2023, now fully dominant) uses a Rust core and is ~5–50× faster than v1. All FastAPI versions since 0.100 use it by default.\n- **SQLAlchemy 2.x async** is stable and widely used — `async with AsyncSession` replaces the old sync ORM pattern.\n- **Python 3.12/3.13** are the production standard; `asyncio` performance improvements are meaningful at scale.\n- **Litestar** (formerly Starlette-based Piccolo API) is the emerging alternative with stricter typing; check it for greenfield projects where FastAPI feels too implicit.\n\n## Project structure\n\n```\napp/\n  main.py           # FastAPI() instance + router includes\n  routers/\n    users.py\n    items.py\n  schemas/\n    user.py         # Pydantic request/response models\n  models/\n    user.py         # SQLAlchemy ORM models\n  services/\n    user_service.py\n  db/\n    session.py      # AsyncSessionLocal + get_db dependency\n  core/\n    config.py       # Settings via pydantic-settings\n    security.py     # JWT helpers\n```\n\n## Bootstrap\n\n```python\n# app/main.py\nfrom fastapi import FastAPI\nfrom app.routers import users, items\n\napp = FastAPI(title=\"My API\", version=\"1.0.0\")\n\napp.include_router(users.router, prefix=\"/api/v1/users\", tags=[\"users\"])\napp.include_router(items.router, prefix=\"/api/v1/items\", tags=[\"items\"])\n\n@app.get(\"/healthz\")\nasync def health():\n    return {\"status\": \"ok\"}\n```\n\n## Schemas and a route\n\n```python\n# app/schemas/user.py\nfrom pydantic import BaseModel, EmailStr\n\nclass UserCreate(BaseModel):\n    email: EmailStr\n    name: str\n\nclass UserResponse(BaseModel):\n    id: int\n    email: str\n    name: str\n\n    model_config = {\"from_attributes\": True}  # replaces orm_mode in v2\n```\n\n```python\n# app/routers/users.py\nfrom fastapi import APIRouter, Depends, HTTPException, status\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom app.db.session import get_db\nfrom app.schemas.user import UserCreate, UserResponse\nfrom app.services.user_service import create_user, get_user\n\nrouter = APIRouter()\n\n@router.post(\"/\", response_model=UserResponse, status_code=201)\nasync def create(body: UserCreate, db: AsyncSession = Depends(get_db)):\n    return await create_user(db, body)\n\n@router.get(\"/{user_id}\", response_model=UserResponse)\nasync def read(user_id: int, db: AsyncSession = Depends(get_db)):\n    user = await get_user(db, user_id)\n    if not user:\n        raise HTTPException(status_code=404, detail=\"User not found\")\n    return user\n```\n\n## Async database session dependency\n\n```python\n# app/db/session.py\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker\nfrom app.core.config import settings\n\nengine = create_async_engine(settings.DATABASE_URL, echo=False)\nAsyncSessionLocal = async_sessionmaker(engine, expire_on_commit=False)\n\nasync def get_db():\n    async with AsyncSessionLocal() as session:\n        yield session\n```\n\nUse `asyncpg` as the driver: `DATABASE_URL = \"postgresql+asyncpg://user:pass@host/db\"`.\n\n## HTTP status code conventions\n\n| Situation | Status |\n| --- | --- |\n| Resource created | 201 Created |\n| Validation error (Pydantic) | 422 Unprocessable Entity (auto) |\n| Auth missing | 401 Unauthorized |\n| Auth present but forbidden | 403 Forbidden |\n| Not found | 404 Not Found |\n| Duplicate / conflict | 409 Conflict |\n\nFastAPI returns 422 automatically for Pydantic validation failures. Map your own domain errors to 400 or 409 explicitly.\n\n## How to pick your Python API framework\n\n| Framework | Best for |\n| --- | --- |\n| FastAPI | Async APIs, ML endpoints, type-safe new projects |\n| Django REST Framework | Teams already on Django; admin-heavy apps |\n| Flask + marshmallow | Small scripts, simple internal services |\n| Litestar | Stricter typing, built-in DTO layer |\n\n## Common mistakes\n\n**Synchronous DB calls in async handlers.** Blocking calls block the event loop. Use `asyncpg` or `aiosqlite`; never call a sync ORM method without `run_in_executor`.\n\n**Returning ORM models directly.** SQLAlchemy objects are lazy-loaded; serializing them outside a session raises `DetachedInstanceError`. Always return a Pydantic response model.\n\n**Ignoring `model_config = {\"from_attributes\": True}`** on response schemas. Without it, `UserResponse.model_validate(orm_obj)` fails.\n\n**Putting config in module-level globals.** Use `pydantic-settings` with a `Settings` class and inject via `Depends(get_settings)`.\n\n**No background task queue.** Long-running work (email, PDF export) must be offloaded to Celery or ARQ — do not block request handlers.\n\n## What to skip\n\n- **Flask for async workloads** — Flask's WSGI model doesn't fit native async. Pick FastAPI.\n- **Mixing sync and async ORM** in the same codebase — pick one and stay consistent.\n- **Manual OpenAPI YAML** — FastAPI generates it from your code; only customize what it generates.\n\n## FAQ\n\n**Should I use SQLAlchemy or Tortoise ORM?**\nSQLAlchemy 2.x async is more widely understood and has better migration tooling (Alembic). Tortoise is simpler but has a smaller community.\n\n**How do I run database migrations?**\nAlembic with `alembic revision --autogenerate` and `alembic upgrade head`. See [How to write a database migration in 2026](/blog/how-to-write-a-database-migration-2026).\n\n**Is FastAPI production-ready?**\nYes. Uvicorn + Gunicorn (or a container with `CMD [\"uvicorn\", \"app.main:app\"]`) handles production traffic at scale.\n\n**How do I add JWT authentication?**\nUse `python-jose` or `PyJWT` to sign and verify tokens. Create a `get_current_user` dependency that reads `Authorization: Bearer <token>` and inject it with `Depends`.\n\n## Where to go next\n\nSee [How to deploy a FastAPI app in 2026](/blog/how-to-deploy-a-fastapi-app-2026), [How to write a database migration in 2026](/blog/how-to-write-a-database-migration-2026), and [How to set up Postgres locally in 2026](/blog/how-to-set-up-postgres-locally-2026).","hue":319,"metaTitle":"How to Build an API in Python 2026: FastAPI + Pydantic Guide","metaDescription":"Complete 2026 guide to building a Python API with FastAPI and Pydantic v2. Covers project structure, async DB, auth, error handling, and deployment.","keywords":["how to build an api in python 2026","fastapi tutorial 2026","python rest api 2026","pydantic v2 api","fastapi sqlalchemy async","python api project structure","fastapi authentication","fastapi deployment 2026","python api best practices","fastapi vs django rest framework"],"tldr":["**FastAPI + Pydantic v2 is the standard stack** — async, type-safe, and auto-generates OpenAPI docs from your models with zero extra work.","**Use dependency injection for shared resources** — database sessions, auth context, and config belong in `Depends()`, not globals.","**Validate all input with Pydantic models** — never accept raw dicts from request bodies; let Pydantic coerce and validate at the boundary.","**Run with Uvicorn behind a process manager** — Gunicorn with Uvicorn workers for multi-core, or plain Uvicorn in containers behind a load balancer.","**Skip:** returning SQLAlchemy ORM objects directly. Define separate response schemas; otherwise internal fields leak and serialization gets messy."],"readTime":"4 min read","wordCount":797},"related":[{"slug":"how-to-learn-fastapi-2026","title":"How to learn FastAPI in 2026: async Python APIs with Pydantic and OpenAPI","excerpt":"FastAPI is the fastest-growing Python web framework and the default choice for async APIs in 2026. This guide gives you a direct, practical path to production-ready FastAPI.","category":"code","date":"April 16, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":313},{"slug":"how-to-learn-django-2026","title":"How to learn Django in 2026: batteries-included Python web development","excerpt":"Django 5.1 ships with async ORM support and a cleaner admin. This 2026 guide takes you from a blank project to a production-grade Django application without wasted steps.","category":"code","date":"March 31, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":312},{"slug":"fastapi-vs-flask-2026","title":"FastAPI vs Flask in 2026: which Python web framework wins?","excerpt":"Flask is still the simplest Python web framework, but FastAPI now dominates new Python API projects. Here is when each one still makes sense.","category":"code","date":"March 27, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":314}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-build-a-website-2026","title":"How to build a website in 2026: from zero to live in a weekend","excerpt":"Building a website in 2026 is faster than ever, but the options are more confusing. This guide picks the right path for your goal and skill level.","category":"code","date":"March 28, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":306},"older":{"slug":"how-to-build-wealth-in-your-20s-2026","title":"How to build wealth in your 20s in 2026: the compounding advantage","excerpt":"Your 20s are the highest-leverage decade in personal finance — not because you earn the most, but because time compounds longer. Here is the playbook.","category":"finance","date":"March 28, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":292}}