DEV Community

Cover image for How to build an API using Flask
Krishnanshu Rathore
Krishnanshu Rathore

Posted on

How to build an API using Flask

In this tutorial, we will learn how to create a simple RESTful API using Flask, a lightweight web framework for Python.

We will also use SQLAlchemy, an ORM (Object-Relational Mapper) that allows us to interact with a database using Python objects. We will use SQLite as our database, but you can use any other database that SQLAlchemy supports.

What is a RESTful API?

A RESTful API (Representational State Transfer) is a way of designing web services that follow some principles:

  • Each resource (such as a user, a product, a post, etc.) is identified by a unique URI (Uniform Resource Identifier), such as /users/1 or /products/42.

  • The client can perform different operations on the resources using HTTP methods, such as GET, POST, PUT, PATCH, DELETE, etc. For example, to create a new user, the client can send a POST request to /users with the user data in the request body.To update an existing user, the client can send a PUT or PATCH request to /users/1 with the updated data in the request body. To delete a user, the client can send a DELETE request to /users/1.

  • The server responds with the appropriate status code and data in the response body, usually in JSON (JavaScript Object Notation) format. For example, if the user creation was successful, the server can respond with a 201 (Created) status code and the created user data in the response body.

If the user update was successful, the server can respond with a 200 (OK) status code and the updated user data in the response body. If the user deletion was successful, the server can respond with a 204 (No Content) status code and no response body.

What is Flask?

Flask is a micro web framework for Python that allows us to create web applications quickly and easily.

It has minimal dependencies and provides us with the essential tools to build web services, such as routing, request and response handling, templating, etc.

Flask is also extensible and supports various extensions that add more functionality to our applications, such as SQLAlchemy for database integration, Flask-RESTful for building RESTful APIs, Flask-JWT for authentication and authorization, etc.

What is SQLAlchemy?

SQLAlchemy is an ORM that allows us to work with databases using Python objects. It abstracts away the low-level details of SQL queries and provides us with a high-level interface to manipulate data.

SQLAlchemy supports various databases, such as SQLite, PostgreSQL, MySQL, Oracle, etc. SQLAlchemy also provides us with declarative models that define our database schema using Python classes and attributes.

We can then use these models to perform CRUD (Create, Read, Update, Delete) operations on our data.

Setting up our project:

To start our project, we need to install some dependencies:

  • Python 3: You can download it from https://www.python.org/downloads/ or use your preferred package manager.

  • Pip: A tool for installing Python packages. It should come with Python 3 by default.

  • Virtualenv: A tool for creating isolated Python environments. You can install it using pip install virtualenv.

  • Flask: Our web framework. You can install it using pip install flask.

  • SQLAlchemy: Our ORM. You can install it using pip install sqlalchemy.

  • Flask-SQLAlchemy: An extension that integrates SQLAlchemy with Flask. You can install it using pip install flask-sqlalchemy.

Next, we need to create our project directory and files:

  • Create a directory called flask-api and navigate to it.

  • Create a virtual environment called venv using virtualenv venv.

  • Activate the virtual environment using source venv/bin/activate on Linux/Mac or venv\Scripts\activate on Windows.

  • Create a file called app.py that will contain our main application code.

  • Create a file called models.py that will contain our database models.

  • Create a file called config.py that will contain our configuration settings.

Configuring our application

In our config.py file, we need to define some configuration settings for our application:

import os

# Get the absolute path of the current directory
basedir = os.path.abspath(os.path.dirname(__file__))

# Define the SQLALCHEMY_DATABASE_URI variable that tells SQLAlchemy where to find our database
# We will use SQLite for simplicity and store it in a file called app.db in our project directory
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')

# Define the SQLALCHEMY_TRACK_MODIFICATIONS variable that tells SQLAlchemy whether to track changes to the database
# We will set it to False to avoid unnecessary overhead
SQLALCHEMY_TRACK_MODIFICATIONS = False
Enter fullscreen mode Exit fullscreen mode

