Designing a RESTful API using Flask-RESTful

Posted by
on under

This is the third article in which I explore different aspects of writing RESTful APIs using the Flask microframework. Here is the first, and the second.

The example RESTful server I wrote before used only Flask as a dependency. Today I will show you how to write the same server using Flask-RESTful, a Flask extension that simplifies the creation of APIs.

The RESTful server

As a reminder, here is the definition of the ToDo List web service that has been serving as an example in my RESTful articles:

HTTP MethodURIAction
GEThttp://[hostname]/todo/api/v1.0/tasksRetrieve list of tasks
GEThttp://[hostname]/todo/api/v1.0/tasks/[task_id]Retrieve a task
POSThttp://[hostname]/todo/api/v1.0/tasksCreate a new task
PUThttp://[hostname]/todo/api/v1.0/tasks/[task_id]Update an existing task
DELETEhttp://[hostname]/todo/api/v1.0/tasks/[task_id]Delete a task

The only resource exposed by this service is a "task", which has the following data fields:

  • uri: unique URI for the task. String type.
  • title: short task description. String type.
  • description: long task description. Text type.
  • done: task completion state. Boolean type.

Routing

In my first RESTful server example (source code here) I have used regular Flask view functions to define all the routes.

Flask-RESTful provides a Resource base class that can define the routing for one or more HTTP methods for a given URL. For example, to define a User resource with GET, PUT and DELETE methods you would write:

from flask import Flask
from flask_restful import Api, Resource

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

class UserAPI(Resource):
    def get(self, id):
        pass

    def put(self, id):
        pass

    def delete(self, id):
        pass

api.add_resource(UserAPI, '/users/<int:id>', endpoint = 'user')

The add_resource function registers the routes with the framework using the given endpoint. If an endpoint isn't given then Flask-RESTful generates one for you from the class name, but since sometimes the endpoint is needed for functions such as url_for I prefer to make it explicit.

My ToDo API defines two URLs: /todo/api/v1.0/tasks for the list of tasks, and /todo/api/v1.0/tasks/<int:id> for an individual task. Since Flask-RESTful's Resource class can wrap a single URL this server will need two resources:

class TaskListAPI(Resource):
    def get(self):
        pass

    def post(self):
        pass

class TaskAPI(Resource):
    def get(self, id):
        pass

    def put(self, id):
        pass

    def delete(self, id):
        pass

api.add_resource(TaskListAPI, '/todo/api/v1.0/tasks', endpoint = 'tasks')
api.add_resource(TaskAPI, '/todo/api/v1.0/tasks/<int:id>', endpoint = 'task')

Note that while the method views of TaskListAPI receive no arguments the ones in TaskAPI all receive the id, as specified in the URL under which the resource is registered.

Request Parsing and Validation

When I implemented this server in the previous article I did my own validation of the request data. For example, look at how long the PUT handler is in that version:

@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods = ['PUT'])
@auth.login_required
def update_task(task_id):
    task = filter(lambda t: t['id'] == task_id, tasks)
    if len(task) == 0:
        abort(404)
    if not request.json:
        abort(400)
    if 'title' in request.json and type(request.json['title']) != unicode:
        abort(400)
    if 'description' in request.json and type(request.json['description']) is not unicode:
        abort(400)
    if 'done' in request.json and type(request.json['done']) is not bool:
        abort(400)
    task[0]['title'] = request.json.get('title', task[0]['title'])
    task[0]['description'] = request.json.get('description', task[0]['description'])
    task[0]['done'] = request.json.get('done', task[0]['done'])
    return jsonify( { 'task': make_public_task(task[0]) } )

Here I have to make sure the data given with the request is valid before using it, and that makes the function pretty long.

Flask-RESTful provides a much better way to handle this with the RequestParser class. This class works in a similar way as argparse for command line arguments.

First, for each resource I define the arguments and how to validate them:

from flask_restful import reqparse

class TaskListAPI(Resource):
    def __init__(self):
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument('title', type = str, required = True,
            help = 'No task title provided', location = 'json')
        self.reqparse.add_argument('description', type = str, default = "", location = 'json')
        super(TaskListAPI, self).__init__()

    # ...

