Skip to main content

Flask: Combining Flask with REST APIs

Flask: Combining Flask with REST APIs

REST APIs enable structured communication between clients and servers, making them essential for modern web applications. Built on Flask’s lightweight core and leveraging Jinja2 Templating and Werkzeug WSGI, Flask excels at creating RESTful APIs with minimal setup. This tutorial explores Flask combining with REST APIs, covering setup, endpoint design, and practical applications for building scalable, client-agnostic backends.


01. Why Use Flask for REST APIs?

Flask’s simplicity, flexibility, and robust ecosystem make it ideal for building REST APIs. It supports JSON responses, HTTP methods, and integrates seamlessly with frontend frameworks or mobile apps. Leveraging Jinja2 Templating for dynamic responses and Werkzeug WSGI for request handling, Flask enables developers to create APIs that are easy to maintain and scale, supporting diverse clients like web browsers, mobile apps, or IoT devices.

Example: Basic Flask REST API

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/api/hello', methods=['GET'])
def hello():
    return jsonify({'message': 'Welcome to the Flask API!'})

if __name__ == '__main__':
    app.run(debug=True)

Output (GET /api/hello):

{
  "message": "Welcome to the Flask API!"
}

Explanation:

  • jsonify - Converts Python dictionaries to JSON responses.
  • methods=['GET'] - Specifies the HTTP method for the endpoint.

02. Key Techniques for REST APIs

Building REST APIs with Flask involves defining endpoints, handling HTTP methods, and managing data. The table below summarizes key techniques and their applications:

Technique Description Use Case
Endpoint Definition @app.route Create resource-based URLs
HTTP Methods methods=['GET', 'POST', ...] Handle CRUD operations
Request Parsing request.get_json() Process client data
Error Handling abort, custom responses Manage invalid requests
Database Integration Flask-SQLAlchemy Persist API data


2.1 Defining RESTful Endpoints

Example: CRUD API for Tasks

from flask import Flask, jsonify, request

app = Flask(__name__)

tasks = [
    {'id': 1, 'title': 'Learn Flask', 'done': False}
]

@app.route('/api/tasks', methods=['GET'])
def get_tasks():
    return jsonify(tasks)

@app.route('/api/tasks', methods=['POST'])
def create_task():
    task = request.get_json()
    tasks.append(task)
    return jsonify(task), 201

