Skip to the content.

Understanding RESTful APIs with Flask-RESTful

RESTful APIs are essential for modern web development, allowing seamless communication between a client and a server. In Flask, we can create RESTful APIs efficiently using the Flask-RESTful extension.

1. What is an API?

An API (Application Programming Interface) is a set of rules that allows software applications to communicate with each other. APIs enable data exchange between systems and provide a standardized way for applications to interact.

2. Why Use an API?

3. When to Use an API?

4. How APIs Work

A client sends a request to the server, and the server processes it and returns a response. APIs follow a request-response cycle:

πŸ’‘ Too Much used Analogy: Imagine ordering food at a restaurant. The waiter (API) takes your order (request), gives it to the kitchen (server), and brings back the food (response).

5. Hierarchy: From Route to Flask-RESTful API

Understanding the progression from a simple route to a full-fledged Flask-RESTful API is crucial for building scalable applications. Below is a structured hierarchy:

  1. Flask Route (@app.route): The most basic way to create an API using Flask. It defines individual endpoints manually and maps them to functions.

    • Example:
      @app.route('/hello', methods=['GET'])
      def hello():
          return "Hello, World!"
      
  2. API (Application Programming Interface): A broader concept that refers to any defined mechanism allowing applications to interact. APIs serve data through structured endpoints.

  3. RESTful API: A RESTful API follows the principles of REST (Representational State Transfer) to enable efficient client-server communication. It follows standard HTTP methods:

HTTP Method Operation CRUD
GET Retrieve data Read
POST Create new data Create
PUT Update existing data Update
DELETE Remove data Delete
  1. Flask-RESTful API: A structured and scalable way to build RESTful APIs using the Flask-RESTful extension. It organizes endpoints using class-based resources, making it easier to manage complex APIs.

    • Example:
      from flask_restful import Resource, Api
      
      class Hello(Resource): #remember class from flask_sqlalchemy it aslo takes db.Model similar to Resource here
          def get(self):
              return {"message": "Hello, World!"}
      

This hierarchy shows how API development progresses from a simple function-based approach to a more scalable, structured Flask-RESTful approach.

6. Difference Between Flask Route and Flask-RESTful

Feature Flask Route Flask-RESTful
API Creation Uses @app.route decorator Uses Resource class and add_resource method
HTTP Methods Needs explicit function definitions Handles multiple HTTP methods using class methods
Code Organization Less structured, function-based More structured, class-based
Scalability Can become complex with many routes Easier to manage as API grows
Data Parsing Uses request.json for input Uses reqparse.RequestParser for structured input handling
Response Format Uses jsonify() for response Returns a dictionary, automatically converted to JSON

Code Comparison: Jinja2 vs Flask-RESTful

Using Jinja2 (render_template)

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

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

Using Flask-RESTful

from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class Home(Resource):
    def get(self):
        return {"message": "Welcome to the API"}

api.add_resource(Home, '/')

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

7. HTTP Methods and Implementation

HTTP methods allow interaction with APIs. Below is a comparison of Flask route and Flask-RESTful for different HTTP methods.

Flask Route Example

from flask import Flask, request, jsonify

app = Flask(__name__)
users = {}

@app.route('/user/<string:user_id>', methods=['GET'])
def get_user(user_id):
    return jsonify(users.get(user_id, "User not found"))

@app.route('/user/<string:user_id>', methods=['POST'])
def create_user(user_id):
    users[user_id] = request.json
    return jsonify({"message": "User added", "data": users[user_id]})

@app.route('/user/<string:user_id>', methods=['PUT'])
def update_user(user_id):
    users[user_id] = request.json
    return jsonify({"message": "User updated", "data": users[user_id]})

@app.route('/user/<string:user_id>', methods=['DELETE'])
def delete_user(user_id):
    users.pop(user_id, None)
    return jsonify({"message": "User deleted"})

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

HTML Form Restriction Example