class TaskAPI(Resource):
    def __init__(self):
        self.reqparse = reqparse.RequestParser()
        self.reqparse.add_argument('title', type = str, location = 'json')
        self.reqparse.add_argument('description', type = str, location = 'json')
        self.reqparse.add_argument('done', type = bool, location = 'json')
        super(TaskAPI, self).__init__()

    # ...

In the TaskListAPI resource the POST method is the only one the receives arguments. The title argument is required here, so I included an error message that Flask-RESTful will send as a response to the client when the field is missing. The description field is optional, and when it is missing a default value of an empty string will be used. One interesting aspect of the RequestParser class is that by default it looks for fields in request.values, so the location optional argument must be set to indicate that the fields are coming in request.json.

The request parser for the TaskAPI is constructed in a similar way, but has a few differences. In this case it is the PUT method that will need to parse arguments, and for this method all the arguments are optional, including the done field that was not part of the request in the other resource.

Now that the request parsers are initialized, parsing and validating a request is pretty easy. For example, note how much simpler the TaskAPI.put() method becomes:

    def put(self, id):
        task = filter(lambda t: t['id'] == id, tasks)
        if len(task) == 0:
            abort(404)
        task = task[0]
        args = self.reqparse.parse_args()
        for k, v in args.iteritems():
            if v != None:
                task[k] = v
        return jsonify( { 'task': make_public_task(task) } )

A side benefit of letting Flask-RESTful do the validation is that now there is no need to have a handler for the bad request code 400 error, this is all taken care of by the extension.

Generating Responses

My original REST server generates the responses using Flask's jsonify helper function. Flask-RESTful automatically handles the conversion to JSON, so instead of this:

        return jsonify( { 'task': make_public_task(task) } )

I can do this:

        return { 'task': make_public_task(task) }

Flask-RESTful also supports passing a custom status code back when necessary:

        return { 'task': make_public_task(task) }, 201

But there is more. The make_public_task wrapper from the original server converted a task from its internal representation to the external representation that clients expected. The conversion included removing the id field and adding a uri field in its place. Flask-RESTful provides a helper function to do this in a much more elegant way that not only generates the uri but also does type conversion on the remaining fields:

from flask_restful import fields, marshal

task_fields = {
    'title': fields.String,
    'description': fields.String,
    'done': fields.Boolean,
    'uri': fields.Url('task')
}

class TaskAPI(Resource):
    # ...

    def put(self, id):
        # ...
        return { 'task': marshal(task, task_fields) }

The task_fields structure serves as a template for the marshal function. The fields.Url type is a special type that generates a URL. The argument it takes is the endpoint (recall that I have used explicit endpoints when I registered the resources specifically so that I can refer to them when needed).

Authentication

The routes in the REST server are all protected with HTTP basic authentication. In the original server the protection was added using the decorator provided by the Flask-HTTPAuth extension.

Since the Resouce class inherits from Flask's MethodView, it is possible to attach decorators to the methods by defining a decorators class variable:

from flask_httpauth import HTTPBasicAuth
# ...
auth = HTTPBasicAuth()
# ...

class TaskAPI(Resource):
    decorators = [auth.login_required]
    # ...

class TaskAPI(Resource):
    decorators = [auth.login_required]
    # ...

Conclusion

The complete server implementation based on Flask-RESTful is available in my REST-tutorial project on github. The file with the Flask-RESTful server is rest-server-v2.py.

You can also download the entire project including both server implementations and a javascript client to test it:

Download REST-tutorial project.

Let me know if you have any questions in the comments below.

Miguel

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