@app.route('/api/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
    task = next((t for t in tasks if t['id'] == task_id), None)
    if task:
        return jsonify(task)
    return jsonify({'error': 'Task not found'}), 404

@app.route('/api/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
    task = next((t for t in tasks if t['id'] == task_id), None)
    if task:
        updates = request.get_json()
        task.update(updates)
        return jsonify(task)
    return jsonify({'error': 'Task not found'}), 404

@app.route('/api/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
    global tasks
    tasks = [t for t in tasks if t['id'] != task_id]
    return jsonify({'message': 'Task deleted'})

if __name__ == '__main__':
    app.run(debug=True)

Output (GET /api/tasks):

[
  {"id": 1, "title": "Learn Flask", "done": false}
]

Explanation:

  • GET - Retrieves all or specific tasks.
  • POST - Creates a new task.
  • PUT - Updates an existing task.
  • DELETE - Removes a task.

2.2 Parsing Request Data

Example: Handling POST Requests

from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    if not data or 'name' not in data:
        return jsonify({'error': 'Name is required'}), 400
    user = {'id': 1, 'name': data['name']}
    return jsonify(user), 201

if __name__ == '__main__':
    app.run(debug=True)

Sample Request (POST /api/users):

curl -X POST -H "Content-Type: application/json" -d '{"name":"Alice"}' http://localhost:5000/api/users

Output:

{
  "id": 1,
  "name": "Alice"
}

Explanation:

  • request.get_json() - Parses JSON data from the request.
  • Validates input to ensure required fields are present.

2.3 Error Handling

Example: Custom Error Responses

from flask import Flask, jsonify, request, abort

app = Flask(__name__)

tasks = [{'id': 1, 'title': 'Task 1', 'done': False}]

@app.route('/api/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
    task = next((t for t in tasks if t['id'] == task_id), None)
    if not task:
        abort(404, description="Task not found")
    return jsonify(task)

@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': str(error.description)}), 404

if __name__ == '__main__':
    app.run(debug=True)

Output (GET /api/tasks/999):

{
  "error": "Task not found"
}

Explanation:

  • abort - Triggers an HTTP error with a custom message.
  • @app.errorhandler - Customizes error responses.

2.4 Integrating with Flask-SQLAlchemy

Example: Database-Backed API

from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
db = SQLAlchemy(app)

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    done = db.Column(db.Boolean, default=False)

@app.route('/api/tasks', methods=['GET'])
def get_tasks():
    tasks = Task.query.all()
    return jsonify([{'id': t.id, 'title': t.title, 'done': t.done} for t in tasks])

@app.route('/api/tasks', methods=['POST'])
def create_task():
    data = request.get_json()
    task = Task(title=data['title'], done=data.get('done', False))
    db.session.add(task)
    db.session.commit()
    return jsonify({'id': task.id, 'title': task.title, 'done': task.done}), 201

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

Output (GET /api/tasks after POST):

[
  {"id": 1, "title": "Database Task", "done": false}
]

Explanation:

  • Flask-SQLAlchemy - Persists tasks in a database.
  • Ensures data durability and scalability.

2.5 Incorrect API Design

Example: Non-RESTful Endpoint

from flask import Flask

app = Flask(__name__)

@app.route('/get_data')  # Incorrect: Non-RESTful naming
def get_data():
    return {'data': 'Not RESTful'}

if __name__ == '__main__':
    app.run(debug=True)

Output (GET /get_data):

{
  "data": "Not RESTful"
}

Explanation:

  • Non-resource-based URLs violate REST principles.
  • Solution: Use resource-based URLs (e.g., /api/data).

03. Effective Usage

3.1 Recommended Practices

  • Follow RESTful conventions: resource-based URLs, appropriate HTTP methods.

Example: Comprehensive REST API

from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)
CORS(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)

@app.route('/api/users', methods=['GET', 'POST'])
def users():
    if request.method == 'GET':
        users = User.query.all()
        return jsonify([{'id': u.id, 'name': u.name} for u in users])
    elif request.method == 'POST':
        data = request.get_json()
        if not data or 'name' not in data:
            return jsonify({'error': 'Name is required'}), 400
        user = User(name=data['name'])
        db.session.add(user)
        db.session.commit()
        return jsonify({'id': user.id, 'name': user.name}), 201

@app.route('/api/users/<int:user_id>', methods=['GET', 'PUT', 'DELETE'])
def user(user_id):
    user = User.query.get(user_id)
    if not user:
        return jsonify({'error': 'User not found'}), 404
    if request.method == 'GET':
        return jsonify({'id': user.id, 'name': user.name})
    elif request.method == 'PUT':
        data = request.get_json()
        user.name = data['name']
        db.session.commit()
        return jsonify({'id': user.id, 'name': user.name})
    elif request.method == 'DELETE':
        db.session.delete(user)
        db.session.commit()
        return jsonify({'message': 'User deleted'})

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)
  • Use Flask-CORS for frontend integration.
  • Implement proper error handling and validation.

3.2 Practices to Avoid

  • Avoid returning non-JSON responses for API endpoints.

Example: Non-JSON Response

from flask import Flask

app = Flask(__name__)

@app.route('/api/data')
def get_data():
    return "Plain text"  # Incorrect: Not JSON

if __name__ == '__main__':
    app.run(debug=True)

Output (fetching /api/data):

SyntaxError: Unexpected token P in JSON at position 0
  • Non-JSON responses break client-side parsing.
  • Solution: Use jsonify for API responses.

04. Common Use Cases

4.1 Task Management API

Build a REST API for managing tasks, consumable by any client.

Example: Task Management API

from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db'
db = SQLAlchemy(app)
CORS(app)

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    done = db.Column(db.Boolean, default=False)

@app.route('/api/tasks', methods=['GET', 'POST'])
def tasks():
    if request.method == 'GET':
        tasks = Task.query.all()
        return jsonify([{'id': t.id, 'title': t.title, 'done': t.done} for t in tasks])
    elif request.method == 'POST':
        data = request.get_json()
        task = Task(title=data['title'], done=data.get('done', False))
        db.session.add(task)
        db.session.commit()
        return jsonify({'id': task.id, 'title': task.title, 'done': task.done}), 201

@app.route('/api/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
    task = Task.query.get(task_id)
    if not task:
        return jsonify({'error': 'Task not found'}), 404
    db.session.delete(task)
    db.session.commit()
    return jsonify({'message': 'Task deleted'})

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

Explanation:

  • Provides endpoints for creating, listing, and deleting tasks.
  • Uses SQLite for persistent storage.

4.2 User Authentication API

Create an API for user registration and login.

Example: User Authentication API

from flask import Flask, jsonify, request
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
db = SQLAlchemy(app)
CORS(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)  # Simplified, no hashing

@app.route('/api/register', methods=['POST'])
def register():
    data = request.get_json()
    if not data or 'username' not in data or 'password' not in data:
        return jsonify({'error': 'Username and password required'}), 400
    user = User(username=data['username'], password=data['password'])
    db.session.add(user)
    db.session.commit()
    return jsonify({'id': user.id, 'username': user.username}), 201

@app.route('/api/login', methods=['POST'])
def login():
    data = request.get_json()
    user = User.query.filter_by(username=data['username'], password=data['password']).first()
    if user:
        return jsonify({'id': user.id, 'username': user.username})
    return jsonify({'error': 'Invalid credentials'}), 401

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

Explanation:

  • Handles user registration and login with basic validation.
  • Note: In production, use password hashing (e.g., bcrypt).

Conclusion

Combining Flask with REST APIs, powered by Jinja2 Templating and Werkzeug WSGI, enables developers to build robust, scalable backends. Key takeaways:

  • Design RESTful endpoints with @app.route and jsonify.
  • Handle HTTP methods for CRUD operations.
  • Integrate with Flask-SQLAlchemy for data persistence.
  • Avoid non-RESTful designs and non-JSON responses.

With Flask, you can create powerful REST APIs that serve diverse clients, from web apps to mobile devices, with ease and efficiency!

Comments