Creating our application

In our app.py file, we need to create our Flask application and initialize it with our configuration settings.

To interact with the database using SQLAlchemy, we need to create a db object and associate it with our Flask application. This can be achieved by importing the SQLAlchemy class from the flask_sqlalchemy module and creating a db object in a separate file called db.py.

Here is an example code snippet for creating the db object:

from flask_sqlalchemy import SQLAlchemy

# Create a SQLAlchemy object
db = SQLAlchemy()
Enter fullscreen mode Exit fullscreen mode

Then, in the main application file app.py, we can create a Flask application and associate it with the db object by calling the init_app() method of the db object. Here is an example code snippet:

from flask import Flask
from db import db

# Create a Flask application with the name of the current module
app = Flask(__name__)

# Load the configuration settings from the config.py file
app.config.from_pyfile('config.py')

# Initialize the SQLAlchemy object with our application
db.init_app(app)
Enter fullscreen mode Exit fullscreen mode

Defining our database models

In our models.py file, we need to define our database models using SQLAlchemy’s declarative syntax. A model is a Python class that represents a table in our database and its attributes represent the columns. We also need to import the db object which we will use to interact with our database.

For this tutorial, we will create a simple model called User that has the following attributes:

  • id: An integer that is the primary key of the table and uniquely identifies each user.
  • name: A string that stores the name of the user.
  • email: A string that stores the email address of the user.

Now, we can define our model as follows:

from db import db

# Define a User model that inherits from db.Model
class User(db.Model):
    # Define the id attribute as an integer column that is the primary key
    id = db.Column(db.Integer, primary_key=True)
    # Define the name attribute as a string column that is not nullable
    name = db.Column(db.String(80), nullable=False)
    # Define the email attribute as a string column that is unique and not nullable
    email = db.Column(db.String(120), unique=True, nullable=False)

    # Define a __repr__ method that returns a string representation of the user object
    def __repr__(self):
        return f'<User {self.name}>'


Enter fullscreen mode Exit fullscreen mode

Creating our database

