Guide to creating a RESTful API using Python, Flask and MongoDB

Intro to RESTful APIs

RESTful APIs using JSON on the HTTP protocol is by far the most popular approach to creating web APIs. They enable developers to think in terms of resources with actions on those resources like CRUD (Create, Read, Update, Delete). However, there are upcoming APIs like GraphQL which is increasingly becoming popular with many applications.

In this tutorial, we are going to build a RESTful API to create, read, update and delete the documents (which will contain User information) from a Mongo database using Python and Flask framework.

Getting started

Here are the tools we’ll need to build our APIs:

Creating your local environment

NOTE: While working with Python, we would recommend to use virtual environment to keep all the project’s dependencies isolated from other projects.

conda create -n restfulapi python=3.7 anaconda # Create the environment
source activate restfulapi # Activate the environment

Install dependencies

pip install -r requirements.txt

Start the MongoDB server

If you’re using MacOS, you could use brew to start the server.

brew services start mongodb

Collections in MongoDB

A collection is similar to a table in a traditional relational Db. We will created a Users collection to store user details similar to a Users table in SQL.

Create Users

We will create an endpoint POST /api/v1/users which takes in a JSON object consisting of the user details like name, email, phone as JSON in the request body. We could also design the endpoint to take an array of users.

@app.route("/api/v1/users", methods=['POST'])
def create_user():
    """
       Function to create new users.
       """
    try:
        # Create new users
        try:
            body = ast.literal_eval(json.dumps(request.get_json()))
        except:
            # Bad request as request body is not available
            # Add message for debugging purpose
            return "", 400

        record_created = collection.insert(body)

        # Prepare the response
        if isinstance(record_created, list):
            # Return list of Id of the newly created item
            return jsonify([str(v) for v in record_created]), 201
        else:
            # Return Id of the newly created item
            return jsonify(str(record_created)), 201
    except:
        # Error while trying to create the resource
        # Add message for debugging purpose
        return "", 500

create_user.png

A single user returns the entity id so the frontend can reference the newly created item. On the other hand, an array of users returns an array of entity id.

Read Users

To fetch a list users which we just created, we will design an endpoint GET /api/v1/users and pass the search criteria as the query string parameters.

@app.route("/api/v1/users", methods=['GET'])
def fetch_users():
    """
       Function to fetch the users.
       """
    try:
        # Call the function to get the query params
        query_params = helper_module.parse_query_params(request.query_string)
        # Check if dictionary is not empty
        if query_params:

            # Try to convert the value to int
            query = {k: int(v) if isinstance(v, str) and v.isdigit() else v for k, v in query_params.items()}

            # Fetch all the record(s)
            records_fetched = collection.find(query)

            # Check if the records are found
            if records_fetched.count() > 0:
                # Prepare the response
                return dumps(records_fetched)
            else:
                # No records are found
                return "", 404

        # If dictionary is empty
        else:
            # Return all the records as query string parameters are not available
            if collection.find().count() > 0:
                # Prepare response if the users are found
                return dumps(collection.find())
            else:
                # Return empty array if no users are found
                return jsonify([])
    except:
        # Error while trying to fetch the resource
        # Add message for debugging purpose
        return "", 500

We will return the user details if the search criteria is provided else we would try to return all the documents if exists else we would return an empty array.

fetch_user.png

Update Users

Now, we will design an endpoint POST /api/v1/users/<user_id> to update the user details.

@app.route("/api/v1/users/<user_id>", methods=['POST'])
def update_user(user_id):
    """
       Function to update the user.
       """
    try:
        # Get the value which needs to be updated
        try:
            body = ast.literal_eval(json.dumps(request.get_json()))
        except:
            # Bad request as the request body is not available
            # Add message for debugging purpose
            return "", 400

        # Updating the user
        records_updated = collection.update_one({"id": int(user_id)}, body)

        # Check if resource is updated
        if records_updated.modified_count > 0:
            # Prepare the response as resource is updated successfully
            return "", 200
        else:
            # Bad request as the resource is not available to update
            # Add message for debugging purpose
            return "", 404
    except:
        # Error while trying to update the resource
        # Add message for debugging purpose
        return "", 500

For example, we would like to update the document with id matches 1.

update_user.png

Remove Users

Finally, we will design an endpoint DELETE /api/v1/users/<user_id> to delete the user from the database.

@app.route("/api/v1/users/<user_id>", methods=['DELETE'])
def remove_user(user_id):
    """
       Function to remove the user.
       """
    try:
        # Delete the user
        delete_user = collection.delete_one({"id": int(user_id)})

        if delete_user.deleted_count > 0 :
            # Prepare the response
            return "", 204
        else:
            # Resource Not found
            return "", 404
    except:
        # Error while trying to delete the resource
        # Add message for debugging purpose
        return "", 500

We would specify the matching condition on which to delete the document. For example, we would like to remove the document with id matches 1

remove_user.png

Handling page not found request

We recommend to handle page not found request with informative message to the user.

@app.errorhandler(404)
def page_not_found(e):
    """Send message to the user with notFound 404 status."""
    # Message to the user
    message = {
        "err":
            {
                "msg": "This route is currently not supported. Please refer API documentation."
            }
    }
    # Making the message looks good
    resp = jsonify(message)
    # Sending OK response
    resp.status_code = 404
    # Returning the object
    return resp

If you would like to play around with the data, you could also use Robo 3T mongo shell.

mongo_shell.png

To see the RESTful API in action, you can git clone and run this example app from GitHub

In the next blog, we would learn about how to authorize and authenticate our APIs. If you’re curious to get started with GraphQL using Python - please refer to this blog. Meanwhile, if you have any questions, reach out to the Moesif Team

Moesif is the most advanced API Analytics platform. Thousands of platform companies leverage Moesif for debugging, monitoring and discovering insights.

Learn More