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
  • #51 Miguel Grinberg said

    @Marcelo: yes, that is perfectly fine, I should have updated the article, since I'm the one who contributed the "absolute=True" feature to Flask-RESTful.

  • #52 Marcelo said

    Cool! keep up the good work! I'm already accessing portions of your book's preview via Safari Online, and can't wait until it's fully published. Btw, since I was also doing an intro to MongoDB, I created a version of your flask-restful example using MongoDB as the backend for storing the tasks. More than happy to contribute the example back if that helps.

  • #53 Miguel Grinberg said

    @Marcelo: I wouldn't mind adding the example to the repository so that people can compare the two implementations. Send me a pull request!

  • #54 Frode Egeland said

    Thank you for this very useful tutorial. Helps a lot!

    If we wanted to break it into a package with multiple files (as per http://flask.pocoo.org/docs/patterns/packages/ ), for example so the DB models live in models.py, and so on, how would the structure and imports of the various bits and pieces look?
    This might be obvious to you, but I'm still learning.. :-)

  • #55 Matt said

    I'm quite new to programming, and sadly, I got started with Django as I heard some Flask plugins weren't supported in Python 3. I've invested a bit of time in Django to make a switch to Flask but I must say I found the 2, Flask Rest tutorials very easy to follow even though I haven't written a single line of Flask in my life:(
    I wasn't totally clear on REST before but after reading these tutorials I fully understand it. Thanks a bunch:) 10/10

  • #56 Khezer said

    Hi Miguel

    Thanks for the great tutorial.
    I have an problem with adding resource in some blueprints
    like this codes

    <h2>app.py</h2>

    from flask import Flask
    from flask.ext.restful import Api

    api = Api()

    app = Flask(name)

    <h1>Initial api for app</h1>

    api.init_app(app)

    from assets import assets as assets_blueprint
    app.register_blueprint(assets_blueprint)

    if name == 'main':
    app.run(debug=True)

    =======================
    assets/init.py

    <hr />

    from flask import Blueprint

    assets = Blueprint('assets', name)

    from . import assets_list

    =================
    assets/assets_list.py

    <hr />

    from flask.ext.restful import Resource
    from rest import api

    class AssetsList(Resource):
    def get(self):
    return {'data': 'ok'}

    api.add_resource(AssetsList, '/list')

    After running the url i give 404 error in
    localhost:5000/list

    where is problem?

    Thanks for helpping

  • #57 Miguel Grinberg said

    @Khezer: not sure. One odd thing is your "from rest import api" statementt, because the api definition is in your app.py module. Also, using a blueprint is not necessary, Flask-RESTful creates its own routes.

  • #58 Khezer said

    @Miguel: Thanks for replay.

    "from rest import api" its typo! in my sample code app.py file name is rest.py! and i copy/pasting the code any way
    I found problem and change the code like this:

    <h2>app.py</h2>

    from flask import Flask

    app = Flask(name)

    from assets import assets as assets_blueprint
    app.register_blueprint(assets_blueprint)

    if name == 'main':
    app.run(debug=True)

    ===========================
    assets/init.py

    <hr />

    from flask import Blueprint
    from flask.ext.restful import Api

    assets = Blueprint('assets', name)

    api = Api(assets)

    from . import assets_list

    ========================================
    assets/assets_list.py

    <hr />

    from flask.ext.restful import Resource
    from . import api

    class AssetsList(Resource):
    def get(self):
    return {'data': 'ok'}

    api.add_resource(AssetsList, '/list')

    I define api in assets blueprint and its works!

    after looking your seminar in pycon 2014 I decide using your way.

  • #59 Raghuram said

    Hey, great tutorial!! I was wondering if in the get method to get a particular task by providing the task id, what if the task id contains a forward slash eg. 1/2 then how do we call the get method, I have a similar issue with my get method and I tried using everything using double quotes, using %2F instead of "/" but nothing worked!!! can somebody help me please!!!

  • #60 Miguel Grinberg said

    @Raghuram: don't use a slash in your IDs, use a dash or an underscore.

  • #61 Pablo said

    Great, thank you it helped a lot.

  • #62 Otger said

    Thank you Miguel for this tutorials, they are easy to follow and give a lot of insight into the subject.

    Could you give me some indications on how to solve the CORS problem. Whenever I try to load the json through the ajax request, it fails. On the console it ouputs (Chrome):

    XMLHttpRequest cannot load http://localhost:5000/todo/api/v1.0/tasks?_=1404315571445. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3333' is therefore not allowed access.
    ajax error: {"readyState":0,"responseText":"","status":0,"statusText":"error"}

    Where this header should be present? When serving the index.html or when serving the json? Or both?

    Thanks in advance

  • #63 Miguel Grinberg said

    @Otger: There is a pretty good example of the exchange required to implement CORS in the Wikipedia page: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing.
    It seems your app is running on port 3333 and your Ajax API server is on 5000, correct? In that case the responses from the API server on 3333 must have a Access-Control-Allow-Origin header that refers to the http://localhost:3333 as an allowed origin.

  • #64 Selcuk Bozdag said

    Thanks Miguel, I read the two tutorials and tried your sample application. It was very neat. You might think of including a section about authorisation besides authentication.

  • #65 Miguel Grinberg said

    @Selcuk: I discuss authorization and include an implementation of roles in my book.

  • #66 John Q said

    I'm still fuzzy on how you would secure this API when consumed (legitimately) via another web page on another site.

    They would have HTML or JavaScript making calls to your API and anyone looking at the source code could see it. How would they pass their credentials to your API without someone looking at source being able to see the credentials. I'm thinking "single page interface" apps where the front end folks are simply provided an API reference from which they make calls (not on same server)

  • #67 Miguel Grinberg said

    @John: the credentials will be entered by the user, they will not be in the HTML/JS of the client side app. In this example it is the user who logs in, not the app. There are other situations where a client app needs to pass its own credentials to the server (maybe in addition to those of the user) and in that case the client app credentials would be in the source code an exposed. But that's not the case here.

  • #68 Romy Nguyen said

    A great tutorial, thank you Miguel about Flask RESTFul API

  • #69 sebastian said

    How would you solve this:
    GET /users/abc123/inboxes

    With flask-restful?? the username is variable, but i dont see how i could access the inboxes from the User resource, or if I have a InboxList resource, how would i send it the username as a parameter?

    will add_resources(InboxList, '/users/<string:username>/inboxes') work?
    Thanks!

  • #70 Miguel Grinberg said

    @sebastian: The "user" resource is only good for users. If you need to work with user's inboxes that is a different resource, and in that case you will create a route like the one you proposed.

  • #71 fird0s said

    HI Miguel, possible that POST image/pdf type ?

    thanks

  • #72 i3 said

    Hey, thank you for your very easy introduction.

    How can I handle the problem, when I want to POST something using @login_required, but want to use the username also in my "normal" function and not only in getPassword(user) ??

  • #73 Miguel Grinberg said

    @i3: you can write any values to the "g" variable when you are in the password validation function and these will still be there when your route handler function is invoked.

  • #74 Luigi said

    Sadly I got:
    File "/Library/Python/2.7/site-packages/flask/exthook.py", line 87, in load_module
    raise ImportError('No module named %s' % fullname)
    ImportError: No module named flask.ext.restful

    anybody with same issue?

  • #75 spitz said

    I am somewhat confused on the best way to handle templates in a flask-restful website. For example, I have a website with a menu that links to many different .html templates. Normally, I would do something like:

    @app.route("/some_template")
    def some_template():
    return render_template("some_template.html")

    For my resources (databases, etc.) I would use flask-restful. However, this mixing of @app.route and api.add_resource does not seem very elegant. Should templates be rendered from within flask-restful? In my case I have templates that make different calls to different resources.

Leave a Comment