Glossary
Last Updated Feb 20, 2025

Create a Flask API: Step by Step Guide

Nicolas Rios

Table of Contents:

Get your free
API key now
4.8 from 1,863 votes
See why the best developers build on Abstract
START FOR FREE
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
No credit card required

Create a Flask API

APIs rule the digital world, they are the key to complex, reliable software interaction. Basic models can handle data and service requests, process information, interact with servers, and return responses in formats like JSON or XML. Learning how to build APIs is, therefore, no minor task. However, it is not an impossible one. 

Developing APIS using Python Flask, in particular, tends to be more straightforward than creating them with other frameworks. This framework allows for flexibility, simplicity, and swiftness, making it the ideal setup to get started with API development and prototyping. APIs developed with Python typically are RESTful APIs, which follow the principles of REST, a standard architecture for designing networked applications.

Keep on reading to learn everything you need to know about how to build an API using Python Flask from scratch. 

Setting Up the Flask Environment

To build an API using Python Flask, you'll first need the right setup. Developing within a dedicated Flask environment (i.e., a virtual environment that isolates your project) offers several benefits. For instance, it allows you to manage which version of Flask and related packages you use, ensuring consistency across different stages of development. It also helps avoid permission-related issues and mix-ups with other projects on your system.

  1. Step one for creating a Flask environment involves installing Python 3, pip, and Flask. Download Python from the official website, and follow the installation instructions. 

"Pip" (Python’s package installer) should come pre-installed with Python. However, if it doesn’t, you can install pip manually by running the following command:

python3 -m ensurepip --upgrade

  1. Next, use pip to install Flask, the core framework for building web applications in Python, with this command:

pip install flask 

  1. Once Flask is installed, you'll need to set up a virtual environment using tools like venv or pipenv. This will isolate your RESTful API from other projects, making development more organized and streamlined. 

If you are using venv, follow these steps:

  1. Generate a directory for your project:

mkdir Flask_API_Abstract

cd Flask_API_Abstract

*Replace "Flask_API_Abstract" with your actual project name.

  1. Then, create the virtual environment:

python3 -m venv venv

  1. Active the environment. Form macOS/Linux, use this comand:

source venv/bin/activate

For Windows, this one:

venv\Scripts\activate

  1. Once activated, your terminal prompt will show the virtual environment name. For example, if you used venv, it will look like this:

(venv) user@machine:~/path/to/Flask_API_Abstract$ [For macOS/Linux]

(venv) C:\path\to\Flask_API_Abstract> [For Windows]

However, if you prefer using pipenv instead, you can follow these steps:

  1. Install pipenv using the following comand:

pip install pipenv

  1. Build a virtual environment and install Flask:

pipenv install flask

  1. Activate the environment with:

pipenv shell

  1. To complete your Flask setup, the final step is to install necessary dependencies such as the Flask-RESTful extension, which helps build REST APIs quickly. You can install it with this command:

pip install flask-RESTful

Additionally, Flask-JWT-Extended is a useful dependency when building APIs in Flask. It handles authentication and authorization using JSON Web Tokens (JWTs). Install it by running:

pip install Flask-JWT-Extended

  1. Once the setup is finished, you can create a simple Flask application that will form the foundation for your RESTful API using the following command:

from flask import Flask, request, jsonify

app = Flask(__name__)

# In-memory storage for items (acting as a simple database)

items = []

if name == '__main__':

    app.run(debug=True)

Notice that:

  • Flask(__name__) generates a new Flask application.
  • items is a “fake” database where your resources (items) will be stored.
  • request is used to retrieve data sent to the server. 
  • jsonify helps to format data as JSON.

To ensure smooth authentication, you can also execute development tools like Postman for API testing. You can use this tool to send an HTTP request to your Flask application and inspect the response. 

This tool allows you to quickly check if your endpoints are functioning correctly, even before a frontend is developed, and without writing additional code for testing.

Creating RESTful Endpoints

RESTful APIs operate on a client-server principle, where the client makes an API call to the server to interact with specific resources stored on the server. These resources are typically represented in JSON or XML formats.

The server processes the request and returns a response, either with the requested data in JSON or XML format or with an HTTP status code indicating that an action has been performed successfully.

Consequently, once the proper Flask environment is set up, the next step is to create RESTful endpoints. API endpoints are specific URLs that handle requests to perform operations on resource items, such as retrieving or modifying data. Each endpoint corresponds to a specific function or route in your application.

  1. To create endpoint routes, first define the paths that users will access through each request. In RESTful APIs, requests follow the HTTP protocol. To associate a URL path with a function that handles the request and defines an endpoint, use the @app.route() decorator. A simple HTTP route definition looks like this:

from flask import Flask

app = Flask(__name__)

@app.route('/')

def home():

    return "Welcome to the home page!"

if name == '__main__':

    app.run(debug=True)

Here @app.route('/') defines the route for the root URL (/). When accessed, the home() function will be executed, returning a "Welcome" message if successful.

  1. Keep in mind that in RESTful APIs, HTTP request methods perform operations on resources. Most common methods include: GET (retrieves data from the server), POST (sends data to the server), PUT (updates an existing resource), and DELETE (removes a resource from the server). 

To define routes that handle these methods, you can use Flask’s @app.route() with the appropriate methods parameter. For example:

from flask import Flask, request, jsonify

# Initialize the Flask app with a custom name

app = Flask("Flask API Abstract")

# Sample data to act as a database

items = []

# Endpoint to get all items

@app.route('/items', methods=['GET'])

def get_items():

    return jsonify(items)

# Endpoint to add a new item

@app.route('/items', methods=['POST'])

def add_item():

    item = request.get_json()  # Get JSON data from the request

    items.append(item)

    return jsonify(item), 201  # Respond with the item and status code 201

# Endpoint to update an existing item by its ID

@app.route('/items/<int:item_id>', methods=['PUT'])

def update_item(item_id):

    item = request.get_json()

    if 0 <= item_id < len(items):

        items[item_id] = item

        return jsonify(item)

    return "Item not found", 404

# Endpoint to delete an item by its ID

@app.route('/items/<int:item_id>', methods=['DELETE'])

def delete_item(item_id):

    if 0 <= item_id < len(items):

        deleted_item = items.pop(item_id)

        return jsonify(deleted_item)

    return "Item not found", 404

