Flask JSON Responses
JSON (JavaScript Object Notation) is the standard data format for web APIs. Flask converts Python data structures to JSON automatically using jsonify(). This topic covers building consistent, well-structured JSON responses for every outcome: success, error, and pagination.
jsonify() in Action
from flask import jsonify
@app.route('/api/user/1')
def get_user():
user = {'id': 1, 'name': 'Alice', 'email': 'alice@example.com'}
return jsonify(user)Response the client receives:
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
}Flask sets the Content-Type: application/json header automatically. The client knows the response is JSON without any extra configuration.
Python Types → JSON Types
| Python | JSON |
|---|---|
dict | object {} |
list | array [] |
str | string "..." |
int, float | number |
True / False | true / false |
None | null |
Consistent Response Structure
Real APIs wrap data in a consistent envelope. Every response — success or error — follows the same shape. This makes client-side code simpler and more predictable.
def success_response(data, message='OK', status=200):
return jsonify({
'success': True,
'message': message,
'data': data
}), status
def error_response(message, status=400):
return jsonify({
'success': False,
'message': message,
'data': None
}), statusRoutes use these helpers:
@app.route('/api/products/<int:product_id>')
def get_product(product_id):
product = Product.query.get(product_id)
if not product:
return error_response('Product not found', 404)
return success_response({
'id': product.id,
'name': product.name,
'price': product.price
})Serializing SQLAlchemy Models
SQLAlchemy model objects are not directly JSON-serializable. Add a to_dict() method to each model:
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(200))
price = db.Column(db.Float)
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'price': self.price
}
@app.route('/api/products')
def list_products():
products = Product.query.all()
return jsonify([p.to_dict() for p in products])Reading JSON from Requests
When a client sends a POST or PUT request with a JSON body, Flask parses it with request.get_json():
@app.route('/api/products', methods=['POST'])
def create_product():
data = request.get_json()
if not data:
return error_response('Request body must be JSON', 400)
name = data.get('name')
price = data.get('price')
if not name or price is None:
return error_response('name and price are required', 400)
product = Product(name=name, price=price)
db.session.add(product)
db.session.commit()
return success_response(product.to_dict(), 'Product created', 201)Paginated Responses
APIs never return thousands of records at once. Paginate large datasets:
@app.route('/api/products')
def list_products():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
pagination = Product.query.paginate(page=page, per_page=per_page, error_out=False)
return jsonify({
'success': True,
'data': [p.to_dict() for p in pagination.items],
'meta': {
'page': pagination.page,
'per_page': pagination.per_page,
'total': pagination.total,
'pages': pagination.pages,
'has_next': pagination.has_next,
'has_prev': pagination.has_prev,
}
})Client calls: GET /api/products?page=2&per_page=20
Handling Non-Serializable Types
datetime objects are not JSON-serializable by default. Convert them to ISO strings before returning:
def to_dict(self):
return {
'id': self.id,
'name': self.name,
'created_at': self.created_at.isoformat() if self.created_at else None
}Summary
Flask's jsonify() converts Python dicts and lists to JSON responses with the correct Content-Type header. Build a consistent envelope structure with success, message, and data fields. Add to_dict() methods to SQLAlchemy models to control what fields the API exposes. Paginate large collections using SQLAlchemy's .paginate() method and include pagination metadata in the response.