<form action="/user/1" method="post">
    <input type="text" name="name" placeholder="Enter Name">
    <input type="submit" value="Submit">
</form>

Note: HTML forms do not support PUT or DELETE methods.

Flask-RESTful with Database Example

from flask import Flask, request, jsonify
from flask_restful import Resource, Api, reqparse
from flask_sqlalchemy import SQLAlchemy

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

class UserModel(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))
    email = db.Column(db.String(100))

with app.app_context():
    db.create_all()

class User(Resource):
    def get(self, user_id):
        user = UserModel.query.get(user_id)
        if user:
            return jsonify({"id": user.id, "name": user.name, "email": user.email})
        return jsonify({"message": "User not found"})

    def post(self, user_id):
        data = request.get_json()
        user = UserModel(id=user_id, name=data['name'], email=data['email'])
        db.session.add(user)
        db.session.commit()
        return jsonify({"message": "User added", "data": data})

    def put(self, user_id):
        user = UserModel.query.get(user_id)
        if not user:
            return jsonify({"message": "User not found"})
        data = request.get_json()
        user.name = data.get('name', user.name)
        user.email = data.get('email', user.email)
        db.session.commit()
        return jsonify({"message": "User updated", "data": {"name": user.name, "email": user.email}})

    def delete(self, user_id):
        user = UserModel.query.get(user_id)
        if not user:
            return jsonify({"message": "User not found"})
        db.session.delete(user)
        db.session.commit()
        return jsonify({"message": "User deleted"})

api.add_resource(User, '/user/<int:user_id>')

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

8. Fetch API (Client-Side Usage)

fetch('http://127.0.0.1:5000/user/1')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

Example of a Simple REST API

To illustrate the REST concept, let’s create a simple API using Flask’s route decorator. This API will allow us to retrieve and create users.

Install Flask-RESTful

pip install flask flask-restful flask-sqlalchemy

Using Flask Route with jsonify:

from flask import Flask, jsonify, request

app = Flask(__name__)

users = {"1": {"name": "Alice", "email": "alice@example.com"}}

@app.route('/user/<string:user_id>', methods=['GET'])
def get_user(user_id):
    return jsonify(users.get(user_id, "User not found"))

@app.route('/user/<string:user_id>', methods=['POST'])
def create_user(user_id):
    users[user_id] = request.json
    return jsonify({"message": "User added", "data": users[user_id]})

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

Another Way to Create APIs: Using Flask with Response Object

Instead of using jsonify, we can directly use the Response object to create API responses:

from flask import Flask, Response, request
import json

app = Flask(__name__)

users = {"1": {"name": "Alice", "email": "alice@example.com"}}

@app.route('/user/<string:user_id>', methods=['GET'])
def get_user(user_id):
    response = json.dumps(users.get(user_id, {"error": "User not found"}))
    return Response(response, status=200, mimetype='application/json')

@app.route('/user/<string:user_id>', methods=['POST'])
def create_user(user_id):
    users[user_id] = request.json
    response = json.dumps({"message": "User added", "data": users[user_id]})
    return Response(response, status=201, mimetype='application/json')

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

9. Conclusion

Flask-RESTful makes it easy to build structured, scalable REST APIs with Flask. It improves API management by enforcing REST principles and simplifying HTTP method handling. Using JSON for data exchange and YAML for documentation ensures standardization and ease of use.

Extra

Breakdown of the code defines a Flask-RESTful API with SQLAlchemy for managing users.


1. Imports and Flask App Initialization

from flask import Flask, request, jsonify
from flask_restful import Resource, Api, reqparse
from flask_sqlalchemy import SQLAlchemy

2. Flask App and Database Configuration

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

with app.app_context():
    db.create_all()


3. Defining the UserModel (Database Table)

class UserModel(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100))
    email = db.Column(db.String(100))

4. Creating the User Resource

class User(Resource):
    def get(self, user_id):
        user = UserModel.query.get(user_id)
        if user:
            return jsonify({"id": user.id, "name": user.name, "email": user.email})
        return jsonify({"message": "User not found"})

