Building REST APIs
Learning Objectives
- By the end of this lesson, you will be able to:
- - Understand REST API principles
- - Install and use Flask-RESTful
- - Design RESTful APIs
- - Create API endpoints
- - Handle HTTP methods (GET, POST, PUT, DELETE)
- - Implement request parsing and validation
- - Add authentication and authorization
- - Handle errors and exceptions
- - Use API versioning
- - Document APIs
- - Test REST APIs
- - Apply API design best practices
- - Build production-ready REST APIs
- - Debug API issues
Lesson 20.2: Building REST APIs
Learning Objectives
By the end of this lesson, you will be able to:
- Understand REST API principles
- Install and use Flask-RESTful
- Design RESTful APIs
- Create API endpoints
- Handle HTTP methods (GET, POST, PUT, DELETE)
- Implement request parsing and validation
- Add authentication and authorization
- Handle errors and exceptions
- Use API versioning
- Document APIs
- Test REST APIs
- Apply API design best practices
- Build production-ready REST APIs
- Debug API issues
Introduction to REST APIs
REST (Representational State Transfer) is an architectural style for designing networked applications. REST APIs use HTTP methods to perform operations on resources.
Key Principles:
- Stateless: Each request contains all information needed
- Resource-based: URLs represent resources
- HTTP methods: GET, POST, PUT, DELETE, PATCH
- JSON: Common data format
- Status codes: Standard HTTP status codes
Flask-RESTful
Installation
# Install Flask-RESTful
pip install flask-restful
# Also install Flask-JWT-Extended for authentication
pip install flask-jwt-extended
# Install Flask-CORS for CORS support
pip install flask-cors
Basic Setup
from flask import Flask
from flask_restful import Api, Resource
from flask_cors import CORS
app = Flask(__name__)
api = Api(app)
CORS(app) # Enable CORS
if __name__ == '__main__':
app.run(debug=True)
Simple Resource
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'message': 'Hello, World!'}
api.add_resource(HelloWorld, '/hello')
if __name__ == '__main__':
app.run(debug=True)
API Design Principles
RESTful URLs
# Good RESTful design
GET /api/v1/users # List all users
GET /api/v1/users/1 # Get user with ID 1
POST /api/v1/users # Create new user
PUT /api/v1/users/1 # Update user with ID 1
DELETE /api/v1/users/1 # Delete user with ID 1
# Bad design (not RESTful)
GET /api/v1/getUsers
POST /api/v1/createUser
POST /api/v1/updateUser
POST /api/v1/deleteUser
HTTP Methods
from flask_restful import Resource
class UserResource(Resource):
def get(self, user_id):
"""Retrieve a user."""
return {'user_id': user_id, 'name': 'John'}
def post(self):
"""Create a new user."""
return {'message': 'User created'}, 201
def put(self, user_id):
"""Update a user."""
return {'user_id': user_id, 'message': 'User updated'}
def delete(self, user_id):
"""Delete a user."""
return {'message': 'User deleted'}, 204
Status Codes
from flask_restful import Resource
class UserResource(Resource):
def get(self, user_id):
user = get_user(user_id)
if not user:
return {'error': 'User not found'}, 404
return user, 200
def post(self):
# Create user
return {'message': 'User created'}, 201
def put(self, user_id):
# Update user
return {'message': 'User updated'}, 200
def delete(self, user_id):
# Delete user
return None, 204 # No content
Request Parsing
Basic Parsing
from flask_restful import Resource, reqparse
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, help='Name is required')
parser.add_argument('email', type=str, required=True)
parser.add_argument('age', type=int, default=0)
class UserResource(Resource):
def post(self):
args = parser.parse_args()
name = args['name']
email = args['email']
age = args['age']
# Create user
return {'message': 'User created', 'name': name}, 201
Advanced Parsing
from flask_restful import Resource, reqparse
from werkzeug.datastructures import FileStorage
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, location='json')
parser.add_argument('email', type=str, required=True, location='json')
parser.add_argument('age', type=int, location='json')
parser.add_argument('active', type=bool, location='json')
parser.add_argument('tags', type=str, action='append', location='json')
parser.add_argument('file', type=FileStorage, location='files')
class UserResource(Resource):
def post(self):
args = parser.parse_args()
# args['name'], args['email'], etc.
return {'message': 'User created'}, 201
Validation
from flask_restful import Resource, reqparse
import re
def validate_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
raise ValueError('Invalid email format')
return email
parser = reqparse.RequestParser()
parser.add_argument('email', type=validate_email, required=True)
parser.add_argument('age', type=int, choices=[18, 19, 20, 21], required=True)
class UserResource(Resource):
def post(self):
args = parser.parse_args()
return {'message': 'User created'}, 201
Resource Classes
Simple Resource
from flask_restful import Resource
class ItemList(Resource):
def get(self):
items = [
{'id': 1, 'name': 'Item 1'},
{'id': 2, 'name': 'Item 2'}
]
return {'items': items}, 200
def post(self):
# Create item
return {'message': 'Item created'}, 201
Resource with ID
from flask_restful import Resource
class Item(Resource):
def get(self, item_id):
item = {'id': item_id, 'name': f'Item {item_id}'}
return item, 200
def put(self, item_id):
# Update item
return {'message': 'Item updated'}, 200
def delete(self, item_id):
# Delete item
return None, 204
Complete CRUD Resource
from flask_restful import Resource, reqparse
# In-memory storage (use database in production)
items = {}
item_id_counter = 1
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True)
parser.add_argument('description', type=str)
class ItemList(Resource):
def get(self):
return {'items': list(items.values())}, 200
def post(self):
global item_id_counter
args = parser.parse_args()
item = {
'id': item_id_counter,
'name': args['name'],
'description': args.get('description', '')
}
items[item_id_counter] = item
item_id_counter += 1
return item, 201
class Item(Resource):
def get(self, item_id):
if item_id not in items:
return {'error': 'Item not found'}, 404
return items[item_id], 200
def put(self, item_id):
if item_id not in items:
return {'error': 'Item not found'}, 404
args = parser.parse_args()
items[item_id]['name'] = args['name']
items[item_id]['description'] = args.get('description', '')
return items[item_id], 200
def delete(self, item_id):
if item_id not in items:
return {'error': 'Item not found'}, 404
del items[item_id]
return None, 204
Authentication and Authorization
JWT Authentication
from flask import Flask
from flask_restful import Api, Resource
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token,
get_jwt_identity, get_jwt
)
from datetime import timedelta
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your-secret-key-change-in-production'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
jwt = JWTManager(app)
api = Api(app)
# User database (use real database in production)
users = {
'admin': {'password': 'admin123', 'role': 'admin'},
'user': {'password': 'user123', 'role': 'user'}
}
class Login(Resource):
def post(self):
from flask_restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('username', required=True)
parser.add_argument('password', required=True)
args = parser.parse_args()
username = args['username']
password = args['password']
if username in users and users[username]['password'] == password:
access_token = create_access_token(
identity=username,
additional_claims={'role': users[username]['role']}
)
return {'access_token': access_token}, 200
else:
return {'error': 'Invalid credentials'}, 401
class ProtectedResource(Resource):
@jwt_required()
def get(self):
current_user = get_jwt_identity()
claims = get_jwt()
return {
'message': f'Hello, {current_user}!',
'role': claims.get('role')
}, 200
api.add_resource(Login, '/login')
api.add_resource(ProtectedResource, '/protected')
Role-Based Authorization
from functools import wraps
from flask_jwt_extended import get_jwt, verify_jwt_in_request
def admin_required(fn):
@wraps(fn)
@jwt_required()
def wrapper(*args, **kwargs):
verify_jwt_in_request()
claims = get_jwt()
if claims.get('role') != 'admin':
return {'error': 'Admin access required'}, 403
return fn(*args, **kwargs)
return wrapper
class AdminResource(Resource):
@admin_required
def get(self):
return {'message': 'Admin access granted'}, 200
API Key Authentication
from functools import wraps
from flask import request
API_KEYS = {
'key123': {'user': 'admin', 'permissions': ['read', 'write']},
'key456': {'user': 'user', 'permissions': ['read']}
}
def api_key_required(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
api_key = request.headers.get('X-API-Key')
if not api_key or api_key not in API_KEYS:
return {'error': 'Invalid API key'}, 401
request.api_key_data = API_KEYS[api_key]
return fn(*args, **kwargs)
return wrapper
class ProtectedResource(Resource):
@api_key_required
def get(self):
key_data = request.api_key_data
return {'user': key_data['user']}, 200
Error Handling
Custom Error Handlers
from flask import Flask
from flask_restful import Api, Resource
from werkzeug.exceptions import NotFound, BadRequest
app = Flask(__name__)
api = Api(app)
@api.errorhandler(NotFound)
def handle_not_found(e):
return {'error': 'Resource not found'}, 404
@api.errorhandler(BadRequest)
def handle_bad_request(e):
return {'error': 'Bad request'}, 400
class Item(Resource):
def get(self, item_id):
if item_id not in items:
raise NotFound('Item not found')
return items[item_id], 200
Exception Handling in Resources
from flask_restful import Resource
from werkzeug.exceptions import BadRequest, NotFound
class Item(Resource):
def get(self, item_id):
try:
item = get_item(item_id)
if not item:
raise NotFound('Item not found')
return item, 200
except ValueError as e:
raise BadRequest(str(e))
except Exception as e:
return {'error': 'Internal server error'}, 500
Validation Errors
from flask_restful import Resource, reqparse
from werkzeug.exceptions import BadRequest
parser = reqparse.RequestParser()
parser.add_argument('email', type=str, required=True)
def validate_email(email):
if '@' not in email:
raise ValueError('Invalid email format')
return email
parser.add_argument('email', type=validate_email, required=True)
class UserResource(Resource):
def post(self):
try:
args = parser.parse_args()
# Create user
return {'message': 'User created'}, 201
except ValueError as e:
raise BadRequest(str(e))
API Versioning
URL-Based Versioning
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api_v1 = Api(app, prefix='/api/v1')
api_v2 = Api(app, prefix='/api/v2')
class UserV1(Resource):
def get(self):
return {'version': 'v1', 'users': []}
class UserV2(Resource):
def get(self):
return {'version': 'v2', 'users': [], 'metadata': {}}
api_v1.add_resource(UserV1, '/users')
api_v2.add_resource(UserV2, '/users')
Header-Based Versioning
from flask import request
from flask_restful import Resource
class VersionedResource(Resource):
def get(self):
version = request.headers.get('API-Version', 'v1')
if version == 'v2':
return {'version': 'v2', 'data': []}
else:
return {'version': 'v1', 'data': []}
Complete Example: Todo API
"""
Complete REST API Example: Todo Application
"""
from flask import Flask
from flask_restful import Api, Resource, reqparse
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token,
get_jwt_identity
)
from flask_cors import CORS
from datetime import timedelta
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your-secret-key-change-in-production'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
jwt = JWTManager(app)
api = Api(app)
CORS(app)
# In-memory storage (use database in production)
todos = {}
todo_id_counter = 1
users = {
'admin': {'password': 'admin123'}
}
# Request parsers
todo_parser = reqparse.RequestParser()
todo_parser.add_argument('title', type=str, required=True, help='Title is required')
todo_parser.add_argument('description', type=str)
todo_parser.add_argument('completed', type=bool, default=False)
login_parser = reqparse.RequestParser()
login_parser.add_argument('username', type=str, required=True)
login_parser.add_argument('password', type=str, required=True)
class Login(Resource):
def post(self):
args = login_parser.parse_args()
username = args['username']
password = args['password']
if username in users and users[username]['password'] == password:
access_token = create_access_token(identity=username)
return {'access_token': access_token}, 200
else:
return {'error': 'Invalid credentials'}, 401
class TodoList(Resource):
@jwt_required()
def get(self):
current_user = get_jwt_identity()
user_todos = [todo for todo in todos.values() if todo['user'] == current_user]
return {'todos': user_todos}, 200
@jwt_required()
def post(self):
global todo_id_counter
current_user = get_jwt_identity()
args = todo_parser.parse_args()
todo = {
'id': todo_id_counter,
'title': args['title'],
'description': args.get('description', ''),
'completed': args.get('completed', False),
'user': current_user
}
todos[todo_id_counter] = todo
todo_id_counter += 1
return todo, 201
class Todo(Resource):
@jwt_required()
def get(self, todo_id):
if todo_id not in todos:
return {'error': 'Todo not found'}, 404
todo = todos[todo_id]
current_user = get_jwt_identity()
if todo['user'] != current_user:
return {'error': 'Access denied'}, 403
return todo, 200
@jwt_required()
def put(self, todo_id):
if todo_id not in todos:
return {'error': 'Todo not found'}, 404
todo = todos[todo_id]
current_user = get_jwt_identity()
if todo['user'] != current_user:
return {'error': 'Access denied'}, 403
args = todo_parser.parse_args()
todo['title'] = args['title']
todo['description'] = args.get('description', '')
todo['completed'] = args.get('completed', False)
return todo, 200
@jwt_required()
def delete(self, todo_id):
if todo_id not in todos:
return {'error': 'Todo not found'}, 404
todo = todos[todo_id]
current_user = get_jwt_identity()
if todo['user'] != current_user:
return {'error': 'Access denied'}, 403
del todos[todo_id]
return None, 204
# Register resources
api.add_resource(Login, '/api/login')
api.add_resource(TodoList, '/api/todos')
api.add_resource(Todo, '/api/todos/<int:todo_id>')
if __name__ == '__main__':
app.run(debug=True)
API Documentation
Using Flask-RESTX (Swagger)
pip install flask-restx
from flask import Flask
from flask_restx import Api, Resource, fields
app = Flask(__name__)
api = Api(app, doc='/swagger/', version='1.0', title='Todo API')
todo_model = api.model('Todo', {
'id': fields.Integer(readonly=True),
'title': fields.String(required=True),
'description': fields.String,
'completed': fields.Boolean
})
@api.route('/todos')
class TodoList(Resource):
@api.doc('list_todos')
@api.marshal_list_with(todo_model)
def get(self):
"""List all todos"""
return todos
@api.doc('create_todo')
@api.expect(todo_model)
@api.marshal_with(todo_model, code=201)
def post(self):
"""Create a new todo"""
# Implementation
pass
Testing REST APIs
Using requests
import requests
import json
BASE_URL = 'http://localhost:5000/api'
# Test login
response = requests.post(f'{BASE_URL}/login', json={
'username': 'admin',
'password': 'admin123'
})
token = response.json()['access_token']
headers = {'Authorization': f'Bearer {token}'}
# Test create todo
response = requests.post(
f'{BASE_URL}/todos',
headers=headers,
json={
'title': 'Test Todo',
'description': 'Test description'
}
)
print(response.json())
# Test get todos
response = requests.get(f'{BASE_URL}/todos', headers=headers)
print(response.json())
Using pytest
import pytest
import requests
BASE_URL = 'http://localhost:5000/api'
@pytest.fixture
def auth_token():
response = requests.post(f'{BASE_URL}/login', json={
'username': 'admin',
'password': 'admin123'
})
return response.json()['access_token']
def test_create_todo(auth_token):
headers = {'Authorization': f'Bearer {auth_token}'}
response = requests.post(
f'{BASE_URL}/todos',
headers=headers,
json={'title': 'Test Todo'}
)
assert response.status_code == 201
assert 'title' in response.json()
def test_get_todos(auth_token):
headers = {'Authorization': f'Bearer {auth_token}'}
response = requests.get(f'{BASE_URL}/todos', headers=headers)
assert response.status_code == 200
assert 'todos' in response.json()
Common Mistakes and Pitfalls
1. Not Using Proper HTTP Methods
# WRONG: Using POST for everything
class UserResource(Resource):
def post(self):
# Get user
pass
def post(self):
# Update user
pass
# CORRECT: Use appropriate HTTP methods
class UserResource(Resource):
def get(self, user_id):
# Get user
pass
def put(self, user_id):
# Update user
pass
2. Not Returning Proper Status Codes
# WRONG: Always returning 200
def post(self):
return {'message': 'Created'}, 200
# CORRECT: Use appropriate status codes
def post(self):
return {'message': 'Created'}, 201
3. Not Validating Input
# WRONG: No validation
def post(self):
data = request.json
name = data['name'] # May fail
# CORRECT: Use reqparse
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True)
def post(self):
args = parser.parse_args()
name = args['name']
4. Exposing Sensitive Data
# WRONG: Returning passwords
def get(self, user_id):
return {'password': user.password}
# CORRECT: Exclude sensitive data
def get(self, user_id):
return {'id': user.id, 'name': user.name}
Best Practices
1. Use Resource Classes
# Good: Separate resources
class UserList(Resource):
def get(self):
pass
def post(self):
pass
class User(Resource):
def get(self, user_id):
pass
def put(self, user_id):
pass
2. Consistent Response Format
def get(self, user_id):
user = get_user(user_id)
if not user:
return {'error': 'User not found'}, 404
return {'data': user}, 200
3. Use Environment Variables
import os
app.config['JWT_SECRET_KEY'] = os.environ.get('JWT_SECRET_KEY')
app.config['DATABASE_URL'] = os.environ.get('DATABASE_URL')
4. Error Handling
from werkzeug.exceptions import NotFound, BadRequest
class Item(Resource):
def get(self, item_id):
try:
item = get_item(item_id)
if not item:
raise NotFound('Item not found')
return item, 200
except ValueError as e:
raise BadRequest(str(e))
5. API Versioning
api_v1 = Api(app, prefix='/api/v1')
api_v2 = Api(app, prefix='/api/v2')
Practice Exercise
Exercise: REST API
Objective: Create a REST API for a blog application.
Instructions:
-
Create a Flask-RESTful API with:
- User authentication (JWT)
- Post resources (CRUD operations)
- Comment resources
- Proper error handling
- Request validation
-
Your API should include:
- POST /api/login - User login
- GET /api/posts - List all posts
- POST /api/posts - Create post (authenticated)
- GET /api/posts/<id> - Get post
- PUT /api/posts/<id> - Update post (authenticated, owner only)
- DELETE /api/posts/<id> - Delete post (authenticated, owner only)
- GET /api/posts/<id>/comments - Get comments for post
- POST /api/posts/<id>/comments - Add comment (authenticated)
Example Solution:
"""
REST API Exercise: Blog Application
"""
from flask import Flask
from flask_restful import Api, Resource, reqparse
from flask_jwt_extended import (
JWTManager, jwt_required, create_access_token,
get_jwt_identity
)
from flask_cors import CORS
from datetime import timedelta
app = Flask(__name__)
app.config['JWT_SECRET_KEY'] = 'your-secret-key-change-in-production'
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
jwt = JWTManager(app)
api = Api(app)
CORS(app)
# In-memory storage
posts = {}
comments = {}
post_id_counter = 1
comment_id_counter = 1
users = {
'alice': {'password': 'alice123', 'name': 'Alice'},
'bob': {'password': 'bob123', 'name': 'Bob'}
}
# Parsers
login_parser = reqparse.RequestParser()
login_parser.add_argument('username', type=str, required=True)
login_parser.add_argument('password', type=str, required=True)
post_parser = reqparse.RequestParser()
post_parser.add_argument('title', type=str, required=True)
post_parser.add_argument('content', type=str, required=True)
comment_parser = reqparse.RequestParser()
comment_parser.add_argument('content', type=str, required=True)
class Login(Resource):
def post(self):
args = login_parser.parse_args()
username = args['username']
password = args['password']
if username in users and users[username]['password'] == password:
access_token = create_access_token(identity=username)
return {
'access_token': access_token,
'user': {'username': username, 'name': users[username]['name']}
}, 200
return {'error': 'Invalid credentials'}, 401
class PostList(Resource):
def get(self):
return {'posts': list(posts.values())}, 200
@jwt_required()
def post(self):
global post_id_counter
current_user = get_jwt_identity()
args = post_parser.parse_args()
post = {
'id': post_id_counter,
'title': args['title'],
'content': args['content'],
'author': current_user,
'author_name': users[current_user]['name']
}
posts[post_id_counter] = post
post_id_counter += 1
return post, 201
class Post(Resource):
def get(self, post_id):
if post_id not in posts:
return {'error': 'Post not found'}, 404
return posts[post_id], 200
@jwt_required()
def put(self, post_id):
if post_id not in posts:
return {'error': 'Post not found'}, 404
post = posts[post_id]
current_user = get_jwt_identity()
if post['author'] != current_user:
return {'error': 'Access denied'}, 403
args = post_parser.parse_args()
post['title'] = args['title']
post['content'] = args['content']
return post, 200
@jwt_required()
def delete(self, post_id):
if post_id not in posts:
return {'error': 'Post not found'}, 404
post = posts[post_id]
current_user = get_jwt_identity()
if post['author'] != current_user:
return {'error': 'Access denied'}, 403
# Delete associated comments
global comments
comments = {k: v for k, v in comments.items() if v['post_id'] != post_id}
del posts[post_id]
return None, 204
class PostComments(Resource):
def get(self, post_id):
if post_id not in posts:
return {'error': 'Post not found'}, 404
post_comments = [c for c in comments.values() if c['post_id'] == post_id]
return {'comments': post_comments}, 200
@jwt_required()
def post(self, post_id):
global comment_id_counter
if post_id not in posts:
return {'error': 'Post not found'}, 404
current_user = get_jwt_identity()
args = comment_parser.parse_args()
comment = {
'id': comment_id_counter,
'post_id': post_id,
'content': args['content'],
'author': current_user,
'author_name': users[current_user]['name']
}
comments[comment_id_counter] = comment
comment_id_counter += 1
return comment, 201
# Register resources
api.add_resource(Login, '/api/login')
api.add_resource(PostList, '/api/posts')
api.add_resource(Post, '/api/posts/<int:post_id>')
api.add_resource(PostComments, '/api/posts/<int:post_id>/comments')
if __name__ == '__main__':
app.run(debug=True)
Expected Output: A complete REST API with authentication, CRUD operations, and proper error handling.
Challenge (Optional):
- Add pagination for posts
- Add search functionality
- Add post categories/tags
- Implement rate limiting
- Add API documentation with Swagger
- Add unit tests
Key Takeaways
- REST principles - Stateless, resource-based, HTTP methods
- Flask-RESTful - Framework for building REST APIs
- Resource classes - Organize API endpoints
- Request parsing - Validate and parse request data
- Authentication - JWT tokens, API keys
- Authorization - Role-based access control
- Error handling - Proper status codes and error messages
- API versioning - URL or header-based versioning
- Status codes - Use appropriate HTTP status codes
- Best practices - Consistent responses, validation, security
- Testing - Test APIs with requests or pytest
- Documentation - Document APIs with Swagger/OpenAPI
- CORS - Handle cross-origin requests
- Security - Never expose sensitive data
- Validation - Always validate input data
Quiz: REST APIs
Test your understanding with these questions:
-
What does REST stand for?
- A) Representational State Transfer
- B) Remote State Transfer
- C) Resource State Transfer
- D) RESTful State Transfer
-
What HTTP method creates a resource?
- A) GET
- B) POST
- C) PUT
- D) DELETE
-
What status code means "Created"?
- A) 200
- B) 201
- C) 204
- D) 400
-
What is used for authentication in REST APIs?
- A) JWT tokens
- B) API keys
- C) Basic auth
- D) All of the above
-
What validates request data in Flask-RESTful?
- A) reqparse
- B) validator
- C) parser
- D) validator
-
What decorator requires authentication?
- A) @auth_required
- B) @jwt_required
- C) @login_required
- D) @secure
-
What status code means "No Content"?
- A) 200
- B) 201
- C) 204
- D) 404
-
What is used for API versioning?
- A) URL prefix
- B) Headers
- C) Query parameters
- D) All of the above
-
What should you never return in API responses?
- A) User IDs
- B) Passwords
- C) Names
- D) Emails
-
What enables CORS in Flask?
- A) flask-cors
- B) flask-cors extension
- C) CORS(app)
- D) All of the above
Answers:
- A) Representational State Transfer (REST definition)
- B) POST (create resource)
- B) 201 (Created status code)
- D) All of the above (authentication methods)
- A) reqparse (request parsing)
- B) @jwt_required (JWT authentication)
- C) 204 (No Content)
- D) All of the above (versioning methods)
- B) Passwords (never expose)
- D) All of the above (CORS setup)
Next Steps
Excellent work! You've mastered building REST APIs. You now understand:
- REST API principles
- Flask-RESTful framework
- Authentication and authorization
- How to build production-ready APIs
What's Next?
- Lesson 20.3: GraphQL Basics
- Learn GraphQL concepts
- Work with Graphene library
- Understand GraphQL queries
Additional Resources
- Flask-RESTful: flask-restful.readthedocs.io/
- Flask-JWT-Extended: flask-jwt-extended.readthedocs.io/
- REST API Tutorial: restfulapi.net/
- HTTP Status Codes: httpstatuses.com/
Lesson completed! You're ready to move on to the next lesson.