After defining our database model, the next step is to create the actual database and its tables. This can be done using SQLAlchemy's create_all method. To do this, we'll first need to activate our virtual environment (if it isn't already), and then create a file called create_db.py.

Inside create_db.py, we'll include the following code snippet:

from app import app, db

# Create and push an application context
with app.app_context():
    # Now you can use the db object
    db.create_all()

Enter fullscreen mode Exit fullscreen mode
  • Execute the file in the terminal.

We can verify that our database and its tables have been created by looking at the app.db file in our project directory. We can also use a tool like DB Browser for SQLite (https://sqlitebrowser.org/) to inspect and manipulate our database.

Building Our API

Now that we have created our database and its model, we can start building our API. We will use Flask’s built-in routing system to define different endpoints for our API and handle different HTTP methods. We will also use Flask’s request and jsonify functions to parse and return JSON data.

We will implement the following endpoints for our API:

  • GET /users: Return a list of all users in JSON format.

  • GET /users/<id>: Return a single user with the given id in JSON format. If no user with that id exists, return a 404 (Not Found) error.

  • POST /users: Create a new user with the data provided in the request body in JSON format. Return the created user in JSON format with a 201 (Created) status code.

  • PUT /users/<id>: Update an existing user with the given id with the data provided in the request body in JSON format. Return the updated user in JSON format with a 200 (OK) status code. If no user with that id exists, return a 404 (Not Found) error.

  • DELETE /users/<id>: Delete an existing user with the given id. Return a 204 (No Content) status code. If no user with that id exists, return a 404 (Not Found) error.
    We can add the following code to our app.py file to implement these endpoints:

from flask import Flask, jsonify, request
from db import db

# Create a Flask application with the name of the current module
app = Flask(__name__)

# Load the configuration settings from the config.py file
app.config.from_pyfile('config.py')

# Initialize the SQLAlchemy object with our application
db.init_app(app)

# Import the User model from models.py
from models import User

# Define a route for the GET /users endpoint
@app.route('/users', methods=['GET'])
def get_users():
    # Query all users from the database
    users = User.query.all()
    # Convert each user object to a dictionary
    users_dict = [user.__dict__ for user in users]
    # Remove the _sa_instance_state attribute from each dictionary
    for user_dict in users_dict:
        user_dict.pop('_sa_instance_state')
    # Return a JSON response with the list of users
    return jsonify(users_dict)

# Define a route for the GET /users/<id> endpoint
@app.route('/users/<int:id>', methods=['GET'])
def get_user(id):
    # Query a user by id from the database
    user = User.query.get(id)
    # Check if the user exists
    if user is None:
        # Return a 404 error if not found
        return jsonify({'message': 'User not found'}), 404
    else:
        # Convert the user object to a dictionary
        user_dict = user.__dict__
        # Remove the _sa_instance_state attribute from the dictionary
        user_dict.pop('_sa_instance_state')
        # Return a JSON response with the user data
        return jsonify(user_dict)

# Define a route for the POST /users endpoint
@app.route('/users', methods=['POST'])
def create_user():
    # Get the data from the request body as a dictionary
    data = request.get_json()
    # Check if the data is valid
    if 'name' not in data or 'email' not in data:
        # Return a 400 error if missing name or email
        return jsonify({'message': 'Name and email are required'}), 400
    else:
        # Create a new user object with the data
        user = User(name=data['name'], email=data['email'])
        # Add and commit the user to the database
        db.session.add(user)
        db.session.commit()
        # Convert the user object to a dictionary
        user_dict = user.__dict__
        # Remove the _sa_instance_state attribute from the dictionary
        user_dict.pop('_sa_instance_state')
        # Return a JSON response with the created user data and a 201 status code
        return jsonify(user_dict), 201

# Define a route for the PUT /users/<id> endpoint
@app.route('/users/<int:id>', methods=['PUT'])
def update_user(id):
    # Query a user by id from the database
    user = User.query.get(id)
    # Check if the user exists
    if user is None:
        # Return a 404 error if not found
        return jsonify({'message': 'User not found'}), 404
    else:
        # Get the data from the request body as a dictionary
        data = request.get_json()
        # Check if the data is valid
        if 'name' not in data or 'email' not in data:
            # Return a 400 error if missing name or email
            return jsonify({'message': 'Name and email are required'}), 400
        else:
            # Update the user object with the data
            user.name = data['name']
            user.email = data['email']
            # Commit the changes to the database
            db.session.commit()
            # Convert the user object to a dictionary
            user_dict = user.__dict__
            # Remove the _sa_instance_state attribute from the dictionary
            user_dict.pop('_sa_instance_state')
            # Return a JSON response with the updated user data and a 200 status code
            return jsonify(user_dict), 200

# Define a route for the DELETE /users/<id> endpoint
@app.route('/users/<int:id>', methods=['DELETE'])
def delete_user(id):
    # Query a user by id from the database
    user = User.query.get(id)
    # Check if the user exists
    if user is None:
        # Return a 404 error if not found
        return jsonify({'message': 'User not found'}), 404
    else:
        # Delete the user from the database
        db.session.delete(user)
        db.session.commit()
        # Return a 204 status code with no response body
        return '', 204

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

Enter fullscreen mode Exit fullscreen mode

Testing our API

Now that we have implemented our API endpoints, we can test them using a tool like Postman (https://www.postman.com/) or curl (https://curl.se/). We can use these tools to send different HTTP requests to our API and inspect the responses.

To test our API, we need to do the following steps:

  • Run our Flask application using python app.py.
  • Open Postman or curl and send different requests to our API endpoints.
  • Check the status codes and response bodies of each request.

First let's create new users using POST method:

  • POST /users: Create a new user with the data provided in the request body in JSON format. Return the created user in JSON format with a 201 (Created) status code.

Request:

#User 1:

curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice","email":"alice@example.com"}' http://127.0.0.1:5000/users
Enter fullscreen mode Exit fullscreen mode

Response:

{}
Enter fullscreen mode Exit fullscreen mode

User 2:

curl -X POST -H "Content-Type: application/json" -d '{"name": "Bob", "email": "bob@example.com"}' http://127.0.0.1:5000/users
Enter fullscreen mode Exit fullscreen mode

User 3:

curl -X POST -H "Content-Type: application/json" -d '{"name": "Charlie","email": "charlie@example.com"}' http://127.0.0.1:5000/users
Enter fullscreen mode Exit fullscreen mode

Now we will use GET method to return the list of users in JSON format.

  • GET /users:

Request:

curl http://127.0.0.1:5000/users
Enter fullscreen mode Exit fullscreen mode

Response:


[
  {
    "id": 1,
    "name": "Alice",
    "email": "alice@example.com"
  },
  {
    "id": 2,
    "name": "Bob",
    "email": "bob@example.com"
  },
  {
    "id": 3,
    "name": "Charlie",
    "email": "charlie@example.com"
  }
]
Enter fullscreen mode Exit fullscreen mode
  • GET /users/<id>: Return a single user with the given id in JSON format. If no user with that id exists, return a 404 (Not Found) error.

Request:

curl http://localhost:5000/users/1
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "id": 1,
  "name": "Alice",
  "email": "alice@example.com"
}
Enter fullscreen mode Exit fullscreen mode

Request:

curl http://localhost:5000/users/4
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "message": "User not found"
}
Enter fullscreen mode Exit fullscreen mode
  • PUT /users/<id>: Update an existing user with the given id with the data provided in the request body in JSON format. Return the updated user in JSON format with a 200 (OK) status code. If no user with that id exists, return a 404 (Not Found) error.

Request:

curl -X PUT -H "Content-Type: application/json" -d '{"name": "Alice", "email": "alice@new.com"}' http://localhost:5000/users/1
Enter fullscreen mode Exit fullscreen mode

Response:

{}
Enter fullscreen mode Exit fullscreen mode

Use GET to check the updated details of the user

curl http://localhost:5000/users/1
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "id": 1,
  "name": "Alice",
  "email": "alice@new.com"
}
Enter fullscreen mode Exit fullscreen mode

