Build a REST API with Python FastAPI - Complete Tutorial 2026

Published Feb 20, 2026 · Tutorial · 5 min read

Step-by-step guide to building production REST APIs with FastAPI. Covers routing, validation, authentication, database integration, deployment with systemd, and rate limiting.

FastAPI has become the most popular Python framework for building REST APIs. Its combination of speed, automatic documentation, and Python type hints makes it the best choice for production APIs in 2026. This guide walks you through building a complete REST API from scratch — from your first endpoint to production deployment.

Why FastAPI in 2026?

FastAPI is used by companies like Microsoft, Netflix, Uber, and thousands of startups. Here's why:

Installation & Setup

Start by creating a virtual environment and installing FastAPI:

# Create project directory
mkdir my-api && cd my-api

# Create virtual environment
python3 -m venv venv
source venv/bin/activate

# Install FastAPI and uvicorn (ASGI server)
pip install fastapi uvicorn[standard]

# Optional but recommended
pip install pydantic[email] python-multipart aiofiles

Create your main application file:

# main.py
from fastapi import FastAPI

app = FastAPI(
    title="My API",
    description="A production REST API built with FastAPI",
    version="1.0.0"
)

@app.get("/")
async def root():
    return {"message": "API is running", "version": "1.0.0"}

Run the development server:

uvicorn main:app --reload --port 8000

Visit http://localhost:8000/docs to see the auto-generated Swagger documentation.

Your First API Endpoint

FastAPI makes it intuitive to define endpoints with Python functions:

from fastapi import FastAPI, Query
from typing import Optional

app = FastAPI()

@app.get("/api/greet")
async def greet(
    name: str = Query(..., min_length=1, max_length=50, description="Your name"),
    language: Optional[str] = Query("en", description="Language code")
):
    greetings = {
        "en": f"Hello, {name}!",
        "es": f"Hola, {name}!",
        "fr": f"Bonjour, {name}!",
        "de": f"Hallo, {name}!",
    }
    return {
        "greeting": greetings.get(language, f"Hello, {name}!"),
        "language": language
    }

FastAPI automatically validates query parameters, generates docs, and returns proper error responses for invalid input.

Request Validation with Pydantic

For POST/PUT endpoints, use Pydantic models for request body validation:

from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List

class CreateUser(BaseModel):
    name: str = Field(..., min_length=2, max_length=100)
    email: EmailStr
    age: Optional[int] = Field(None, ge=0, le=150)
    tags: List[str] = Field(default_factory=list, max_length=10)

    class Config:
        json_schema_extra = {
            "example": {
                "name": "Jane Developer",
                "email": "jane@example.com",
                "age": 28,
                "tags": ["python", "fastapi"]
            }
        }

@app.post("/api/users", status_code=201)
async def create_user(user: CreateUser):
    # Pydantic has already validated all fields
    # Invalid email? 422 error with details
    # Name too short? 422 error with details
    return {"id": 1, **user.model_dump()}

Pydantic handles type coercion, validation, and serialization. If a request doesn't match the schema, FastAPI returns a detailed 422 error automatically.

Building CRUD Endpoints

Here's a complete CRUD (Create, Read, Update, Delete) API for a task manager:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime

app = FastAPI()

# In-memory storage (replace with database in production)
tasks = {}
next_id = 1

class TaskCreate(BaseModel):
    title: str = Field(..., min_length=1, max_length=200)
    description: Optional[str] = None
    priority: int = Field(default=1, ge=1, le=5)

class TaskUpdate(BaseModel):
    title: Optional[str] = Field(None, min_length=1, max_length=200)
    description: Optional[str] = None
    priority: Optional[int] = Field(None, ge=1, le=5)
    completed: Optional[bool] = None

@app.post("/api/tasks", status_code=201)
async def create_task(task: TaskCreate):
    global next_id
    task_id = next_id
    next_id += 1
    tasks[task_id] = {
        "id": task_id,
        **task.model_dump(),
        "completed": False,
        "created_at": datetime.utcnow().isoformat()
    }
    return tasks[task_id]

@app.get("/api/tasks")
async def list_tasks(completed: Optional[bool] = None, limit: int = 50):
    result = list(tasks.values())
    if completed is not None:
        result = [t for t in result if t["completed"] == completed]
    return {"tasks": result[:limit], "total": len(result)}

@app.get("/api/tasks/{task_id}")
async def get_task(task_id: int):
    if task_id not in tasks:
        raise HTTPException(404, f"Task {task_id} not found")
    return tasks[task_id]

@app.patch("/api/tasks/{task_id}")
async def update_task(task_id: int, updates: TaskUpdate):
    if task_id not in tasks:
        raise HTTPException(404, f"Task {task_id} not found")
    for key, value in updates.model_dump(exclude_unset=True).items():
        tasks[task_id][key] = value
    return tasks[task_id]

@app.delete("/api/tasks/{task_id}", status_code=204)
async def delete_task(task_id: int):
    if task_id not in tasks:
        raise HTTPException(404, f"Task {task_id} not found")
    del tasks[task_id]

Adding API Key Authentication

Protect your endpoints with API key authentication using a FastAPI dependency:

