Project: REST API

Introduction

This project exposes a JSON API for posts under /api/v1/, protected write operations with JWT, and CORS for a separate frontend. You practice unified error responses, versioning, and tools like curl or Postman—patterns used by SPAs and mobile clients.

Prerequisites

Project Goals

  • GET /api/v1/posts — list (public)
  • GET /api/v1/posts/<id> — detail (public)
  • POST /api/v1/posts — create (JWT required)
  • PUT /api/v1/posts/<id> — update (JWT required)
  • DELETE /api/v1/posts/<id> — delete (JWT required)
  • Consistent JSON envelope: {code, message, data}

Step 1: API Blueprint

app/api/routes.py:

python
from flask import Blueprint, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from app.extensions import db
from app.models import Post, User
 
api_v1_bp = Blueprint("api_v1", __name__, url_prefix="/api/v1")
 
def ok(data=None, message="success", code=0):
    return jsonify({"code": code, "message": message, "data": data})
 
def err(message, http_status=400, code=1):
    return jsonify({"code": code, "message": message, "data": None}), http_status

Register in create_app and configure Flask-CORS for /api/*.

Step 2: Serialize Post

python
def post_to_dict(post):
    return {
        "id": post.id,
        "title": post.title,
        "body": post.body,
        "created_at": post.created_at.isoformat() + "Z",
    }

Keep serializers in app/api/serializers.py as the API grows.

Step 3: List and Detail

Step 4: Auth Endpoints

python
@api_v1_bp.route("/login", methods=["POST"])
def login():
    data = request.get_json(silent=True) or {}
    user = User.query.filter_by(username=data.get("username", "")).first()
    if user is None or not user.check_password(data.get("password", "")):
        return err("invalid credentials", 401, code=401)
    from flask_jwt_extended import create_access_token
    token = create_access_token(identity=str(user.id))
    return ok({"access_token": token, "token_type": "Bearer"})

Protect mutations:

Add author_id on Post linked to User if you need per-author ownership checks.

Step 5: Update and Delete

Step 6: Test With curl

Step 7: pytest Smoke Tests

tests/test_api.py:

python
def test_list_posts(client):
    r = client.get("/api/v1/posts")
    assert r.status_code == 200
    assert r.get_json()["code"] == 0
 
def test_create_requires_auth(client):
    r = client.post("/api/v1/posts", json={"title": "x", "body": "y"})
    assert r.status_code == 401

Step 8: Frontend Handoff

Document base URL http://127.0.0.1:5000/api/v1 for React/Vue devs. Enable CORS for http://localhost:5173—see CORS chapter.

Optional OpenAPI with flask-smorest—compare SpringDoc on Spring Boot track.

Verification Checklist

  • Public GET works without token
  • POST without token returns 401
  • JSON envelope consistent on success and error
  • CORS preflight succeeds from dev frontend origin
  • pytest passes for list and auth-required create

FAQ

204 response body?

Empty body is correct for DELETE success.

Pagination defaults?

Document page and per_page in a README for API consumers.

Flask-Login instead of JWT?

Possible for same-origin apps—JWT fits SPA chapter goals.

Rate limiting?

Add Flask-Limiter on /loginsecurity chapter.

Version v2?

Register second blueprint url_prefix="/api/v2" without breaking v1.

Postman collection?

Export requests from Postman for teammates—optional deliverable.