5. Handling User Creation

    def post(self, user_id):
        data = request.get_json()
        user = UserModel(id=user_id, name=data['name'], email=data['email'])
        db.session.add(user)
        db.session.commit()
        return jsonify({"message": "User added", "data": data})

6. Updating an Existing User

    def put(self, user_id):
        user = UserModel.query.get(user_id)
        if not user:
            return jsonify({"message": "User not found"})
        data = request.get_json()
        user.name = data.get('name', user.name)
        user.email = data.get('email', user.email)
        db.session.commit()
        return jsonify({"message": "User updated", "data": {"name": user.name, "email": user.email}})

7. Deleting a User

    def delete(self, user_id):
        user = UserModel.query.get(user_id)
        if not user:
            return jsonify({"message": "User not found"})
        db.session.delete(user)
        db.session.commit()
        return jsonify({"message": "User deleted"})

8. Adding Resource to the API

api.add_resource(User, '/user/<int:user_id>')

9. Running the Application

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

πŸ”Ή Summary of CRUD Operations

HTTP Method Endpoint Description
GET /user/1 Fetches user with ID 1.
POST /user/1 + JSON body Creates a user with ID 1.
PUT /user/1 + JSON body Updates user with ID 1.
DELETE /user/1 Deletes user with ID 1.

πŸ”Ή Testing with Thunder Client or Postman

  1. GET Request

    • URL: http://127.0.0.1:5000/user/1
    • Response:
      {
        "id": 1,
        "name": "John Doe",
        "email": "john@example.com"
      }
      
  2. POST Request

    • URL: http://127.0.0.1:5000/user/1
    • Headers: { "Content-Type": "application/json" }
    • Body:
      {
        "name": "John Doe",
        "email": "john@example.com"
      }
      
    • Response:
      {
        "message": "User added",
        "data": {
          "name": "John Doe",
          "email": "john@example.com"
        }
      }
      
  3. PUT Request

    • URL: http://127.0.0.1:5000/user/1
    • Headers: { "Content-Type": "application/json" }
    • Body:
      {
        "name": "Jane Doe",
        "email": "jane@example.com"
      }
      
    • Response:
      {
        "message": "User updated",
        "data": {
          "name": "Jane Doe",
          "email": "jane@example.com"
        }
      }
      
  4. DELETE Request

    • URL: http://127.0.0.1:5000/user/1
    • Response:
      {
        "message": "User deleted"
      }
      

πŸ”Ή Key Takeaways

βœ… Flask-RESTful organizes APIs using class-based resources.
βœ… SQLAlchemy provides ORM support for interacting with the database.
βœ… GET, POST, PUT, DELETE enable full CRUD functionality.
βœ… Thunder Client/Postman can be used to test API requests.
βœ… db.create_all() ensures the database is created before running.

Idempotency in HTTP Verbs

Idempotency means that making multiple identical requests has the same effect as making a single request. In other words, the outcome remains unchanged no matter how many times the request is sent.

Idempotent HTTP Methods

These methods ensure that repeated requests do not change the server state after the first successful request.

  1. GET:

    • Retrieves data from the server.
    • Multiple GET requests return the same result without modifying data.
    • Example: Fetching user details (GET /user/1) multiple times does not alter the user.
  2. PUT:

    • Updates or creates a resource at a specified location.
    • Sending the same PUT request repeatedly results in the same final state.
    • Example: PUT /user/1 with { "name": "John" } will always update user 1 to "John" regardless of how many times it is sent.
  3. DELETE:

    • Removes a resource.
    • Multiple DELETE requests for the same resource will always result in the resource being removed or remain absent.
    • Example: DELETE /user/1 ensures user 1 is deleted, and subsequent calls do nothing.
  4. HEAD:

    • Similar to GET, but it only returns headers without the response body.
    • It does not modify any resources, so it is idempotent.
  5. OPTIONS:

    • Used to describe the communication options available for a resource.
    • Making multiple requests does not change anything.