Request:

curl -X PUT -H "Content-Type: application/json" -d '{"name": "Eve", "email": "eve@example.com"}' http://localhost:5000/users/4
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "message": "User not found"
}
Enter fullscreen mode Exit fullscreen mode
  • DELETE /users/<id>: Delete an existing user with the given id. Return a 204 (No Content) status code. If no user with that id exists, return a 404 (Not Found) error.

Request:

curl -X DELETE http://localhost:5000/users/2
Enter fullscreen mode Exit fullscreen mode

Response:

No response body.

Request:

curl -X DELETE http://localhost:5000/users/5
Enter fullscreen mode Exit fullscreen mode

Response:

{
  "message": "User not found"
}
Enter fullscreen mode Exit fullscreen mode

To sum up, in this tutorial we have learned how to create a simple RESTful API using Flask and SQLAlchemy. We have also learned how to perform CRUD operations on our database using Python objects and how to test our API using curl.

Flask and Flask-RESTful provide many advanced features and options for building REST APIs such as connecting to databases using flask_sqlalchemy, serialization and deserialization of data using flask_marshmallow, adding authentication and authorization using flask_jwt_extended, and generating interactive documentation for the API using flask_swagger_ui.

For more information and examples, you can refer to the official documentation of Flask and Flask-RESTful available at:

I hope you enjoyed this tutorial and found it useful. Happy coding!

Top comments (0)