Codeskill

Learn to code, step by step

Modern API design Flask/FastAPI

Modern API design with Flask as the main example. FastAPI appears briefly where async and automatic OpenAPI docs matter. The focus is practical: clear URLs, consistent JSON, sensible status codes, and error bodies a front-end developer can actually use.

Resources and HTTP verbs

Think in resources (nouns), not actions (verbs in the URL). Use HTTP methods for the action: GET to read, POST to create, PUT/PATCH to update, DELETE to remove. Plural collection names are a common convention.

from flask import Flask, jsonify, request, abort

app = Flask(__name__)
TASKS: dict[int, dict] = {}
_next_id = 1

@app.get("/api/tasks")
def list_tasks():
    return jsonify(list(TASKS.values()))

@app.post("/api/tasks")
def create_task():
    global _next_id
    data = request.get_json(silent=True) or {}
    title = (data.get("title") or "").strip()
    if not title:
        abort(400, description="title is required")
    task = {"id": _next_id, "title": title, "done": False}
    TASKS[_next_id] = task
    _next_id += 1
    return jsonify(task), 201

@app.patch("/api/tasks/<int:task_id>")
def update_task(task_id: int):
    task = TASKS.get(task_id)
    if not task:
        abort(404)
    data = request.get_json(silent=True) or {}
    if "done" in data:
        task["done"] = bool(data["done"])
    return jsonify(task)

In-memory storage keeps the example short. Real services swap in files, a database, or a repository module – the HTTP layer stays the same.

Consistent JSON shapes

Pick conventions and stick to them. Snake_case keys match Python and read well in JSON. Return the created resource on POST, not a bare success flag. Errors should use a predictable structure:

from flask import Flask, jsonify

app = Flask(__name__)

@app.errorhandler(400)
@app.errorhandler(404)
def api_error(error):
    return jsonify({"error": error.description or "Request failed"}), error.code

Application factory and blueprints

Split routes by domain. The factory pattern from the intermediate Flask tutorial scales to APIs cleanly.

from flask import Blueprint, Flask

tasks_bp = Blueprint("tasks", __name__, url_prefix="/api/tasks")

@tasks_bp.get("")
def list_tasks():
    ...

Def create_app() -> Flask:
    app = Flask(__name__)
    app.register_blueprint(tasks_bp)
    return app

Validation and serialisation

Validate input at the edge. Dataclasses or small Pydantic models (if you already depend on Pydantic) keep field lists explicit. Do not dump arbitrary request JSON straight into storage.

from dataclasses import asdict, dataclass

@dataclass
class TaskCreate:
    title: str

    @classmethod
    def from_dict(cls, data: dict) -> "TaskCreate":
        title = (data.get("title") or "").strip()
        if not title:
            raise ValueError("title is required")
        return cls(title=title)

def task_to_json(task: TaskCreate, task_id: int, done: bool) -> dict:
    return {"id": task_id, **asdict(task), "done": done}

Pagination and filtering

Large collections need limits. Accept limit and offset (or cursor tokens) as query parameters. Document defaults – for example max 100 items per page.

@app.get("/api/tasks")
def list_tasks():
    limit = min(int(request.args.get("limit", 20)), 100)
    offset = int(request.args.get("offset", 0))
    items = list(TASKS.values())[offset : offset + limit]
    return jsonify({"items": items, "limit": limit, "offset": offset})

FastAPI when async pays off

FastAPI generates OpenAPI docs and plays well with async endpoints. Use it when you are already async end to end – async database drivers, many concurrent upstream calls – not as a default replacement for a straightforward Flask service.

# FastAPI sketch - same resource idea, async handler
# from fastapi import FastAPI, HTTPException
# app = FastAPI()
#
# @app.get("/api/tasks")
# async def list_tasks():
#     return list(TASKS.values())

Version your API in the path (/api/v1/tasks) when external clients depend on stability. For internal front ends you control, you can move faster – but still avoid breaking changes without warning.

PreviousData cleaning pipelines