# Run the Flask application

if name == '__main__':

    app.run(debug=True)

  1. Rather than hardcoding URLs, you can use Flask’s url_for function to dynamically generate URLs based on function names. This will also ensure that URL remain accurate even if you change route names. 

Here’s how to generate URLs for GET and PUT requests:

from flask import Flask, url_for

# Initialize Flask app with custom name

app = Flask("Flask API Abstract")

@app.route('/items', methods=['GET'])

def get_items():

    return "Get all items"

@app.route('/items/<int:item_id>', methods=['GET'])

def get_item(item_id):

    return f"Get item with ID {item_id}"

@app.route('/items/<int:item_id>', methods=['PUT'])

def update_item(item_id):

    return f"Update item with ID {item_id}"

# Testing URL generation using url_for

with app.test_request_context():

    # Dynamically generated URLs

    print("URL for get_items:", url_for('get_items'))  # Generates URL for retrieving all items

    print("URL for get_item with item_id=1:", url_for('get_item', item_id=1))  # Generates URL for getting item with ID 1

    print("URL for update_item with item_id=2:", url_for('update_item', item_id=2))  # Generates URL for updating item with ID 2

# Run the app

if name == '__main__':

    app.run(debug=True)

  1. AAfter running your Flask app, you can test the endpoints using Postman. Set the request type and enter the local Flask app URL: http://127.0.0.1:5000/. Then:
    1. Use GET on /items to retrieve all resources: http://127.0.0.1:5000/items.
    2. Use POST on /items to create a new item. Do not forget to include JSON data in the body (e.g., {"name": "Item 1", "price": 10}): http://127.0.0.1:5000/item.
    3. Use PUT on /items/<item_id> to update a resource. Also remember to include JSON data in the body (e.g., {"name": "Updated Item", "price": 20}): http://127.0.0.1:5000/items/<item_id>
    4. Use DELETE on /items/<item_id> to remove an item from the server: http://127.0.0.1:5000/items/<item_id>.

Using Flask-RESTful for API Development

The Flask-RESTful extension can be used to add support for building APIs with Python, simplifying development by providing a structured approach that retains Flask’s flexibility. Promptly, it streamlines the process by offering:

  • Built-in support for handling HTTP request methods for each resource class. Its resource-based design allows you to map resources directly to their corresponding HTTP methods using add_resource, promoting a uniform and intuitive API interface as it grows.
  • Automatic parsing of incoming JSON data using native tools like reqparse, ensuring input is properly validated and cleaned without needing to manually handle request.form or request.get_json() in each route. Besides saving time, this helps protect your API from external attacks and bugs.
  • Customizable error messages for handling mistakes in a structured way. It also supports standardized and customized  API responses, including status codes. This provides a consistent experience to API users that makes the software easier to debug and fosters scalability.
  • Definition of API declarative resources (i.e., objects that represent API endpoints) as Python classes, keeping code clean and organized. Each resource is self-contained, making it easier to maintain, extend, and scale your API over time.
  • Native support for RESTful principles, including a uniform HTTP interface, client-server architecture (with the server handling requests/responses and the client consuming the API), and stateless servers, all of which improve the API’s scalability and reliability.

Since Flask is a micro-framework, rather suited for small to medium-sized APIs that don’t require large-scale frameworks or support high traffic, the Flask-RESTful extension enables you to build fully scalable APIs without sacrificing Flask flexible, simple frame. 

In addition to these features (which foster scalability through consistency, resource structuring, and security), Flask-RESTful integrates seamlessly with extensions and tools, such as pagination, rate-limiting CORS, and authentication support, allowing you to customize your API to meet your specific needs and to successfully scale it.

How to Create a Resource Class

In resource classes, each resource’s functionalities (i.e., the HTTP methods it supports) are grouped together as self-contained operations within the class. This modularity simplifies API maintenance: if there’s an issue with handling a specific request for a resource, you can easily locate and address it. The same principle applies to scalability and testing.

Resource classes also streamline API procedures. They enable you to define helper methods (code snippets that share common logic) to be reused within the class. Additionally, classes support inheritance, allowing you to create base resource classes with common functionalities that can be extended to child classes. Both approaches help avoid redundancies, ensuring clear, easy-to-read code.

Simply put, Python resource classes are essential for enhancing the organization and functionality of any API built with Flask. Therefore, knowing how to create them is a must for every developer. 

Let’s break down the process step by step, to give you a clearer idea of how to proceed.

  1. To create a resource class, you'll first need to install Flask-RESTful, create a Flask API, and initialize the extension to set up the foundation of your API. You can do this with the following commands:

pip install Flask-RESTful # install Flask-RESTful

from flask import Flask

from flask_restful import Resource, Api

app = Flask("Flask API Abstract")

api = Api(app) # initialize Flask

  1. The next step is defining the ItemResource class, a resource class that inherits from Flask-RESTful’s Resource. It encapsulates the logic for handling operations related to items in one place, as opposed to generic resource classes that handle all types of data. This makes your code more organized and easier to understand.

Defining this class establishes how the API will interact with a given set of items using specific HTTP methods, in response to incoming requests. To define a simple ItemResource that handles GET and POST requests use this command:

from flask_restful import Resource

class ItemResource(Resource):

    # Define the GET method to retrieve data

    def get(self):

        return {'message': 'This is a GET request for an item'}

    # Define the POST method to create new data

    def post(self):

        return {'message': 'This is a POST request to create an item'}, 201

  1. Once you have defined the ItemResource class, you’ll have to map it to a specific URL endpoint using the add_resource() method provided by Flask-RESTful. By doing so, you’ll instruct Flask how to route requests to the appropriate resource class:

