2013-07-28T19:11:38Z

Designing a RESTful API using Flask-RESTful

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.ext.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.ext.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.ext.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.ext.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

170 comments

  • #151 Miguel Grinberg said 2017-01-30T17:31:15Z

    @Mark: Not sure. I did not notice any slowness myself. You may want to investigate further. Try different projects to see if it is a problem across the board, or just on this particular API.

  • #152 Andy said 2017-05-11T01:47:32Z

    Hi Miguel. Great tutorial. I'm in the process of creating an API with flask but wanted to ask you opinion on the code injection protection in flask-restful. My api basically allows the user to input their email address and receive some information back on their account. api.add_resource(TaskAPI, '/api/v1.0//<string:email>', endpoint = 'email') Firstly, does this code have injection protection built in? e.g. if the email variable provide through the url contains some code like ../../../../etc/passwd%00 would this escape out or would it be protected? I'm also concerned to be able to let all valid email addresses in, which are allowed to contain a plethora of potentially dangerous characters, so it's not just a matter of using regex to strip them out... http://stackoverflow.com/questions/2049502/what-characters-are-allowed-in-an-email-address I'd be interested if you know how python flask restful handles this.

  • #153 Miguel Grinberg said 2017-05-11T11:40:29Z

    @Andy: The <string:email> argument is not going to match something that has forward slashes, so your example of "../../../../../etc/passwd%00" is not going to be accepted. In any case, it is really up to you to sanitize, escape and interpret the arguments passed to your endpoint.

  • #154 Andy said 2017-05-16T12:45:08Z

    Miguel, thanks for the info. 1. In one sense, I DO want to filter out things like forward slashes, for security, however, valid email addresses can actually contain them, so I'm not really sure that is correct for my use case! Could you recommend any resources on santizing inputs such as email addresses? It seems like this is a very obvious issue but doesn't seem to be dealt with very often... 2. Also can you recommend some python / flask resources regarding how the api arguments work? e.g. if I choose string, what would it accept/reject, what errors would it throw etc. Thanks!

  • #155 Miguel Grinberg said 2017-05-16T14:56:04Z

    @Andy: you can use marshmallow (http://marshmallow.readthedocs.io/en/latest/) to validate fields that clients provide in your API calls. The process is somewhat similar to what you do with Flask-WTF and WTForms when validating web forms.

  • #156 Rory OConnor said 2017-07-02T18:40:37Z

    Miguel, this is super helpful. I am prototyping an ecommerce API with flask and ember at the frontend (new to both) and was able to implement token-based login with your help. Thanks! My question is, I have routes like /cart/<id> that need to be "protected". i.e. I need to make sure that only the browser requesting gets the response, but do not require a login. Being a newbie, I'm unsure how to handle these types of routes that aren't public but also don't require true authentication (i.e. username/password). I was just using a cookie but I know that's generally frowned upon and are tough to secure. Any help appreciated!

  • #157 Miguel Grinberg said 2017-07-02T21:24:51Z

    @Rory: I answered on the authentication article, so check that out.

  • #158 Wojciech Orlowski said 2017-07-19T09:36:33Z

    Hi Miguel, First of all I want to thank you for all the good effort you make to guide though multiple aspects of dev. I am grateful for your posts, code snippets and every good words you left here on the web! Now back to the topic and the subject open here I would like to ask you a question regarding endpoint='' in the @api.route or rather api.add_resource() section. I cannot understand the use of it even following the DOC: https://flask-restful.readthedocs.io/en/0.3.5/quickstart.html#endpoints "You can also match parts of the path as variables to your resource methods." I cannot neither find the use of it in your code and the usefulness of it in the DOC guide. api.add_resource(Todo, '/todo/<int:todo_id>', endpoint='todo_ep') How does it work and how should I use endpoints in general? My first impression is that if I will POST /todo/3/?todo_ep=hello then todo_ep is the argument in the path according the sentence I pasted, however how then the Resource class is handling this? I must be missing something trivial I am I? Would you be able to help me and perhaps others on this please?

  • #159 Miguel Grinberg said 2017-07-19T13:57:24Z

    @Wojciech: the endpoint is a name you give to your resource, it is not something external that goes in the URL. If you don't want to set your own endpoint names for resources, just remove that from the route definition and Flask-Restful will create an endpoint name for you.

  • #160 Ram Kapil said 2017-08-07T07:43:59Z

    Very helpful.

  • #161 CHAK-PONG CHUNG said 2017-08-18T18:06:08Z

    I am using your code behind proxy, curl gave me a html message from zscalar server. How should I fix it?

  • #162 Miguel Grinberg said 2017-08-18T20:45:21Z

    @chak: Can't really tell you. You likely have a badly configured proxy server.

  • #163 ankur said 2017-09-04T10:50:44Z

    is there a way to use operators like OR , NOT in URL for the api? Right now I am only able to use and functionality

  • #164 Miguel Grinberg said 2017-09-04T16:39:36Z

    @ankur: What do you mean by "and" functionality? Is this for queries that you include in the URL? You can implement any operators that you need, that is totally up to you. Some people pass complex strings or even JSON structures in the query string that specify database queries.

  • #165 Joao said 2017-11-30T01:41:57Z

    Hello mate, I followed both your articles: This one and https://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask I would like to make a connection right now, where I make relationships between tasks and users. How should I do it?

  • #166 Miguel Grinberg said 2017-11-30T04:08:49Z

    @Joao: You need a many-to-many relationship. You can find an example in the Flask-SQLAlchemy documentation, or also in my Mega Tutorial articles.

  • #167 Franky said 2017-12-27T10:34:45Z

    Hi Miguel. Many thank for your article. I've a question about errorhandlers. I decorate a function to handle when exception occur in my Flask Restful App: @the_api.errorhandler def default_error_handler(error): return {'_msg': str(error)}, 500 When exception occurs, I get response: HTTP Code: 500 Response body: { "_msg": "error message here", "message": "error message here" } Could you pls tell me how to skip "message" from response? I want to keep only "_msg" key.

  • #168 Helen said 2017-12-27T16:51:49Z

    I enjoy reading your articles! Can you make an article on Python + Flask unit testing. I am new to this and found unit testing my API program hard. Thank you so much in advance. :)

  • #169 Miguel Grinberg said 2017-12-28T07:24:33Z

    @Helen: there is some information on unit testing Flask apps in my Flask Web Development book, and also in my Flask Mega-Tutorial.

  • #170 Miguel Grinberg said 2017-12-28T07:35:47Z

    @Franky: not entirely sure, but Flask-Restful uses "message" in its own error handling, which may be taking precedence over yours. You may want to read the documentation section on producing custom error responses with Flask-Restful.

Leave a Comment