Introduction
In this comprehensive tutorial, we'll learn how to create a Flask API that handles POST requests with JSON data. POST requests are essential for any web API, allowing clients to send data to your server for processing, storage, or other operations.
By the end of this guide, you'll know how to: - Create basic POST endpoints in Flask - Handle and parse JSON data - Validate incoming requests - Return proper JSON responses - Handle errors gracefully - Test your API with curl and Postman
Prerequisites
Before starting, make sure you have Python installed. Then install Flask:
pip install flask
Step 1: Basic Flask Application
Let's start with a minimal Flask application:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Hello World!"
if __name__ == '__main__':
app.run(debug=True)
Save this as app.py and run it:
python app.py
Your server is now running at http://localhost:5000.
Step 2: Creating a Basic POST Endpoint
Now let's add a POST endpoint that accepts JSON data:
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/api/users', methods=['POST'])
def create_user():
# Get JSON data from request
data = request.get_json()
# Process the data (in real app, save to database)
username = data.get('username')
email = data.get('email')
# Return response
response = {
'status': 'success',
'message': f'User {username} created successfully',
'data': {
'username': username,
'email': email
}
}
return jsonify(response), 201
if __name__ == '__main__':
app.run(debug=True)
Step 3: Understanding request.get_json()
The request.get_json() method parses the incoming JSON data:
@app.route('/api/data', methods=['POST'])
def handle_data():
# Basic usage
data = request.get_json()
# With options
data = request.get_json(force=False, silent=False, cache=True)
# force=True: Parse even without correct Content-Type header
# silent=True: Return None instead of raising error on bad JSON
# cache=True: Cache the parsed JSON
return jsonify(data)
Step 4: Request Validation
Always validate incoming data. Here's a complete example with validation:
from flask import Flask, request, jsonify
app = Flask(__name__)
def validate_user_data(data):
"""Validate user registration data."""
errors = []
if not data:
return ['No data provided']
# Required fields
if not data.get('username'):
errors.append('Username is required')
elif len(data['username']) < 3:
errors.append('Username must be at least 3 characters')
if not data.get('email'):
errors.append('Email is required')
elif '@' not in data['email']:
errors.append('Invalid email format')
if not data.get('password'):
errors.append('Password is required')
elif len(data['password']) < 8:
errors.append('Password must be at least 8 characters')
return errors
@app.route('/api/users', methods=['POST'])
def create_user():
# Check Content-Type
if not request.is_json:
return jsonify({
'status': 'error',
'message': 'Content-Type must be application/json'
}), 415
# Get JSON data
data = request.get_json()
# Validate data
errors = validate_user_data(data)
if errors:
return jsonify({
'status': 'error',
'message': 'Validation failed',
'errors': errors
}), 400
# Process valid data (save to database, etc.)
user = {
'id': 1, # In real app, this comes from database
'username': data['username'],
'email': data['email']
}
return jsonify({
'status': 'success',
'message': 'User created successfully',
'data': user
}), 201
if __name__ == '__main__':
app.run(debug=True)
Step 5: Error Handling
Implement proper error handling for your API:
from flask import Flask, request, jsonify
from werkzeug.exceptions import HTTPException
import json
app = Flask(__name__)
# Global error handler for all exceptions
@app.errorhandler(Exception)
def handle_exception(e):
# Handle HTTP exceptions
if isinstance(e, HTTPException):
response = {
'status': 'error',
'message': e.description,
'code': e.code
}
return jsonify(response), e.code
# Handle non-HTTP exceptions
response = {
'status': 'error',
'message': 'Internal server error',
'code': 500
}
return jsonify(response), 500
# Handle JSON decode errors
@app.errorhandler(400)
def handle_bad_request(e):
return jsonify({
'status': 'error',
'message': 'Bad request - invalid JSON',
'code': 400
}), 400
# Handle 404 errors
@app.errorhandler(404)
def handle_not_found(e):
return jsonify({
'status': 'error',
'message': 'Resource not found',
'code': 404
}), 404
@app.route('/api/items', methods=['POST'])
def create_item():
try:
data = request.get_json()
if not data or not data.get('name'):
return jsonify({
'status': 'error',
'message': 'Name is required'
}), 400
# Simulate processing
item = {
'id': 1,
'name': data['name'],
'description': data.get('description', '')
}
return jsonify({
'status': 'success',
'data': item
}), 201
except Exception as e:
return jsonify({
'status': 'error',
'message': str(e)
}), 500
if __name__ == '__main__':
app.run(debug=True)
Step 6: Complete Working Example
Here's a complete Flask API with multiple endpoints:
from flask import Flask, request, jsonify
from datetime import datetime
import uuid
app = Flask(__name__)
# In-memory storage (use a database in production)
users = {}
items = {}
# ========== User Endpoints ==========
@app.route('/api/users', methods=['POST'])
def create_user():
"""Create a new user."""
if not request.is_json:
return jsonify({'error': 'Content-Type must be application/json'}), 415
data = request.get_json()
# Validation
required_fields = ['username', 'email', 'password']
for field in required_fields:
if field not in data:
return jsonify({'error': f'{field} is required'}), 400
# Check if user exists
if data['email'] in [u['email'] for u in users.values()]:
return jsonify({'error': 'Email already registered'}), 409
# Create user
user_id = str(uuid.uuid4())
user = {
'id': user_id,
'username': data['username'],
'email': data['email'],
'created_at': datetime.utcnow().isoformat()
}
users[user_id] = user
return jsonify({
'message': 'User created successfully',
'user': user
}), 201
@app.route('/api/users/<user_id>', methods=['GET'])
def get_user(user_id):
"""Get a user by ID."""
if user_id not in users:
return jsonify({'error': 'User not found'}), 404
return jsonify(users[user_id])
# ========== Item Endpoints ==========
@app.route('/api/items', methods=['POST'])
def create_item():
"""Create a new item."""
if not request.is_json:
return jsonify({'error': 'Content-Type must be application/json'}), 415
data = request.get_json()
if not data.get('name'):
return jsonify({'error': 'Name is required'}), 400
item_id = str(uuid.uuid4())
item = {
'id': item_id,
'name': data['name'],
'description': data.get('description', ''),
'price': data.get('price', 0),
'created_at': datetime.utcnow().isoformat()
}
items[item_id] = item
return jsonify({
'message': 'Item created successfully',
'item': item
}), 201
@app.route('/api/items', methods=['GET'])
def list_items():
"""List all items."""
return jsonify(list(items.values()))
@app.route('/api/items/<item_id>', methods=['PUT'])
def update_item(item_id):
"""Update an item."""
if item_id not in items:
return jsonify({'error': 'Item not found'}), 404
data = request.get_json()
# Update fields
item = items[item_id]
if 'name' in data:
item['name'] = data['name']
if 'description' in data:
item['description'] = data['description']
if 'price' in data:
item['price'] = data['price']
item['updated_at'] = datetime.utcnow().isoformat()
return jsonify({
'message': 'Item updated successfully',
'item': item
})
@app.route('/api/items/<item_id>', methods=['DELETE'])
def delete_item(item_id):
"""Delete an item."""
if item_id not in items:
return jsonify({'error': 'Item not found'}), 404
del items[item_id]
return jsonify({'message': 'Item deleted successfully'})
# ========== Health Check ==========
@app.route('/api/health', methods=['GET'])
def health_check():
"""API health check endpoint."""
return jsonify({
'status': 'healthy',
'timestamp': datetime.utcnow().isoformat()
})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
Step 7: Testing with curl
Test your POST endpoints using curl:
Create User
curl -X POST http://localhost:5000/api/users \
-H "Content-Type: application/json" \
-d '{"username": "johndoe", "email": "[email protected]", "password": "secret123"}'
Expected response:
{
"message": "User created successfully",
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "johndoe",
"email": "[email protected]",
"created_at": "2025-02-12T10:30:00.000000"
}
}
Create Item
curl -X POST http://localhost:5000/api/items \
-H "Content-Type: application/json" \
-d '{"name": "Widget", "description": "A useful widget", "price": 29.99}'
Test Validation Error
curl -X POST http://localhost:5000/api/users \
-H "Content-Type: application/json" \
-d '{"username": "test"}'
Response:
{
"error": "email is required"
}
Step 8: Testing with Postman
To test with Postman:
- Create a new request
- Set method to POST
- Enter URL:
http://localhost:5000/api/users - Go to Headers tab: Add
Content-Type: application/json - Go to Body tab: Select "raw" and "JSON"
- Enter JSON data:
json { "username": "testuser", "email": "[email protected]", "password": "mypassword123" } - Click Send
Step 9: Using Flask-RESTful (Alternative)
For larger APIs, consider using Flask-RESTful:
pip install flask-restful
from flask import Flask
from flask_restful import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
# Request parser for validation
user_parser = reqparse.RequestParser()
user_parser.add_argument('username', type=str, required=True, help='Username is required')
user_parser.add_argument('email', type=str, required=True, help='Email is required')
user_parser.add_argument('password', type=str, required=True, help='Password is required')
class UserResource(Resource):
def post(self):
args = user_parser.parse_args()
user = {
'id': 1,
'username': args['username'],
'email': args['email']
}
return {'message': 'User created', 'user': user}, 201
api.add_resource(UserResource, '/api/users')
if __name__ == '__main__':
app.run(debug=True)
Best Practices
- Always validate input data before processing
- Use proper HTTP status codes (201 for created, 400 for bad request, etc.)
- Return consistent JSON response structures
- Handle errors gracefully with meaningful messages
- Use Content-Type validation to ensure JSON requests
- Implement rate limiting for production APIs
- Add authentication for protected endpoints
- Log requests and errors for debugging
- Use HTTPS in production
Summary
In this tutorial, we covered how to:
- Create Flask POST endpoints that accept JSON data
- Parse JSON using
request.get_json() - Validate incoming data
- Return proper JSON responses with appropriate status codes
- Handle errors gracefully
- Test APIs with curl and Postman
Flask makes it easy to build robust REST APIs with Python. For larger applications, consider using extensions like Flask-RESTful or Flask-RESTX for additional features like automatic documentation.