api.add_resource(ItemResource, '/item)

The previous command enables Flask to route GET and POST requests sent to /item to the ItemResource class. 

For example, to handle requests related to users based on their unique user_id, the command would look like this: 

api.add_resource(UserResource, '/users/<int:user_id>')

  1. The final step in creating the class is running the Flask app to start your server. To do so, use the following script:

if name == '__main__':

    app.run(debug=True)

If properly implementd, the server should respond with a JSON message when you visit http://127.0.0.1:5000/item and send a GET or POST request.

  1. You can further extend the ItemResource class to handle additional HTTP requests, such as PUT and DELETE using the following command:

class ItemResource(Resource):

    def get(self):

        return {'message': 'This is a GET request for an item'}

    def post(self):

        return {'message': 'This is a POST request to create an item'}, 201

    def put(self):

        return {'message': 'This is a PUT request to update an item'}

    def delete(self):

        return {'message': 'This is a DELETE request to remove an item'}

API Authentication and Security

No API is fully complete without proper security measures against breaches and bugs. This is particularly relevent if you’re building a Python Flask API that will handle sensitive data (mainly, private user information). 

Some key measures to consider include using API keys, JWT tokens, or Auth0 for authentication (with token revocation strategies for enhanced security), as well as securing endpoints with Flask-HTTPAuth and @login_required decorators. Let’s walk through how to implement each of these.

API Key Authentication

API keys are unique identifiers assigned to each client of an API. To authenticate a request, the client must include the API key in the Authorization header. The server checks whether the provided key is valid and associated with an authorized user or client.

For example, the client will send the API key as follows:

Authorization: Bearer <API_KEY>

To implement basic API key authentication in your Flask API, you can use the following code:

from flask import Flask, request, jsonify

app = Flask(__name__)

# Store a secret API key (ensure this is stored securely)

API_KEY = "your_secret_api_key"

@app.route('/protected', methods=['GET'])

def protected():

    api_key = request.headers.get('X-API-KEY')  

    if api_key == API_KEY:

        return jsonify({"message": "This is a protected endpoint!"}), 200

    else:

        return jsonify({"message": "Unauthorized"}), 401

if name == '__main__':

    app.run(debug=True)

Additionally, to store API keys securely, along with related user information, you can use SQLAlchemy. For each incoming request, the API will retrieve the key from the database and check if the provided key matches the stored one. Here's how to store an API key in your database:

from your_app import db

from models import ApiKey

def create_api_key(user_id):

    key, hashed_key = generate_api_key() # Generate key (ensure proper security measures)

    # Create a new API key record in the database

    new_api_key = ApiKey(key=hashed_key, user_id=user_id)

    db.session.add(new_api_key)

    db.session.commit()  

    return key  # Return the plain-text key to the user (use with caution in a real-world scenario)

Note: It's advisable to hash the API key before storing it to mitigate security risks. The plain-text key should only be returned for immediate use, such as for initial setup.

Token Authentication With JWT

Another way to authenticate API calls is by using JSON Web Tokens (JWTs). These compact, URL-safe tokens securely carry user information and are widely used in web applications. 

The process works as follows: whenever a user logs in successfully, a JWT is issued. The client must then include this token in the Authorization header with every API request to authenticate the call:

Authorization: Bearer <JWT>

JWTs have an expiration time to help prevent identity fraud. Additionally, they can be signed using HMAC or RSA to ensure the token’s integrity and that it hasn’t been tampered with.

To implement JWT authentication in your Python-Flask API, follow this approach:

import jwt

from flask import jsonify, request

from datetime import datetime, timedelta

SECRET_KEY = "your_secret_key" # Ideally, store this securely

def generate_token(user_id):

    payload = {

        'exp': datetime.utcnow() + timedelta(hours=2), # Token expiration time

        'iat': datetime.utcnow(), # Issuance time

        'sub': user_id # Subject (user) for whom the token was issued

    }

    return jwt.encode(payload, SECRET_KEY, algorithm='HS256') # Sign the token with the secret key

@app.route('/login', methods=['POST'])

def login():

    # Assuming user authentication is successful

    token = generate_token(user.id) # Generate JWT for authenticated user

    return jsonify({"token": token}), 200

@app.route('/protected', methods=['GET'])

def protected():

    token = request.headers.get('Authorization').split()[1] # Extract token from the Authorization header

    try:

        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) # Decode and verify token

        user_id = payload['sub'] # Get the user ID from the token

        # Proceed with the request if token is valid

    except jwt.ExpiredSignatureError:

        return jsonify({"error": "Token expired"}), 401 # Handle expired token

    except jwt.InvalidTokenError:

        return jsonify({"error": "Invalid token"}), 401 # Handle invalid token

Third-Party Authentication With Auth0

A third measure to securing your API is integrating third-party authentication, such as Auth0. This solution simplifies user management by offloading authentication tasks.

To use Auth0 in your Flask API, you'll first need to register an Auth0 account and configure your API to validate tokens issued by Auth0 using their public keys. 

Here's how you can integrate Auth0 into your Flask API:

from flask import Flask, jsonify

from flask_jwt_extended import JWTManager, jwt_required, create_access_token

from authlib.integrations.flask_client import OAuth

app = Flask(__name__)

# Set up Auth0 client configuration

app.config['SECRET_KEY'] = 'your_secret_key'

app.config['JWT_SECRET_KEY'] = 'your_jwt_secret'

oauth = OAuth(app)

auth0 = oauth.register(

    'auth0',

    client_id='your_client_id',

    client_secret='your_client_secret',

    authorize_url='https://your-auth0-domain/authorize',

    authorize_params=None,

    access_token_url='https://your-auth0-domain/oauth/token',

    refresh_token_url=None,

    api_base_url='https://your-auth0-domain/api/v2/',

    client_kwargs={'scope': 'openid profile email'},

)

@app.route('/login', methods=['GET'])

def login():

    redirect_uri = url_for('auth', _external=True)

    return auth0.authorize_redirect(redirect_uri)

@app.route('/auth')

def auth():

    token = auth0.authorize_access_token()

    user_info = auth0.parse_id_token(token)

    return jsonify(user_info)

if name == '__main__':

    app.run(debug=True)

Once the integration is complete, Auth0 will handle the authentication process. The client will be redirected to Auth0 to log in, and Auth0 will return a JWT token. The client must then include this token in the Authorization header as Bearer <token> with every subsequent request to access protected routes.

Flask-HTTPAuth and @login_required Decorators to Secure Endpoints

Another way to secure your Flask API is by using the Flask-HTTPAuth extension, which supports both HTTP basic authentication and token-based authentication.

HTTP Basic Authentication requires users to send their username and password in the Authorization header, encoded in base64. It's crucial to use this method over HTTPS to prevent credentials from being exposed during transmission. 

On the other hand, token offers a more secure and scalable solution. The process is similar to basic authentication, as the token must be sent in the Authorization header as follows:

