}

Flask API POST Request: Handle JSON Data [Complete Tutorial]

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:

  1. Create a new request
  2. Set method to POST
  3. Enter URL: http://localhost:5000/api/users
  4. Go to Headers tab: Add Content-Type: application/json
  5. Go to Body tab: Select "raw" and "JSON"
  6. Enter JSON data: json { "username": "testuser", "email": "[email protected]", "password": "mypassword123" }
  7. 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

  1. Always validate input data before processing
  2. Use proper HTTP status codes (201 for created, 400 for bad request, etc.)
  3. Return consistent JSON response structures
  4. Handle errors gracefully with meaningful messages
  5. Use Content-Type validation to ensure JSON requests
  6. Implement rate limiting for production APIs
  7. Add authentication for protected endpoints
  8. Log requests and errors for debugging
  9. 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.

Additional Resources


Related Tutorials