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
- REST: HTTP plus resource-oriented URLs
- Flask: lightweight, flexible API servers
- Django REST Framework: batteries-included API toolkit
- Auth: JWT, API keys
- Errors: consistent JSON bodies and correct status codes