Authorization: Bearer <TOKEN>. 

Additionally, you can enhance security by combining authentication methods with Flask-Login @login_required decorator, which restricts access to certain endpoints. Using both methods helps build an API that supports session management, user login tracking, and secure access control.

To use Flask-HTTPAuth and the @login_required decorator, you first need to install the necessary packages:

pip install Flask-HTTPAuth # For HTTPAuth integration

pip install Flask-Login # For Flask-Login decorators

Afterwards, you’ll have to set up basic HTTP authentication. Here’s an example of how to do it, while using the @auth.login_required decorator to protect specific endpoints:

from flask import Flask, jsonify

from flask_httpauth import HTTPBasicAuth

app = Flask(__name__)

auth = HTTPBasicAuth()

# Example user data (in a real app, query the database)

users = {

    "admin": "password123",  # username: password

    "user1": "mysecret"

}

# Verify the username and password

@auth.verify_password

def verify_password(username, password):

    if username in users and users[username] == password:

        return username  # Return the username (or user object) if valid 

    return None  # Return None if authentication fails

# Protect this endpoint with HTTP Basic Auth

@app.route('/secure-basic', methods=['GET'])

@auth.login_required  # This ensures the user must be authenticated, protecting the route

def secure_basic():

    return jsonify({"message": f"Hello, {auth.current_user()}! You are authenticated."}), 200

if name == '__main__':

    app.run(debug=True)

Alternatively, you can use token-based authentication to secure your API endpoints. Here’s an example:

from flask import Flask, jsonify, request

from flask_httpauth import HTTPTokenAuth

app = Flask(__name__)

auth = HTTPTokenAuth(scheme='Bearer')  # Use 'Bearer' for token authentication

# Example user tokens (in a real app, store these in a database)

tokens = {

    "token123": "user1",  # token: username

    "token456": "admin"

}

# Verify the token

@auth.verify_token

def verify_token(token):

    if token in tokens:

        return tokens[token]  # Return the associated user if valid

    return None  # Return None if the token is invalid

# Protect this endpoint with Token Authentication

@app.route('/secure-token', methods=['GET'])

@auth.login_required # Protect the route with token authentication

def secure_token():

    return jsonify({"message": f"Hello, {auth.current_user()}! You are authenticated with a token."}), 20z

if name == '__main__':

    app.run(debug=True)

Token Revocation Strategies

While highly effective, tokens are not foolproof. Without proper safeguards, they can be vulnerable to misuse, particularly after a user logs out or if their account is compromised. To prevent this, it's crucial to implement token revocation mechanisms.

Here are three common strategies to protect against invalid token misuse:

  • Blacklist revoked tokens. Store revoked tokens in a database, and check them for each incoming API request. This can be achieved with the following code:

from flask import jsonify

from models import RevokedToken  # Assuming a table for revoked tokens

@app.route('/revoke_token', methods=['POST'])

def revoke_token():

    token = request.headers.get('Authorization').split()[1]

    revoked_token = RevokedToken(token=token)

    db.session.add(revoked_token)

    db.session.commit()

    return jsonify({"message": "Token revoked"}), 200

To protect the endpoints, you can also verify tokens against the database:

@app.route('/protected', methods=['GET'])

def protected():

    token = request.headers.get('Authorization').split()[1]

    if db.session.query(RevokedToken).filter_by(token=token).first():

        return jsonify({"error": "Token has been revoked"}), 401

    # Proceed with request

  • Setting short expiration times. By limiting the lifespan of tokens, you ensure that they must be refreshed frequently. For instance, with JWT:

from flask import Flask, jsonify, request

import jwt

from datetime import datetime, timedelta

app = Flask(__name__)

SECRET_KEY = "super_secret_key"

def generate_token(user_id):

    payload = {

        'exp': datetime.utcnow() + timedelta(minutes=15),  # Token expires in 15 minutes

        'iat': datetime.utcnow(),  # Issued at time

        'sub': user_id  # User ID

    }

    return jwt.encode(payload, SECRET_KEY, algorithm='HS256')

@app.route('/login', methods=['POST'])

def login():

    user_id = request.json.get('user_id')

    if user_id:

        token = generate_token(user_id)

        return jsonify({"token": token}), 200

    return jsonify({"error": "Invalid credentials"}), 401

@app.route('/protected', methods=['GET'])

def protected():

    auth_header = request.headers.get('Authorization')

    if auth_header:

        try:

            token = auth_header.split()[1]

            payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])

            user_id = payload['sub']

            return jsonify({"message": f"Hello, {user_id}! Token is valid."}), 200

        except jwt.ExpiredSignatureError:

            return jsonify({"error": "Token expired. Please re-authenticate."}), 401

        except jwt.InvalidTokenError:

            return jsonify({"error": "Invalid token. Please provide a valid token."}), 401

    return jsonify({"error": "Authorization header missing"}), 401

if name == '__main__':

    app.run(debug=True)    

    return jsonify({"error": "Authorization header is missing"}), 401

if name == '__main__':

    app.run(debug=True)

  • Issuing refresh tokens. Issue short-lived access tokens and long-lived refresh tokens. When an access token expires, the client can use the refresh token to obtain a new one. This strategy ensures users can refresh their access tokens without re-authenticating every time:

from flask import Flask, jsonify, request

import jwt

from datetime import datetime, timedelta

app = Flask(__name__)

SECRET_KEY = "super_secret_key"

REFRESH_SECRET_KEY = "super_refresh_secret_key"

def generate_tokens(user_id):

    access_token = jwt.encode({

        'exp': datetime.utcnow() + timedelta(minutes=15),  # Access token expires in 15 minutes

        'iat': datetime.utcnow(),

        'sub': user_id

    }, SECRET_KEY, algorithm='HS256')

    refresh_token = jwt.encode({

        'exp': datetime.utcnow() + timedelta(days=30),  # Refresh token expires in 30 days

        'iat': datetime.utcnow(),

        'sub': user_id

    }, REFRESH_SECRET_KEY, algorithm='HS256')

    return access_token, refresh_token

@app.route('/login', methods=['POST'])

def login():

    user_id = request.json.get('user_id')

    if user_id:

        access_token, refresh_token = generate_tokens(user_id)

        return jsonify({"access_token": access_token, "refresh_token": refresh_token}), 200

    return jsonify({"error": "Invalid credentials"}), 401

