Python Flask Practice Problems & Exercises
Practice: Flask
← Back to lessonCreate a basic Flask health check endpoint that returns a JSON response.Solution
from flask import Flask, jsonify
app = Flask(__name__)
# TODO: Add a GET route at /health that returns JSON:
# {"status": "ok", "service": "my-api", "version": "1.0"}
# Test it
with app.test_client() as client:
resp = client.get('/health')
print(f"Status: {resp.status_code}")
print(f"JSON: {resp.get_json()}")
print(f"Content-Type: {resp.content_type}")
Expected Output
Status: 200
JSON: {'status': 'ok', 'service': 'my-api', 'version': '1.0'}
Content-Type: application/jsonHints
Hint 1: Use @app.route('/health') decorator above your view function.
Hint 2: Return jsonify({'key': 'value'}) to send a JSON response.
Hint 3: Flask's test_client() lets you make requests without running a server.
Build a Flask route that looks up a user by integer ID from the URL path.Solution
from flask import Flask, jsonify
app = Flask(__name__)
USERS = {
1: {'id': 1, 'name': 'Alice', 'role': 'admin'},
2: {'id': 2, 'name': 'Bob', 'role': 'user'},
3: {'id': 3, 'name': 'Carol', 'role': 'user'},
}
# TODO: Add GET /users/<int:user_id>
# Return the user dict if found (200)
# Return {"error": "User not found"} with 404 if not found
with app.test_client() as client:
r1 = client.get('/users/1')
print(f"{r1.status_code}: {r1.get_json()}")
r2 = client.get('/users/99')
print(f"{r2.status_code}: {r2.get_json()}")
Expected Output
200: {'id': 1, 'name': 'Alice', 'role': 'admin'}
404: {'error': 'User not found'}Hints
Hint 1: Use <int:user_id> in the route pattern to capture an integer path segment.
Hint 2: The captured value is passed as a keyword argument to your view function.
Hint 3: Return a tuple (jsonify(data), status_code) to set a non-200 status.
Implement a POST endpoint that validates a JSON body and creates a new item.Solution
from flask import Flask, request, jsonify
app = Flask(__name__)
items = []
# TODO: Add POST /items
# Parse JSON body, require 'name' and 'price' fields
# If missing fields, return {"error": "name and price required"} with 400
# Otherwise append {'id': len(items)+1, 'name': ..., 'price': ...} to items
# Return the new item with 201
with app.test_client() as client:
r1 = client.post('/items', json={'name': 'Widget', 'price': 9.99})
print(f"{r1.status_code}: {r1.get_json()}")
r2 = client.post('/items', json={'name': 'Gadget'})
print(f"{r2.status_code}: {r2.get_json()}")
Expected Output
201: {'id': 1, 'name': 'Widget', 'price': 9.99}
400: {'error': 'name and price required'}Hints
Hint 1: request.get_json() parses the JSON body into a Python dict.
Hint 2: Check for required fields and return 400 if missing.
Hint 3: Return (jsonify(new_item), 201) — HTTP 201 Created is correct for POST.
Build a Flask products endpoint that filters and sorts using query string parameters.Solution
from flask import Flask, request, jsonify
app = Flask(__name__)
PRODUCTS = [
{'id': 1, 'name': 'Widget', 'category': 'hardware', 'price': 9.99},
{'id': 2, 'name': 'Gadget', 'category': 'electronics', 'price': 49.99},
{'id': 3, 'name': 'Doohickey', 'category': 'hardware', 'price': 19.99},
{'id': 4, 'name': 'Thingamajig', 'category': 'electronics', 'price': 99.99},
]
# TODO: GET /products
# Optional query params: category (filter), max_price (float, filter), sort (name or price)
# Return filtered and sorted list
with app.test_client() as client:
r1 = client.get('/products?category=hardware')
print(f"hardware: {[p['name'] for p in r1.get_json()]}")
r2 = client.get('/products?max_price=50&sort=price')
print(f"max_price=50 sorted: {[p['name'] for p in r2.get_json()]}")
Expected Output
hardware: ['Widget', 'Doohickey']
max_price=50 sorted: ['Widget', 'Doohickey', 'Gadget']Hints
Hint 1: request.args.get('category') returns the query param or None.
Hint 2: Use request.args.get('max_price', type=float) to auto-convert to float.
Hint 3: Chain filter() and sorted() calls to apply multiple filters.
Register JSON error handlers for 404 and 500 HTTP errors in Flask.Solution
from flask import Flask, jsonify, abort
app = Flask(__name__)
# TODO: Register error handlers for 404 and 500 that return JSON
# 404: {"error": "Not found", "status": 404}
# 500: {"error": "Internal server error", "status": 500}
@app.route('/users/<int:uid>')
def get_user(uid):
if uid == 0:
abort(500)
if uid > 100:
abort(404)
return jsonify({'id': uid, 'name': f'User {uid}'})
with app.test_client() as client:
r1 = client.get('/users/42')
print(f"{r1.status_code}: {r1.get_json()}")
r2 = client.get('/users/999')
print(f"{r2.status_code}: {r2.get_json()}")
r3 = client.get('/users/0')
print(f"{r3.status_code}: {r3.get_json()}")
Expected Output
200: {'id': 42, 'name': 'User 42'}
404: {'error': 'Not found', 'status': 404}
500: {'error': 'Internal server error', 'status': 500}Hints
Hint 1: Use @app.errorhandler(404) decorator to register a handler for 404 errors.
Hint 2: The handler receives an error object as its argument.
Hint 3: Return jsonify(response_dict), status_code as a tuple.
Implement a before_request hook that validates Bearer tokens and sets the current user in Flask's g object.Solution
from flask import Flask, jsonify, request, g
app = Flask(__name__)
VALID_TOKENS = {
'token-alice': {'user_id': 1, 'name': 'Alice', 'role': 'admin'},
'token-bob': {'user_id': 2, 'name': 'Bob', 'role': 'user'},
}
# TODO: Add a before_request hook that:
# 1. Skips /public (no auth needed)
# 2. For all other routes, reads Authorization: Bearer <token> header
# 3. If token is valid, sets g.user to the user dict
# 4. If missing or invalid, returns {"error": "Unauthorized"} with 401
@app.route('/public')
def public():
return jsonify({'message': 'public endpoint'})
@app.route('/me')
def me():
return jsonify({'user': g.user})
with app.test_client() as client:
r1 = client.get('/public')
print(f"/public: {r1.status_code} {r1.get_json()}")
r2 = client.get('/me', headers={'Authorization': 'Bearer token-alice'})
print(f"/me (alice): {r2.status_code} {r2.get_json()}")
r3 = client.get('/me', headers={'Authorization': 'Bearer bad-token'})
print(f"/me (bad): {r3.status_code} {r3.get_json()}")
r4 = client.get('/me')
print(f"/me (no auth): {r4.status_code} {r4.get_json()}")
Expected Output
/public: 200 {'message': 'public endpoint'}
/me (alice): 200 {'user': {'user_id': 1, 'name': 'Alice', 'role': 'admin'}}
/me (bad): 401 {'error': 'Unauthorized'}
/me (no auth): 401 {'error': 'Unauthorized'}Hints
Hint 1: Use @app.before_request to register a function that runs before every request.
Hint 2: Return None from the hook to allow the request to proceed; return a Response to short-circuit.
Hint 3: request.path gives the current path; check if it starts with '/public'.
Organize Flask routes into two separate blueprints and register them on the main app.Solution
from flask import Flask, Blueprint, jsonify
# TODO: Create two blueprints:
# 1. users_bp with url_prefix='/api/users'
# - GET / -> list of users
# - GET /<int:uid> -> single user
# 2. products_bp with url_prefix='/api/products'
# - GET / -> list of products
USERS = [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
PRODUCTS = [{'id': 1, 'name': 'Widget'}, {'id': 2, 'name': 'Gadget'}]
# Register blueprints and test
app = Flask(__name__)
# app.register_blueprint(...)
with app.test_client() as client:
r1 = client.get('/api/users/')
print(f"users: {r1.get_json()}")
r2 = client.get('/api/users/1')
print(f"user 1: {r2.get_json()}")
r3 = client.get('/api/products/')
print(f"products: {r3.get_json()}")
Expected Output
users: [{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}]
user 1: {'id': 1, 'name': 'Alice'}
products: [{'id': 1, 'name': 'Widget'}, {'id': 2, 'name': 'Gadget'}]Hints
Hint 1: Blueprint('users', __name__) creates a blueprint named 'users'.
Hint 2: Use @users_bp.route('/') and @users_bp.route('/<int:uid>') to register blueprint routes.
Hint 3: app.register_blueprint(users_bp, url_prefix='/api/users') mounts it at that prefix.
Build a Flask registration endpoint that reads form data and validates all fields, collecting errors.Solution
from flask import Flask, request, jsonify
app = Flask(__name__)
# TODO: POST /register
# Accepts multipart/form-data OR application/x-www-form-urlencoded
# Required fields: username, email, password
# Validate: username >= 3 chars, email contains '@', password >= 8 chars
# On success return {"message": "registered", "username": ...} 201
# On failure return {"errors": [...list of error strings...]} 400
with app.test_client() as client:
r1 = client.post('/register', data={
'username': 'alice',
'email': '[email protected]',
'password': 'secret123'
})
print(f"{r1.status_code}: {r1.get_json()}")
r2 = client.post('/register', data={
'username': 'ab',
'email': 'notanemail',
'password': 'short'
})
print(f"{r2.status_code}: {r2.get_json()}")
Expected Output
201: {'message': 'registered', 'username': 'alice'}
400: {'errors': ['username must be at least 3 characters', 'email must contain @', 'password must be at least 8 characters']}Hints
Hint 1: request.form is a MultiDict — use request.form.get('field') to read form fields.
Hint 2: Collect all errors in a list and return them together (don't fail-fast).
Hint 3: Return 201 on success: return jsonify(data), 201.
Implement request timing middleware using Flask's before_request and after_request hooks.Solution
import time
from flask import Flask, request, jsonify, g
app = Flask(__name__)
request_log = []
# TODO: Implement request timing middleware using before_request and after_request:
# before_request: record start time in g.start_time
# after_request: compute duration_ms, log to request_log list
# Each log entry: {'method': ..., 'path': ..., 'status': ..., 'duration_ms': float}
# after_request must return the response object unchanged
@app.route('/fast')
def fast():
return jsonify({'endpoint': 'fast'})
@app.route('/slow')
def slow():
time.sleep(0.05)
return jsonify({'endpoint': 'slow'})
with app.test_client() as client:
client.get('/fast')
client.get('/slow')
client.get('/fast')
for entry in request_log:
fast_enough = entry['duration_ms'] < 200
print(f"{entry['method']} {entry['path']} -> {entry['status']} ({fast_enough=})")
print(f"Total requests logged: {len(request_log)}")
Expected Output
GET /fast -> 200 (fast_enough=True)
GET /slow -> 200 (fast_enough=True)
GET /fast -> 200 (fast_enough=True)
Total requests logged: 3Hints
Hint 1: Use @app.before_request to set g.start_time = time.time().
Hint 2: @app.after_request receives the response object and must return it.
Hint 3: duration_ms = (time.time() - g.start_time) * 1000; access status via response.status_code.
Implement a complete CRUD REST API for notes using Flask with correct HTTP status codes.Solution
from flask import Flask, request, jsonify
app = Flask(__name__)
# TODO: Implement a complete CRUD REST API for /api/notes
# In-memory store: dict keyed by int ID, auto-incrementing
# POST /api/notes -> create (201)
# GET /api/notes -> list all (200)
# GET /api/notes/<id> -> get one (200 or 404)
# PUT /api/notes/<id> -> full replace (200 or 404)
# DELETE /api/notes/<id> -> delete (204 or 404)
# Note fields: id, title, content
with app.test_client() as client:
r1 = client.post('/api/notes', json={'title': 'First', 'content': 'Hello'})
r2 = client.post('/api/notes', json={'title': 'Second', 'content': 'World'})
print(f"create: {r1.status_code}, {r2.status_code}")
r3 = client.get('/api/notes')
print(f"list: {[n['title'] for n in r3.get_json()]}")
r4 = client.put('/api/notes/1', json={'title': 'Updated', 'content': 'Changed'})
print(f"update: {r4.status_code}, {r4.get_json()['title']}")
r5 = client.delete('/api/notes/1')
print(f"delete: {r5.status_code}")
r6 = client.get('/api/notes/1')
print(f"get deleted: {r6.status_code}")
Expected Output
create: 201, 201
list: ['First', 'Second']
update: 200, Updated
delete: 204
get deleted: 404Hints
Hint 1: Use a module-level dict `notes = {}` and `next_id = 1` as your store.
Hint 2: DELETE should return 204 No Content — use make_response('', 204).
Hint 3: PUT replaces the whole resource: build a new dict with the ID preserved.
Implement the Flask application factory pattern to support multiple independent app instances with different configs.Solution
from flask import Flask, jsonify
# TODO: Implement a Flask application factory pattern.
# create_app(config=None) should:
# 1. Create the Flask app
# 2. Apply config dict if provided (app.config.update)
# 3. Register a blueprint 'api_bp' with url_prefix='/api'
# - GET /api/config-check returns {"debug": app.config.get("DEBUG"), "testing": app.config.get("TESTING")}
# 4. Return the configured app
# The factory pattern enables creating separate test/prod instances.
def create_app(config=None):
pass
# Test production config
prod_app = create_app({'DEBUG': False, 'TESTING': False})
with prod_app.test_client() as client:
r = client.get('/api/config-check')
print(f"prod: {r.get_json()}")
# Test testing config
test_app = create_app({'DEBUG': True, 'TESTING': True})
with test_app.test_client() as client:
r = client.get('/api/config-check')
print(f"test: {r.get_json()}")
# Verify they are independent instances
print(f"independent: {prod_app is not test_app}")
Expected Output
prod: {'debug': False, 'testing': False}
test: {'debug': True, 'testing': True}
independent: TrueHints
Hint 1: Create the Flask app inside create_app() — this is the factory pattern.
Hint 2: Use app.config.update(config) if config is not None.
Hint 3: Create the blueprint inside or outside the factory, then register it inside.