Non-Idempotent HTTP Methods

These methods do not guarantee the same outcome when called multiple times.

  1. POST:

    • Creates a new resource.
    • Sending multiple POST requests results in multiple resources being created.
    • Example: POST /user with { "name": "Alice" } creates multiple user entries.
  2. PATCH:

    • Partially updates a resource.
    • Multiple PATCH requests may lead to different states depending on how they are applied.
    • Example: Incrementing a counter in a PATCH request changes the state each time.

Key Takeaways

Here's a Flask example demonstrating idempotency for PUT, DELETE, and POST using SQLite and SQLAlchemy.


Scenario:


Flask Application with Idempotency Handling

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

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///users.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)

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

# Initialize database
with app.app_context():
    db.create_all()

# βœ… IDEMPOTENT: PUT (Update or Create)
@app.route("/users/<int:user_id>", methods=["PUT"])
def update_user(user_id):
    data = request.get_json()
    user = User.query.get(user_id)
    
    if user:  # If user exists, update it
        user.name = data["name"]
    else:  # If user doesn't exist, create it (still idempotent)
        user = User(id=user_id, name=data["name"])
        db.session.add(user)

    db.session.commit()
    return jsonify({"id": user.id, "name": user.name}), 200

# βœ… IDEMPOTENT: DELETE (Always results in resource absence)
@app.route("/users/<int:user_id>", methods=["DELETE"])
def delete_user(user_id):
    user = User.query.get(user_id)
    
    if user:  # Delete if exists
        db.session.delete(user)
        db.session.commit()
        return jsonify({"message": "User deleted"}), 200
    else:  # If already deleted, response remains same
        return jsonify({"message": "User not found"}), 200

# ❌ NON-IDEMPOTENT: POST (Creates new entry every time)
@app.route("/users", methods=["POST"])
def create_user():
    data = request.get_json()
    new_user = User(name=data["name"])
    db.session.add(new_user)
    db.session.commit()
    return jsonify({"id": new_user.id, "name": new_user.name}), 201

# Run the app
if __name__ == "__main__":
    app.run(debug=True)

Testing the API

1. PUT Request (Idempotent)

curl -X PUT http://127.0.0.1:5000/users/1 -H "Content-Type: application/json" -d '{"name": "Alice"}'

2. DELETE Request (Idempotent)

curl -X DELETE http://127.0.0.1:5000/users/1

3. POST Request (Non-Idempotent)

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

Key Takeaways

βœ… PUT is idempotent β†’ No matter how many times you update user 1, the result remains the same.
βœ… DELETE is idempotent β†’ User 1 remains deleted regardless of retries.
❌ POST is not idempotent β†’ Creates a new user every time.


JSON vs YAML