Representing Data as JSON

A Flask API built with Python typically displays resources in Python’s native data structures. However, before it reaches the client, it’s advisable to convert the data into JSON, a lightweight, text-based format, both human-readable and machine-readable.

user_data = {

    "id": 1,

    "email": "user@example.com"

} #Python Dictionary

{

    "id": 1,

    "email": "user@example.com"

} #JSON representation

Using JSON standarized responses for resource representation helps you reduce ambiguity, minimize the amount of data transmitted (JSON is a compact format), and simplifies handling information. 

Therefore, let’s review how to set up your Flask API to automatically convert data into JSON format when issuing a response.

Set Content-Type Header to Application/json

First of all, you’ll have to configure your Flask API to handle and return JSON data. In other words, that the Content-Type header is set to application/json. 

Flask’s jsonify() function should do this automatically. However, if it fails to do so or if you are not using jsonify(), you can run this code to set Content-Type to application/json:

from flask import Flask, make_response, json

app = Flask(__name__)

@app.route('/api/data')

def get_data():

    data = {"message": "Hello, World!"}

    response = make_response(json.dumps(data))  # Convert data to JSON

    response.headers['Content-Type'] = 'application/json'  # Set Content-Type manually

    return response

if name == "__main__":

    app.run()

Normally, jsonify() would guarantee proper conversion and the correct response header, so, if you are not using it, remember to make sure that the data returned in the response is a serializable format (dictionaries, lists, or simple data types).

Additionally, for better results, you can use to_dict() methods to make Python resources JSON-friendly, and then convert them to JSON with jsonify(). This approach simplifies the conversion process and helps reduce the margin for errors.

Use Request.get_json() to Parse API Calls

The second step would be using request.get_json() to set your API to parse incoming JSON API payloads from POST or PUT requests. This will help you handle potential errors if, for example, the request body is not in JSON format.

To have a Flask API process incoming JSON requests, but reject payloads in other formats, use the following code:

from flask import request

@app.route('/api/submit', methods=['POST'])

def submit_data():

    try:

        data = request.get_json()  # Parses incoming JSON data

        if not data:

            return jsonify({"error": "Invalid JSON"}), 400

        # Process the data

        return jsonify({"message": "Data received successfully!"}), 200

    except Exception as e:

        return jsonify({"error": str(e)}), 400

When using the request.get_json() for parsing incoming data, some best practices you can apply for better results include: 

  • Gracefully handling errors. Specify how to handle exceptions when issuing the request.get_json(). The adequate is to provide a clear error response with a relevant HTTP status code (such as 400 Bad Request) if, for example the incoming request body lacks a JSON payload or contains invalid JSON.
  • Limit input size. Setting limits on the maximum allowable size will help you prevent Denial-of-Service (DoS) attacks caused by excessively large inputs. This will come especially at hand if you expect large JSON payloads.
  • Check for required fields. Tailor the code above to validate that all required fields (e.g., name, email) are present in the incoming data. If there’s missing data, return an appropriate error message specifying the missing field.
  • Handle optional fields. That is, define how optional fields (e.g. email, telephone) are to be handled. It’s advidable to manage the absence of those fields in the API logic. For instance, you can instruct the API to skip certain operations when optional data isn't provided, or establish default values (e.g. “None”).

Structure JSON Responses 

APIs (and humans) thrive on predictability. Structuring your JSON responses according to industry standards is, therefore, an essential step when creating an API using Python Flask. 

On the one hand, a well-structured response is easier to parse, which reduces the chances of errors on the frontend side. This is especially important when dealing with sensitive data. Structuring API resources ensures that only necessary information is sent to the client, minimizing the risk of data exposure.

On the other hand, a well-structured JSON API is significantly simpler to manage, scale, and maintain. Besides, a clear structure facilitates integration with client-side applications, as these often require a predictable format to efficiently parse and process the data.

To structure JSON responses with to_dict() and to_collection_dict(), use the following code:

from flask import Flask, jsonify

app = Flask(__name__)

# Example Item class with to_dict() method

class Item:

    def init(self, name, price):

        self.name = name

        self.price = price

    def to_dict(self):

        return {"name": self.name, "price": self.price}

    # to_collection_dict() converts a collection of Item objects to a list of dictionaries

    @staticmethod

    def to_collection_dict(items):

        return [item.to_dict() for item in items]

@app.route('/api/items')

def get_items():

    items = [Item("Item 1", 20), Item("Item 2", 25)]

    # Use to_collection_dict() to convert the list of Item objects into a list of dictionaries

    return jsonify(Item.to_collection_dict(items))  # Structured JSON response

if name == "__main__":

    app.run()

If successfully implemented, this will convert Python objects into JSON-friendly dictionaries. Keep in mind that the to_dict() method is used for individual objects, while the to_collection_dict() is used with collections (i.e., groups of objects treated like as a unit).

To achieve high-quality results, here are some best practices you can apply when building an API using Python Flask:

  • Simplify conversion.  Structure only the necessary fields, excluding sensitive or unnecessary internal data. This keeps the API data clean and simple. Remember to use a to_dict() method for individual resources.
  • Limit the depth of nesting. "Nesting" refers to embedding one JSON object or array within another to represent resource hierarchies. However, excessive nesting can hinder readability and ease of use.
  • Use a predictable structure. All responses should follow a consistent schema, which streamlines processes and prevents parsing mistakes. 
  • Provide additional metadata. Including information such as status codes, timestamps, or versioning improves the context of responses with complex data or collections, enhancing API usability.

Pagination Logic

If you're building an API with Python Flask, it's worth doing it right. Implementing pagination logic in JSON responses is the cherry on top that ensures your API is well-organized and scalable. 

To start with, pagination helps structure your API resources more effectively. It works like an index, allowing users and applications to load only the specific information they need. By dividing data into smaller, manageable chunks, pagination prevents clients from having to retrieve entire datasets at once, improving performance.

However, there are further advantages to using pagination. Notably, it enhances the scalability and responsiveness of your API by controlling how much data is sent at one time. This also helps avoid memory overload, making the API more efficient.

To implement pagination, there are two key steps: first, add pagination parameters (e.g., page and limit) to your API endpoints. This controls the size of data returned to the client.