188 comments
  • #26 Bob said

    Great tutorials, Miguel! Regarding flask-restful, how would you deal with controlling the order of the marshal'ed fields in the JSON response. Technically, field order shouldn't matter but for readability (and consistency with API documentation) I would like to preserve field order. Thanks!

  • #27 Miguel Grinberg said

    @Bob: I don't think you can control the order with the standard JSON renderer, but if you feel strongly about this nothing prevents you from writing your own and forking Flask-Restful to use it. Note that the JSON specification defines a JSON object as an "unordered collection of properties".

  • #28 Evgeny said

    Miguel, how can I create my own method in Resource class, that will have a unique url?

  • #29 Miguel Grinberg said

    @Evgeny: the URL associated with a Resource is given in the add_resource() call. The methods of the class match the HTTP methods that come in the request.

  • #30 Busata said

    Your example of marshal will not generate the same URI as in your "manual" approach of flask rest API though. It'll just generate an URI to the entry point.

  • #31 Miguel Grinberg said

    @Busata: I have submitted a pull request to Flask-Restful to add the option to generate full URLs, you can find my fork with this fix at https://github.com/miguelgrinberg/flask-restful

  • #32 Busata said

    @Miguel: I read that, but despite having a full uri, it will generate the /api/todo url, without any ID specified, which still won't let you "access" the todo item you specified?

  • #33 Miguel Grinberg said

    @Busada: I get the ID just fine. Here is an example response:

    {
    "tasks": [
    {
    "description": "Milk, Cheese, Pizza, Fruit, Tylenol",
    "done": false,
    "title": "Buy groceries",
    "uri": "/todo/api/v1.0/tasks/1"
    },
    {
    "description": "Need to find a good Python tutorial on the web",
    "done": false,
    "title": "Learn Python",
    "uri": "/todo/api/v1.0/tasks/2"
    }
    ]
    }

  • #34 lovesh said

    Here are 2 articles that will help you customize flask-restful. One talks about request and response processing and the other talks about error handling and logging and response logging

    http://icybean.com/customizing-flask-restful.html

    http://icybean.com/error-handling-and-logging-in-flask-restful.html

  • #35 Julian Alexander Uran said

    Hi Miguel, your tutorials are superb, especially the web base REST client for this server.

    I also wanted to ask you why could it be that I'm getting PUT 405 Methos not allowed errors when running the v2 server trying (i.e.) to mark tasks as done. I don't get any errors while using the v1 server.

  • #36 Miguel Grinberg said

    @Julian: Thanks. I'm not sure about the 405 error. The v2 server uses flask-restful to control that, did you compare your version of the server with the one I have on github?

  • #37 cancerballs said

    Thanks for the tutorial.

    GitHub's API lets you star a gist with PUT /gists/:id/star and unstar with DELETE /gists/:id/star.

    Any idea how I can do the same using flask-restful?

  • #38 Miguel Grinberg said

    @cancerballs: Sure, you would register the resource endpoint as something like '/gists/<int:id>/star', then implement put() and delete() methods.

  • #39 Hector said

    Miguel, I followed your REST-tutorials step by step. They are great!

    I am trying to follow your tutorials for solving one design problem but without success. Allow me a slight description of an application developed for reporting, viewing and plotting graphs of different electrical energy variables.

    The system was developed as a GUI for a module/device that capture and measure different variables of electrical energy consumption. This module, communicates the different captured values to the receiver PC (server) using MODBUS Protocol.

    I use Minimalmodbus for developing the client/server communications system. The application is developed in Python2-7, Pandas, Flask and Mysqldb. All in OS Ubuntu 12.04LTS – 64bits system. As standalone unit the system works fine, but I do not see how to set up the Flask server correctly. What I am missing?

    The idea previously exposed, is to have the application deployed on an Intranet server. Allowing some clients to access the data and or ploy graphs.

    I already tried to implement the server using: Flask+Nginx+uWSGI without success. I also used Flask plus the old Apache + mod_WSGI with same negative result. Unfortunately I do not see how to implement either a RESTful flask server for my application.

    One of the main difficulties for example, is when we try to plot a graph from a client PC. The client communicates with server without problem, but the popup window (graph) only raises on the server screen. It is not shown in the client side.
    Similarly, occurs the same when we try to use treeview/listview widgets to visualize some data from data base.

    Have you seen similar approach to solve problems like mine elsewhere? Do you have any suggestions?

    Thanks a lot
    Hector

  • #40 Miguel Grinberg said

    @Hector: the technologies to draw something on the screen are completely different between desktop and web-based apps. I'm not sure I understand you correctly, but it looks like you are trying to adapt a desktop based program to work as a web server. This will take some effort, you will need to code your GUI in HTML, CSS and Javascript and remove any reference to GUI elements such as popups or widgets.

  • #41 Charles Bueche said

    Hi,

    Thanks a lot for your blog entries, got me running in no time.

    I have a problem with "marshal" crashing on me, when I try to use fields.Url(). Without Url(), it works fine.

    Traceback (most recent call last):
    File ".../AJ/lib/python2.6/site-packages/flask/app.py", line 1836, in call
    return self.wsgi_app(environ, start_response)
    File ".../AJ/lib/python2.6/site-packages/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
    File ".../AJ/lib/python2.6/site-packages/flask_restful/init.py", line 250, in error_router
    return self.handle_error(e)
    File ".../AJ/lib/python2.6/site-packages/flask_restful/init.py", line 268, in handle_error
    raise exc
    TypeError: init() takes exactly 4 arguments (2 given)

    I call it with one or 3 parameters, always the same error:

    device_fields = {
    stuff...,
    'uri': fields.Url('device', absolute=True, scheme='http')
    }

    or

    device_fields = {
    stuff...,
    'uri': fields.Url('device')
    }

    Any idea ?
    TIA,
    Charles

  • #42 Miguel Grinberg said

    @Charles: I see that you are using Python 2.6, which I've never tested. Does my code for this article (which you can download from GitHub) works fine? It works fine for me on 2.7, even with the latest Flask-RESTful release.

  • #43 Lu Liu said

    many thanks for the great articles about flask!

    It seems that the sample code of REST-tutorial on github has CORS problem. I have set up the server successfully. But my browser reports the error 'Failed to load resource: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:5000' is therefore not allowed access.' when I tried adding a new task. Do you think it's necessary to add a cors decorator for each resource url? If yes, how to do that with flask-restful?

  • #44 Miguel Grinberg said

    @Lu: I'm not sure why you get this problem. Are you opening http://localhost:5000/index.html in your browser? In any case, to support CORS you have to look at the "Origin" header sent by browsers and respond with the "Access-Control-Allow-Origin" that includes that origin. Flask-RESTful does not do this, but it is easy enough to do directly with the request and response objects.

  • #45 Iñigo said

    Hello Miguel!

    First of all, I want to say "thank you so much!!" for this and, in general, all your posts.

    I am following this tutorial on building a REST API and getting a strange error when trying to register my API classes using the "add_resource" method. It seems that my classes are not inheriting the "as_view" method from the "Resource" class.

    I posted this question also in StackOverflow. You can have a look at it in the following link:
    http://stackoverflow.com/questions/21547073/flask-restful-error-as-view-method-not-inherited

    Thanks in advance for your help!

  • #46 Miguel Grinberg said

    @Iñigo: this tutorial was written when Flask-Restful was at v0.2.3. Are you using a newer version? Give 0.2.3 a try to see if that makes any difference.

  • #47 Iñigo said

    I tried with different versions of Flask-Restful as suggested, but it didn't work anyway. I finally managed to solve this issue by inheriting directly from the MethodView class of Flask instead of from the Resource class of Flask-Restful.

    Thank you for your help! And again, thanks a lot for this blog!

  • #48 Arne said

    Is there a proper illustration/example, how to use the "DELETE" request from the front end. I do only know, that it is possible to use the delete call with a XMLHttpRequest. The html-forms do not support the delete method. (Since HTTP != HTML I have to use ajax to do the delete call ;))

  • #49 Miguel Grinberg said

    @Arne: You are correct, the DELETE and PUT methods are normally issued over ajax when the client is talking to an API. If you have a traditional site, then deletions are done as GET or POST, and the response of the operation has to include a new HTML document or a redirect.

  • #50 Marcelo said

    Thank you Miguel! This is an amazing tutorial. One minor comment on the marshaling portion: I've replaced fields.Url('task') by fields.Url('task', absolute=True) in my code, so that the resulting URL is absolute and makes hyper-linking simpler. I think this is similar to what you were doing on your non-Restful Flask tutorial, but using "url_for" with "_external=True" as parameter.

Leave a Comment