Feature JSON (JavaScript Object Notation) YAML (YAML Ain't Markup Language)
Format Text-based, structured like JavaScript objects Human-readable, uses indentation
Data Types Supports numbers, strings, lists, dictionaries (objects) Supports all JSON types + complex types (e.g., multiline strings)
Syntax Uses {} for objects and [] for lists Uses indentation instead of brackets
Readability Machine-friendly, compact Human-friendly, clean format
Usage APIs, web services, databases Config files (Docker, Kubernetes, Ansible)
Example {"name": "Alice", "age": 30} name: Alice\nage: 30

πŸ“Œ JSON is used for data exchange (APIs, web apps), whereas YAML is used for configuration files.


json.dumps()

json.dumps() converts a Python dictionary into a JSON-formatted string.

import json

data = {"name": "Alice", "age": 30}
json_string = json.dumps(data)
print(json_string)  # Output: {"name": "Alice", "age": 30}

πŸ“Œ Use case: Serializing Python objects for storage, logging, or API responses.


flask.Response in Flask

Response is used to return HTTP responses with custom headers, content, and status codes.

from flask import Flask, Response, jsonify

app = Flask(__name__)

@app.route("/")
def custom_response():
    data = {"message": "Hello, Flask!"}
    response = Response(json.dumps(data), status=200, mimetype="application/json")
    return response

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

πŸ“Œ Use case: When you need to manually control the HTTP response.


flask_restful.Resource

In Flask-RESTful, Resource is a class-based way to define REST API endpoints.

from flask import Flask
from flask_restful import Api, Resource

app = Flask(__name__)
api = Api(app)

class HelloWorld(Resource):
    def get(self):
        return {"message": "Hello, Flask-RESTful!"}

api.add_resource(HelloWorld, "/")

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

πŸ“Œ Use case: Resource makes REST API development structured and reusable.


Final Thoughts


json.dump() vs json.dumps()

Both functions convert Python objects to JSON format, but they differ in their output.

Function Purpose Output
json.dumps() Converts Python object to JSON string Returns a string
json.dump() Writes JSON data to a file Saves directly to a file

Example: json.dump()

Writes JSON data to a file.

import json

data = {"name": "Alice", "age": 30}

with open("data.json", "w") as file:
    json.dump(data, file)  # Saves JSON directly to data.json

πŸ“Œ Use case: Storing JSON data in files.


flask.jsonify()

jsonify() is a Flask helper function that automatically converts Python dictionaries into JSON responses and sets the correct Content-Type (application/json).

Example: jsonify()

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/")
def json_response():
    data = {"message": "Hello, Flask!"}
    return jsonify(data)  # Converts to JSON and sets Content-Type

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

πŸ”Ή Advantages over json.dumps() in Flask:

  1. Automatically sets Content-Type: application/json
  2. Handles Unicode characters correctly
  3. Handles lists and tuples properly

JSON.stringify() (JavaScript)

JSON.stringify() is a JavaScript function that converts a JavaScript object into a JSON string.

Example: JSON.stringify()

const data = { name: "Alice", age: 30 };
const jsonString = JSON.stringify(data);
console.log(jsonString);  // Output: {"name":"Alice","age":30}

πŸ“Œ Use case: Sending JSON data in AJAX requests or APIs.


Summary

Function Language Purpose
json.dumps() Python Converts Python object to JSON string
json.dump() Python Writes JSON data to a file
jsonify() Flask (Python) Converts dictionary to JSON and sets Content-Type
JSON.stringify() JavaScript Converts JavaScript object to JSON string

Sure! In Flask-RESTful, marshal and marshal_with are used for formatting API responses according to a specified structure. They help in ensuring that API responses follow a consistent format.


marshal and marshal_with in Flask-RESTful

marshal

Example:

from flask_restful import marshal, fields

# Define response structure
resource_fields = {
    'id': fields.Integer,
    'name': fields.String,
    'age': fields.Integer
}

# Sample data
data = {'id': 1, 'name': 'John', 'age': 30, 'extra_field': 'Not needed'}

# Use marshal to format data
formatted_data = marshal(data, resource_fields)

print(formatted_data)  # Output: {'id': 1, 'name': 'John', 'age': 30}

Extra fields (extra_field) not mentioned in resource_fields are ignored.


πŸ”Ή marshal_with

Example:

from flask import Flask
from flask_restful import Api, Resource, fields, marshal_with

app = Flask(__name__)
api = Api(app)

# Define response structure
resource_fields = {
    'id': fields.Integer,
    'name': fields.String,
    'age': fields.Integer
}

class User(Resource):
    @marshal_with(resource_fields)  # Apply formatting automatically
    def get(self):
        user_data = {'id': 1, 'name': 'John', 'age': 30, 'extra_field': 'Ignored'}
        return user_data  # No need to call marshal manually

api.add_resource(User, '/user')

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

The response will automatically be:

{
    "id": 1,
    "name": "John",
    "age": 30
}

Difference Between marshal and marshal_with

Feature marshal marshal_with
Type Function Decorator
Usage Called manually Used above a method
When to Use If you need to format multiple responses If formatting is always required for a function's return value