Once this is done, structure the JSON response to include pagination details like total_items, total_pages, and current_page, along with the actual paginated data. Do not forget to include the paginated data in the command. 

The following code demonstrates how to return paginated data with relevant metadata:

@app.route('/api/items')

def get_paginated_items():

    items = [{"name": f"Item {i}", "price": 20 + i} for i in range(1, 101)]   

    # Pagination logic

    page = int(request.args.get('page', 1))

    limit = int(request.args.get('limit', 10))

    start = (page - 1) * limit

    end = start + limit 

    paginated_items = items[start:end]

    return jsonify({

        "total_items": len(items),

        "total_pages": (len(items) // limit) + 1,

        "current_page": page,

        "data": paginated_items

    })

Some best practices that can help you obtain quality results include:

  • Include next/previous page links. This allows users to navigate through paginated data without having to manually constructURLs
  • Provide pagination metadata. Enhance the user experience by including details like the total number of records, total pages, current page, and number of items per page.
  • Ensure consistent response structure. Maintaining a uniform structure across pages simplifies client-side processing, ensuring a smooth user experience.
  • Control response size. void overwhelming clients with too much data. Pagination can help you control the response size, breaking data into manageable blocks using parameters like page and limit.

Data Serialization with Marshmallow

Although Flask’s jsonify() natively converts your API’s resources into JSON, other tools offer more advanced means to serialize and structure data for external systems. One such tool is Marshmallow, a versatile Python library whose functionalities extend beyond mere serialization.

Marshmallow allows you to not only serialize data but also deserialize it—converting JSON data into Python data structures. More importantly, it helps you define schemas that validate and organize your data, ensuring that it adheres to specific rules before and after serialization. 

Additionally, Marshmallow supports nested data structures, customized fields, and complex transformations, which are essential for handling more intricate API responses.

Take into account that while Flask’s jsonify() ensures that the data is formatted correctly as JSON, it does not verify the semantic correctness of the data itself. Marshmallow steps in to fill this gap, ensuring your data is both correctly formatted and validated according to your predefined schema.

Now, let’s dive into how you can use Marshmallow to validate and serialize Python objects into JSON-compatible formats (like lists and dictionaries), as well as how to deserialize them effectively.

  1. Install Marshmallow using the following Flask command:

pip install marshmallow

  1. Set up a Marshmallow schema by creating a class that defines the structure of the data you will serialize or deserialize. If you're working with SQLAlchemy models, you can use schemas like SQLAlchemySchema or SQLAlchemyAutoSchema to simplify the integration. Within the schema, you can define custom behaviors for handling your API’s data, such as:
    1. Fields. Specify the fields you want Marshmallow to include in your API resources (e.g. name, email, age). These fields define the structure of the schema.
    2. Validation. Apply validation rules to ensure data integrity. For instance, you can mark fields as required or enforce specific constraints using validators like validate.Length.
    3. Serialization. This enables the schema to convert Python objects into JSON format, preparing the data for external use.
    4. Deserialization. This allows the schema to parse incoming JSON data and convert it back into Python objects for internal processing.

from marshmallow import Schema, fields, validate

class UserSchema(Schema):

    # Define the fields for serialization and validation

    id = fields.Int(required=True, description="User ID")

    name = fields.Str(required=True, validate=validate.Length(min=1, max=100), description="User name")

    email = fields.Email(required=True, description="User email")

    age = fields.Int(required=True, validate=validate.Range(min=0), description="User age")

# Example usage to serialize data

user_schema = UserSchema()

user_data = {

    "id": 1,

    "name": "John Doe",

    "email": "johndoe@example.com",

    "age": 30

}

  1. Validate API input to ensure that incoming API request data is valid before further processing. This helps guarantee that the input data is both correctly formatted and meets specific business requirements. You can use a Marshmallow schema to define validation rules. 

For instance, let’s assume you have a simple User model with fields such as name, email, and age. In addition to verifying the general correctness of the data, you want to ensure that the user is at least 18 years old. In this case, you can define and use your schema as follows:

from marshmallow import Schema, fields, validate, ValidationError

# Define the schema

class UserSchema(Schema):

    name = fields.Str(required=True, validate=validate.Length(min=1), description="User name")

    email = fields.Email(required=True, description="User email")

    age = fields.Int(required=True, validate=validate.Range(min=18), description="User age")

# Create a UserSchema instance

user_schema = UserSchema()

# Example user data (John Doe)

user_data = {

    "name": "John Doe",

    "email": "johndoe@example.com",

    "age": 30  # Age is above 18, so it passes validation

}

# Validate before serializing

try:

    # Validate input data before any further processing

    validated_data = user_schema.load(user_data)

    print("Validated data:", validated_data)

    # Now serialize the validated data to JSON-compatible format

    serialized_data = user_schema.dump(validated_data)

    print("Serialized data:", serialized_data)

except ValidationError as err:

    # Handle validation errors

    print("Validation errors:", err.messages)

If the data is invalid, the load() method will raise a ValidationError you can catch and return as an “error message” to the client. However, if data is valid, you can save it to the database using SQLAlchemy’s methods add() and commit().

  1. Serialize Python objects by converting them to JSON-compatible formats. One reliable way to achieve this is by using Marshmallow’s dump() method, especially when combined with schemas for validation and structuring the data. Here’s an example:

# Example User data (Python object)

user_data = {"name": "John Doe", "email": "john@example.com", "age": 30}

# Create a UserSchema instance

user_schema = UserSchema()

# Serialize the data

result = user_schema.dump(user_data)

print(result)

# JSON-serialized data (as a dictionary)

{

  "name": "John Doe",

  "email": "john@example.com",

  "age": 30

}

  1. Deserialize API request data into Python objects using Marshmallow’s load() method. Deserialization allows your API to process incoming JSON data and transform it into usable Python objects. Here’s an example of how to achieve this:

from flask import Flask, request, jsonify

app = Flask(__name__)

# Define a simple UserSchema for deserialization class UserSchema(Schema): 

name = fields.Str(required=True, validate=validate.Length(min=1)) 

email = fields.Email(required=True) 

age = fields.Int(required=True, validate=validate.Range(min=18))

@app.route('/api/users', methods=['POST'])

def create_user():

    # Get JSON data from request body

    json_data = request.get_json()

    # Create a UserSchema instance for deserialization

    user_schema = UserSchema()

    try:

        # Deserialize JSON into a Python object

        user_data = user_schema.load(json_data) 

        # If the data is valid, you can now use the user_data

        # For example, save it to the database (not implemented here)        

        return jsonify(user_data), 201    

    except ValidationError as err:

        # If validation fails, return the errors

        return jsonify(err.messages), 400

if name == '__main__':

    app.run(debug=True)

For further clarification, here’s a code example that illustrates the complete process of data serialization using Marshmallow:

from flask import Flask, request, jsonify

from marshmallow import Schema, fields, validate, ValidationError

# Define the Marshmallow Schema for user data

class UserSchema(Schema):

    name = fields.Str(required=True, validate=validate.Length(min=1))

    email = fields.Email(required=True)

    age = fields.Int(required=True, validate=validate.Range(min=18))

app = Flask(__name__)

# Example in-memory user database, including John Doe as default user

users_db = [

    {"name": "John Doe", "email": "johndoe@example.com", "age": 30}

]

# Route to handle POST requests for creating a new user

@app.route('/api/users', methods=['POST'])

def create_user():

    # Get the incoming JSON data from the request body

    json_data = request.get_json()

    # Initialize the schema for deserialization and validation

    user_schema = UserSchema()

    try:

        # Deserialize and validate the incoming data

        user_data = user_schema.load(json_data)

        # If valid, save to in-memory database (for the sake of the example)

        users_db.append(user_data)

        # Serialize the data to return a response

        return jsonify(user_schema.dump(user_data)), 201

    except ValidationError as err:

        # If validation fails, return the error messages

        return jsonify(err.messages), 400

#  Route to handle GET requests for retrieving the list of users

@app.route('/api/users', methods=['GET'])

def get_users():

    user_schema = UserSchema(many=True)

    return jsonify(user_schema.dump(users_db))

if name == '__main__':

    app.run(debug=True)

Error Handling in APIs

Errors are inevitable—they are just part of the development process. However, when properly managed, they don’t have to significantly impact the performance or reliability of your API. The key to maintaining a stable API? Incorporating robust error-handling strategies right from the start. 

Let’s break down some essential techniques for handling errors in your Python Flask API:

  • Lifesaving tip n°1: To ensure your API is machine-friendly, always issue error messages in a JSON format. This allows clients to easily parse error details and handle them appropriately. A well-structured error response should include the following key information:
    • Message identifying the error.
    • Description of the issue.
    • HTTP status code
    • A more detailed error code or identifier (optional).

Use the error_response function to format these error messages in JSON. This function leverages jsonify to serialize the Python dictionary into JSON, and to guarantee that the message is properly formatted. It also ensures the correct HTTP status code is returned.

For example, when a requested resource is not found, the API should return the following JSON response:

{

    "error": {

        "status_code": 404,

        "message": "Resource not found"

    }

}

Content negotiation—i.e., allowing the server to return responses in different formats based on the client's preferences—can be implemented in your Flask API by inspecting the Accept header in the request. 

However, it’s generally recommended to prioritize JSON, as it is widely used in REST APIs, and offers a standard, machine-readable format that ensures better compatibility across platforms.

  • Lifesaving tip n°2: To enhance your Flask API’s error handling, use Flask’s errorhandler decorator to register custom error handlers. This will allow you to catch specific HTTP errors (e.g., 400 Bad Request or 404 Not Found), and customize the error messages and responses accordingly.

By using this approach, your Flask API will route specific errors to their designated handlers. These handlers will use the error_response function to return a JSON-formatted error message with the appropriate HTTP status code. Here’s an example of how to set this up:

python

CopiarEditar

from flask import Flask

app = Flask(__name__)

# Handle 404 Not Found errors

@app.errorhandler(404)

def not_found_error(error):

    # Return a JSON-formatted response for 404 errors

    return error_response(404, "Resource not found")

# Handle 500 Internal Server errors

@app.errorhandler(500)

def internal_error(error):

    # Return a JSON-formatted response for 500 errors

    return error_response(500, "Internal server error")

By implementing this code, your API will return machine-friendly error messages like this when a 500 Internal Server Error occurs: 

{

    "error": {

        "status_code": 500,

        "message": "Internal server error"

    }

}

For the 404 Not Found error, refer to the previous tip for the example response

  • Lifesaving tip n°3: To ensure your Flask API returns the correct HTTP status codes, you can use the errorhandler decorator. 

However, another approach is to manually control error responses within your route logic. This allows you to define custom error messages and set the appropriate HTTP status code using jsonify.

 For example, if you want your Flask API to cover 400, 404, and 405 HTTP errors, you can implement the following code:

from flask import Flask, jsonify, request

app = Flask(__name__)

# Function to generate custom error responses

def error_response(status_code, message=None):

    response = {

        "error": {

            "status_code": status_code,

            "message": message or "An error occurred"

        }

    }

    return jsonify(response), status_code

# Example route with manual error handling

@app.route("/items/<int:item_id>", methods=["GET"])

def get_item(item_id):

    # Simulating a check for a valid item_id

    if item_id <= 0:

        # Return a 400 Bad Request if the item_id is invalid

        return error_response(400, "Item ID must be greater than 0") 

    # Simulating data fetching (replace with actual database query or logic)

    items_db = {1: "Item 1", 2: "Item 2", 3: "Item 3"}

    item = items_db.get(item_id)

    if not item:

        # Return a 404 Not Found if the item doesn't exist in the database

        return error_response(404, f"Item with ID {item_id} not found")    

    # If no errors, return the item

    return jsonify({"item_id": item_id, "item_name": item})

# Example of a route with an internal server error simulation

@app.route("/server_error", methods=["GET"])

def trigger_server_error():

    try:

        # Simulating an unexpected server error (e.g., database failure)

        raise Exception("An unexpected server error occurred")

    except Exception as e:

        # Return a 500 Internal Server Error with the error message

        return error_response(500, f"Internal server error: {str(e)}")

if name == "__main__":

    app.run(debug=True)

With this setup, your API will return the following machine-friendly error responses:

{

    "error": {

        "status_code": 400,

        "message": "Item ID must be greater than 0"

    }

}

{

    "error": {

        "status_code": 404,

        "message": "Item with ID 99 not found"

    }

}

{

    "error": {

        "status_code": 500,

        "message": "Internal server error: An unexpected server error occurred"

    }

}

Dockerizing Flask Applications

Once your Python Flask API is set up, it will be time to dockerize your application. This means packaging it along with all of its dependencies into a Docker container, a lightweight and consistent runtime environment designed for seamless portability.

By containerizing your API, you ensure that it runs in isolation and in a consistent manner, regardless of the underlying system. 

This happens because the Docker container typically includes the specific caching services, libraries, databases, and tools that your API depends on. Furthermore, it isolates the API from the host system, ensuring a clean, conflict-free setup.

To dockerize your Flask API follow these steps:

  1. First, write a Dockerfile, a script that contains intructions for building your API’s Docker image, which serves as the blueprint for creating the Docker container. 
    1. Start the Dockerfile with an official Python base image that includes the Python runtime, such as python:3.9-slim:

FROM python:3.9-slim

  1. Set the working directory inside the container using the WORKDIR instruction. This directory will store your API files and serve as the location where commands are executed during the build process and runtime:

WORKDIR /app

  1. Copy the API’s code and files into the container. For example, to copy the requirements.txt file:

COPY requirements.txt /app/.

  1. Install the necessary Python dependencies using pip. These dependencies should be listed in a requirements.txt file. The, copy the rest of the application into the container.

RUN pip install --no-cache-dir -r requirements.txt

COPY . /app/

  1. Expose the port on which the Flask API will run (the default port is 5000):

EXPOSE 5000

  1. Specify the API’s entry point by setting enviromental variables and defining the command that will start the Flask application when the container runs:

ENV FLASK_APP=app.py

CMD ["flask", "run", "--host=0.0.0.0", "--port=5000"]

  1. Once you have your Dockerfile, the next step is to build your API’s Docker image, which is a snapshot containing everything your API needs to run. To create it:
    1. Open a terminal and navigate to the Flask project directory where your Dockerfile is located:

cd path/to/flask-api-abstract

  1. Use the docker build command to build the Docker image. To name your API, use the -t flag. The . at the end will specify that the current directory is the build context, telling Docker to look for the Dockerfile and application files in this directory.

docker build -t flask-api-abstract .

  1. When you run the command above, Docker will:
    1. Pull the base image (python:3.9-slim).
    2. Set up the working directory.
    3. Install the dependencies listed in requirements.txt.
    4. Copy your Flask API code into the image.
    5. Configure Flask to run inside the container.
  2. If the Docker image has been successfully created, it should now be available locally. You can check for it using the docker images command. The image will appear in the output, along with its tag and ID:

REPOSITORY

TAG

IMAGE ID

CREATED

SIZE

flask-api-abstract

latest

abc123456789

2 minutes ago

150 MB

  1. The next step is running your Python Flask API within the Docker container using the docker run command. This command creates a new container from your image and starts it. The basic command is as follows:

docker run -p 5000:5000 flask-api-abstract

This binds port 5000 in the container to port 5000 on your local machine, allowing access to the Flask API.

  1. Once the Flask API is running, you will see the following logs in the terminal:

* Serving Flask app 'app.py'

* Running on all addresses (0.0.0.0)

* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

  1. To access the API locally open a browser and go to http://localhost:5000, or use a command-line tool like curl to verify the API is up and running:

curl http://localhost:5000/

  1. You should receive a response from your Python Flask API (e.g. “Welcome to Flask API Abstract!”)
  2. Bonus tip: If you'd prefer to run the Flask API in the background, so you don’t need to keep the terminal open, you can run the container in detached mode by adding the -d flag to the docker run command:

docker run -d -p 5000:5000 flask-api-abstract

Staging and Production Environments

When building an API with Python Flask, the process typically involves three distinct environments: the development machine, the staging environment, and the production environment. 

Keeping these environments separate is crucial for ensuring a smooth transition from development to live operation and for building a robust API. Additionally, using Docker to develop your API in each environment guarantees it runs inside an isolated and consistent environment, eliminating inconsistencies between development, staging, and production.

The development machine is your local environment where the Flask API is initially built and tested. Typically, this consists of a laptop or workstation where you write and run the application for the first time. As seen above, to run the API locally, you could use a command like:

docker run -p 5000:5000 flask-api-abstract

However, development machines have a significant limitation: they don't replicate real-world production environments. Local testing alone is not sufficient to ensure that the API will function correctly under production conditions. This is where the staging environment becomes crucial.

The staging environment acts as an intermediate step between the development machine and the production environment. It is designed to closely mirror the production environment, deploying real data, services, and configurations that mimic live conditions. However, it remains inaccessible to end-users, providing a safe space for testing.

This allows you to assess how the API performs under different loads, diagnose potential production issues, and ensure it integrates properly with cloud services. Many problems that don't appear on a local development machine can surface in staging due to differences in environmental variables

To run your Flask API in staging mode using Docker, you can use a command like this:

docker run -p 5000:5000 --env FLASK_ENV=staging flask-api-abstract

As mentioned before, the production environment is the live setting where the Flask API is accessed by real users. To succeed in production, the API must run reliably, consistently, and efficiently while offering scalability, high availability, and robust security measures.

Thorough testing in a staging environment before deploying to production is essential for ensuring this level of performance. Additionally, running your API inside a Docker container in production provides isolation from the host system, contributing to a more stable and consistent setup.

To run your Flask API in the production environment using Docker, you can use the following command:

docker run -d -p 5000:5000 --env FLASK_ENV=production flask-api-abstract

Remember that here the -d flag runs the container in detached mode, allowing it to continue running in the background.

Your API, Your Call

To build an API using Python Flask is relatively straightforward and doesn’t require extensive programming knowledge. It enables you to quickly create simple, flexible, and lightweight solutions, ideal for small to medium-sized web or networked projects.

However, APIs built with Python Flask have limitations that can become significant when scaling is necessary or when larger, more complex solutions are required.

Our advice? Play it safe, especially when playing it safe means going big.

At Abstract API, we know that with the right API, the only limit is the world, and we’re here to help you conquer it.

Visit our website to explore our solutions—whether that’s reviewing our API list, or getting your free API key to start using our go-to tools, built by developers for developers.

Get your free
API
key now
4.8 from 1,863 votes
See why the best developers build on Abstract
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
No credit card required