Python REST APIs | Build API Servers with Flask and Django

Python REST APIs | Build API Servers with Flask and Django

이 글의 핵심

Build and secure REST APIs in Python using Flask and Django REST Framework, with clear patterns for CRUD, auth, and errors.

Introduction

“The backbone of backend work”

REST APIs are the standard way to connect frontends and backends.


1. REST API fundamentals

RESTful design

Resource-oriented routes:
GET    /api/users       - list users
GET    /api/users/1     - get user 1
POST   /api/users       - create user
PUT    /api/users/1     - replace user 1
DELETE /api/users/1     - delete user 1

2. REST API with Flask

CRUD API

from flask import Flask, jsonify, request

app = Flask(__name__)

users = [
    {'id': 1, 'name': 'Alice', 'email': '[email protected]'},
    {'id': 2, 'name': 'Bob', 'email': '[email protected]'}
]

# List (GET)
@app.route('/api/users', methods=['GET'])
def get_users():
    return jsonify({'users': users, 'count': len(users)})

# Single resource (GET)
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)
    if user:
        return jsonify(user)
    return jsonify({'error': 'User not found'}), 404

# Create (POST)
@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    
    if not data or 'name' not in data or 'email' not in data:
        return jsonify({'error': 'Invalid data'}), 400
    
    new_user = {
        'id': max(u['id'] for u in users) + 1 if users else 1,
        'name': data['name'],
        'email': data['email']
    }
    users.append(new_user)
    return jsonify(new_user), 201

# Update (PUT)
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    user = next((u for u in users if u['id'] == user_id), None)
    if not user:
        return jsonify({'error': 'User not found'}), 404
    
    data = request.get_json()
    user.update(data)
    return jsonify(user)

# Delete (DELETE)
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    global users
    users = [u for u in users if u['id'] != user_id]
    return '', 204

if __name__ == '__main__':
    app.run(debug=True)

3. Django REST Framework

Installation

pip install djangorestframework

Serializers

# blog/serializers.py
from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'title', 'content', 'author', 'created_at']
        read_only_fields = ['id', 'created_at']

ViewSet

# blog/views.py
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

URL routing

# blog/urls.py
from rest_framework.routers import DefaultRouter
from .views import PostViewSet

router = DefaultRouter()
router.register(r'posts', PostViewSet)

urlpatterns = router.urls

4. Authentication and permissions

JWT authentication

pip install djangorestframework-simplejwt
# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

# urls.py
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    path('api/token/', TokenObtainPairView.as_view()),
    path('api/token/refresh/', TokenRefreshView.as_view()),
]

5. Error handling

Custom JSON errors

from flask import jsonify

@app.errorhandler(404)
def not_found(error):
    return jsonify({
        'error': 'Not Found',
        'message': 'The requested resource could not be found'
    }), 404

@app.errorhandler(500)
def internal_error(error):
    return jsonify({
        'error': 'Internal Server Error',
        'message': 'An unexpected server error occurred'
    }), 500

6. Practical example

Full Flask API sketch

from flask import Flask, jsonify, request
from functools import wraps

app = Flask(__name__)

# Simple API key check
API_KEY = "secret-key-123"

def require_api_key(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        api_key = request.headers.get('X-API-Key')
        if api_key != API_KEY:
            return jsonify({'error': 'Unauthorized'}), 401
        return f(*args, **kwargs)
    return decorated

# In-memory store instead of a database
posts = []

@app.route('/api/posts', methods=['GET'])
def get_posts():
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 10, type=int)
    
    start = (page - 1) * per_page
    end = start + per_page
    
    return jsonify({
        'posts': posts[start:end],
        'page': page,
        'total': len(posts)
    })

@app.route('/api/posts', methods=['POST'])
@require_api_key
def create_post():
    data = request.get_json()
    
    required_fields = ['title', 'content']
    if not all(field in data for field in required_fields):
        return jsonify({'error': 'Missing required fields'}), 400
    
    post = {
        'id': len(posts) + 1,
        'title': data['title'],
        'content': data['content'],
        'author': data.get('author', 'Anonymous')
    }
    posts.append(post)
    
    return jsonify(post), 201

if __name__ == '__main__':
    app.run(debug=True)

Practical tips

API design best practices

# Use nouns, not verbs
GET /api/users  # OK
GET /api/getUsers  # Avoid

# Prefer plural collection names
GET /api/users  # OK
GET /api/user  # Avoid

# Hierarchical resources
GET /api/users/1/posts  # posts belonging to user 1

# Meaningful HTTP status codes
200 OK           # success
201 Created      # created
400 Bad Request  # bad input
401 Unauthorized # auth required
404 Not Found    # missing resource
500 Server Error # server failure

Summary

Key takeaways

  1. REST: HTTP plus resource-oriented URLs
  2. Flask: lightweight, flexible API servers
  3. Django REST Framework: batteries-included API toolkit
  4. Auth: JWT, API keys
  5. Errors: consistent JSON bodies and correct status codes

Next steps