from fastapi import Depends, Security
from fastapi.security import APIKeyHeader
import secrets

api_key_header = APIKeyHeader(name="X-API-Key")

# In production, store keys in a database
VALID_KEYS = {
    "sk_test_abc123": {"email": "dev@example.com", "tier": "free"},
}

async def get_api_key(api_key: str = Security(api_key_header)):
    if api_key not in VALID_KEYS:
        raise HTTPException(401, "Invalid API key")
    return VALID_KEYS[api_key]

@app.get("/api/protected")
async def protected_endpoint(user=Depends(get_api_key)):
    return {"message": "Authenticated!", "user": user["email"]}

This pattern is used by production API services. For example, DevProToolkit API Hub uses this exact approach to manage 13 APIs and 104+ endpoints with a single unified key system.

Database Integration

For production, replace in-memory storage with SQLite (simple) or PostgreSQL (scalable):

import aiosqlite

DB_PATH = "app.db"

async def init_db():
    async with aiosqlite.connect(DB_PATH) as db:
        await db.execute("""
            CREATE TABLE IF NOT EXISTS tasks (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT NOT NULL,
                description TEXT,
                priority INTEGER DEFAULT 1,
                completed BOOLEAN DEFAULT 0,
                created_at TEXT DEFAULT (datetime('now'))
            )
        """)
        await db.commit()

@app.on_event("startup")
async def startup():
    await init_db()

@app.post("/api/tasks", status_code=201)
async def create_task(task: TaskCreate):
    async with aiosqlite.connect(DB_PATH) as db:
        cursor = await db.execute(
            "INSERT INTO tasks (title, description, priority) VALUES (?, ?, ?)",
            (task.title, task.description, task.priority)
        )
        await db.commit()
        task_id = cursor.lastrowid

    return {"id": task_id, **task.model_dump()}

Rate Limiting

Add rate limiting with a simple in-memory counter:

from collections import defaultdict
import time

class RateLimiter:
    def __init__(self, max_requests: int = 100, window: int = 60):
        self.max_requests = max_requests
        self.window = window
        self.requests = defaultdict(list)

    def is_allowed(self, key: str) -> bool:
        now = time.time()
        # Clean old entries
        self.requests[key] = [
            t for t in self.requests[key]
            if now - t < self.window
        ]
        if len(self.requests[key]) >= self.max_requests:
            return False
        self.requests[key].append(now)
        return True

limiter = RateLimiter(max_requests=10, window=60)

@app.middleware("http")
async def rate_limit_middleware(request, call_next):
    client_ip = request.client.host
    if not limiter.is_allowed(client_ip):
        return JSONResponse(
            status_code=429,
            content={"error": "Rate limit exceeded. Try again later."}
        )
    return await call_next(request)

Deploying with systemd

For production deployment on Linux, use systemd to manage your FastAPI service:

# /etc/systemd/system/my-api.service
[Unit]
Description=My FastAPI Service
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/my-api
ExecStart=/home/ubuntu/my-api/venv/bin/uvicorn main:app \
    --host 0.0.0.0 --port 8000 --workers 2
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target
# Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable my-api
sudo systemctl start my-api

# Check status
sudo systemctl status my-api

# View logs
journalctl -u my-api -f

Best Practices for Production APIs

  1. Version your API: Use URL prefixes like /api/v1/ for breaking changes
  2. Return consistent error formats: Always return {"error": "message", "detail": "..."}
  3. Add health checks: Create a /health endpoint that returns service status
  4. Use async everywhere: Use async def for I/O-bound endpoints
  5. Log structured data: Use JSON logging for better observability
  6. Set CORS properly: Don't use allow_origins=["*"] in production
  7. Use environment variables: Never hardcode secrets, database URLs, or API keys
  8. Write tests: Use httpx with FastAPI's TestClient for testing
  9. Monitor performance: Track response times, error rates, and request volumes
  10. Document thoroughly: FastAPI auto-generates docs, but add descriptions to all parameters

Need Ready-Made APIs?

Instead of building from scratch, use DevProToolkit's 13 production APIs: QR codes, PDFs, TTS, email validation, crypto prices, and more. All built with FastAPI and the patterns shown in this guide.

Get Free API Key   Try APIs

Frequently Asked Questions

Is FastAPI good for production?

Yes. FastAPI is used in production by companies like Microsoft, Netflix, and Uber. It runs on uvicorn (ASGI) and supports async/await for high concurrency. With multiple workers and proper deployment, it handles thousands of requests per second.

How does FastAPI compare to Django REST Framework?

FastAPI is faster, has better type safety, and generates docs automatically. Django REST Framework is better if you need Django's ORM, admin panel, and ecosystem. For pure API services, FastAPI is the modern choice.

How do I deploy FastAPI in production?

Use uvicorn behind systemd (Linux), Docker, or a cloud platform. For high traffic, use multiple workers with --workers 4 or put it behind nginx as a reverse proxy. Always use HTTPS in production.

Quick Start

Get your free API key and start making requests in minutes.

curl "http://147.224.212.116/api/..." \
  -H "X-API-Key: YOUR_API_KEY"

Start Using This API Today

Get a free API key with 100 requests/day. No credit card required.

Get Free API Key