REST API Design
REST (Representational State Transfer) is an architectural style for web APIs. RESTful APIs use HTTP methods (GET, POST, PUT, DELETE) and status codes meaningfully.
| Method | Action | Example | Status |
|---|---|---|---|
| GET | Read | GET /users/1 | 200 OK |
| POST | Create | POST /users | 201 Created |
| PUT | Replace | PUT /users/1 | 200 OK |
| PATCH | Update | PATCH /users/1 | 200 OK |
| DELETE | Delete | DELETE /users/1 | 204 No Content |
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
# In-memory "database" for this example
users = {
1: {"id": 1, "name": "Alice", "email": "alice@example.com"},
2: {"id": 2, "name": "Bob", "email": "bob@example.com"},
}
next_id = 3
# GET /users โ list all
@app.route("/users", methods=["GET"])
def get_users():
return jsonify({"users": list(users.values())})
# GET /users/<id> โ get one
@app.route("/users/<int:user_id>", methods=["GET"])
def get_user(user_id):
user = users.get(user_id)
if user is None:
abort(404)
return jsonify(user)
# POST /users โ create
@app.route("/users", methods=["POST"])
def create_user():
global next_id
data = request.get_json()
if not data or "name" not in data or "email" not in data:
abort(400) # Bad Request
user = {"id": next_id, "name": data["name"], "email": data["email"]}
users[next_id] = user
next_id += 1
return jsonify(user), 201
# PUT /users/<id> โ replace
@app.route("/users/<int:user_id>", methods=["PUT"])
def update_user(user_id):
if user_id not in users:
abort(404)
data = request.get_json()
users[user_id].update(data)
return jsonify(users[user_id])
# DELETE /users/<id>
@app.route("/users/<int:user_id>", methods=["DELETE"])
def delete_user(user_id):
if user_id not in users:
abort(404)
del users[user_id]
return "", 204Input Validation and Error Handling
from functools import wraps
def validate_json(*required_fields):
"""Decorator to validate required JSON fields."""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
data = request.get_json()
if not data:
return jsonify({"error": "Request body must be JSON"}), 400
missing = [f for f in required_fields if f not in data]
if missing:
return jsonify({"error": f"Missing fields: {missing}"}), 400
return func(*args, **kwargs)
return wrapper
return decorator
@app.route("/users", methods=["POST"])
@validate_json("name", "email")
def create_user():
data = request.get_json()
# We know name and email exist here
...
# Consistent error responses
@app.errorhandler(400)
def bad_request(e):
return jsonify({"error": "Bad request", "message": str(e)}), 400
@app.errorhandler(404)
def not_found(e):
return jsonify({"error": "Not found"}), 404
@app.errorhandler(500)
def server_error(e):
return jsonify({"error": "Internal server error"}), 500Authentication and Blueprints
# Simple API key auth
from functools import wraps
def require_api_key(func):
@wraps(func)
def wrapper(*args, **kwargs):
api_key = request.headers.get("X-API-Key")
if api_key != app.config.get("API_KEY"):
return jsonify({"error": "Unauthorized"}), 401
return func(*args, **kwargs)
return wrapper
@app.route("/protected")
@require_api_key
def protected():
return jsonify({"data": "secret"})
# Blueprints โ organize routes into modules
from flask import Blueprint
users_bp = Blueprint("users", __name__, url_prefix="/api/v1")
@users_bp.route("/users")
def get_users():
return jsonify([])
# Register in app factory
app.register_blueprint(users_bp)Key Takeaways
- Use HTTP methods semantically: GET=read, POST=create, PUT=replace, DELETE=delete
- Return appropriate status codes: 200 OK, 201 Created, 400 Bad Request, 404 Not Found
- Validate input before processing
- Blueprints organize large APIs: group related routes in separate files
- Consistent error format: always return {"error": "..."} for errors
Practice Exercises
- Build a complete CRUD API for a "tasks" resource: create, list, get one, update, delete.
- Add pagination to your list endpoint:
/tasks?page=1&per_page=10 - Add API key authentication to your API using a decorator.
- Organize a medium-sized API using Flask Blueprints โ separate users, tasks, and auth into different files.