Quickstart

It’s time to write your first REST API. This guide assumes you have a working understanding of Flask, and that you have already installed both Flask and Flask-RESTful. If not, then follow the steps in the Installation section.

A Minimal API

A minimal Flask-RESTful API looks like this:

  1. from flask import Flask
  2. from flask_restful import Resource, Api
  3. app = Flask(__name__)
  4. api = Api(app)
  5. class HelloWorld(Resource):
  6. def get(self):
  7. return {'hello': 'world'}
  8. api.add_resource(HelloWorld, '/')
  9. if __name__ == '__main__':
  10. app.run(debug=True)

Save this as api.py and run it using your Python interpreter. Note that we’ve enabled Flask debugging mode to provide code reloading and better error messages.

  1. $ python api.py
  2. * Running on http://127.0.0.1:5000/
  3. * Restarting with reloader

Warning

Debug mode should never be used in a production environment!

Now open up a new prompt to test out your API using curl

  1. $ curl http://127.0.0.1:5000/
  2. {"hello": "world"}

Resourceful Routing

The main building block provided by Flask-RESTful are resources. Resources are built on top of Flask pluggable views, giving you easy access to multiple HTTP methods just by defining methods on your resource. A basic CRUD resource for a todo application (of course) looks like this:

  1. from flask import Flask, request
  2. from flask_restful import Resource, Api
  3. app = Flask(__name__)
  4. api = Api(app)
  5. todos = {}
  6. class TodoSimple(Resource):
  7. def get(self, todo_id):
  8. return {todo_id: todos[todo_id]}
  9. def put(self, todo_id):
  10. todos[todo_id] = request.form['data']
  11. return {todo_id: todos[todo_id]}
  12. api.add_resource(TodoSimple, '/<string:todo_id>')
  13. if __name__ == '__main__':
  14. app.run(debug=True)

You can try it like this:

  1. $ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT
  2. {"todo1": "Remember the milk"}
  3. $ curl http://localhost:5000/todo1
  4. {"todo1": "Remember the milk"}
  5. $ curl http://localhost:5000/todo2 -d "data=Change my brakepads" -X PUT
  6. {"todo2": "Change my brakepads"}
  7. $ curl http://localhost:5000/todo2
  8. {"todo2": "Change my brakepads"}

Or from python if you have the requests library installed:

  1. >>> from requests import put, get
  2. >>> put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json()
  3. {u'todo1': u'Remember the milk'}
  4. >>> get('http://localhost:5000/todo1').json()
  5. {u'todo1': u'Remember the milk'}
  6. >>> put('http://localhost:5000/todo2', data={'data': 'Change my brakepads'}).json()
  7. {u'todo2': u'Change my brakepads'}
  8. >>> get('http://localhost:5000/todo2').json()
  9. {u'todo2': u'Change my brakepads'}

Flask-RESTful understands multiple kinds of return values from view methods. Similar to Flask, you can return any iterable and it will be converted into a response, including raw Flask response objects. Flask-RESTful also support setting the response code and response headers using multiple return values, as shown below:

  1. class Todo1(Resource):
  2. def get(self):
  3. # Default to 200 OK
  4. return {'task': 'Hello world'}
  5. class Todo2(Resource):
  6. def get(self):
  7. # Set the response code to 201
  8. return {'task': 'Hello world'}, 201
  9. class Todo3(Resource):
  10. def get(self):
  11. # Set the response code to 201 and return custom headers
  12. return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'}

Endpoints

Many times in an API, your resource will have multiple URLs. You can pass multiple URLs to the add_resource() method on the Api object. Each one will be routed to your Resource

  1. api.add_resource(HelloWorld,
  2. '/',
  3. '/hello')

You can also match parts of the path as variables to your resource methods.

  1. api.add_resource(Todo,
  2. '/todo/<int:todo_id>', endpoint='todo_ep')

Note

If a request does not match any of your application’s endpoints, Flask-RESTful will return a 404 error message with suggestions of other endpoints that closely match the requested endpoint. This can be disabled by setting ERROR_404_HELP to False in your application config.

Argument Parsing

While Flask provides easy access to request data (i.e. querystring or POST form encoded data), it’s still a pain to validate form data. Flask-RESTful has built-in support for request data validation using a library similar to argparse.

  1. from flask_restful import reqparse
  2. parser = reqparse.RequestParser()
  3. parser.add_argument('rate', type=int, help='Rate to charge for this resource')
  4. args = parser.parse_args()

Note

Unlike the argparse module, reqparse.RequestParser.parse_args() returns a Python dictionary instead of a custom data structure.

Using the reqparse module also gives you sane error messages for free. If an argument fails to pass validation, Flask-RESTful will respond with a 400 Bad Request and a response highlighting the error.

  1. $ curl -d 'rate=foo' http://127.0.0.1:5000/todos
  2. {'status': 400, 'message': 'foo cannot be converted to int'}

The inputs module provides a number of included common conversion functions such as inputs.date() and inputs.url().

Calling parse_args with strict=True ensures that an error is thrown if the request includes arguments your parser does not define.

  1. args = parser.parse_args(strict=True)

Data Formatting

By default, all fields in your return iterable will be rendered as-is. While this works great when you’re just dealing with Python data structures, it can become very frustrating when working with objects. To solve this problem, Flask-RESTful provides the fields module and the marshal_with() decorator. Similar to the Django ORM and WTForm, you use the fields module to describe the structure of your response.

  1. from collections import OrderedDict
  2. from flask_restful import fields, marshal_with
  3. resource_fields = {
  4. 'task': fields.String,
  5. 'uri': fields.Url('todo_ep')
  6. }
  7. class TodoDao(object):
  8. def __init__(self, todo_id, task):
  9. self.todo_id = todo_id
  10. self.task = task
  11. # This field will not be sent in the response
  12. self.status = 'active'
  13. class Todo(Resource):
  14. @marshal_with(resource_fields)
  15. def get(self, **kwargs):
  16. return TodoDao(todo_id='my_todo', task='Remember the milk')

The above example takes a python object and prepares it to be serialized. The marshal_with() decorator will apply the transformation described by resource_fields. The only field extracted from the object is task. The fields.Url field is a special field that takes an endpoint name and generates a URL for that endpoint in the response. Many of the field types you need are already included. See the fields guide for a complete list.

Full Example

Save this example in api.py

  1. from flask import Flask
  2. from flask_restful import reqparse, abort, Api, Resource
  3. app = Flask(__name__)
  4. api = Api(app)
  5. TODOS = {
  6. 'todo1': {'task': 'build an API'},
  7. 'todo2': {'task': '?????'},
  8. 'todo3': {'task': 'profit!'},
  9. }
  10. def abort_if_todo_doesnt_exist(todo_id):
  11. if todo_id not in TODOS:
  12. abort(404, message="Todo {} doesn't exist".format(todo_id))
  13. parser = reqparse.RequestParser()
  14. parser.add_argument('task')
  15. # Todo
  16. # shows a single todo item and lets you delete a todo item
  17. class Todo(Resource):
  18. def get(self, todo_id):
  19. abort_if_todo_doesnt_exist(todo_id)
  20. return TODOS[todo_id]
  21. def delete(self, todo_id):
  22. abort_if_todo_doesnt_exist(todo_id)
  23. del TODOS[todo_id]
  24. return '', 204
  25. def put(self, todo_id):
  26. args = parser.parse_args()
  27. task = {'task': args['task']}
  28. TODOS[todo_id] = task
  29. return task, 201
  30. # TodoList
  31. # shows a list of all todos, and lets you POST to add new tasks
  32. class TodoList(Resource):
  33. def get(self):
  34. return TODOS
  35. def post(self):
  36. args = parser.parse_args()
  37. todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
  38. todo_id = 'todo%i' % todo_id
  39. TODOS[todo_id] = {'task': args['task']}
  40. return TODOS[todo_id], 201
  41. ##
  42. ## Actually setup the Api resource routing here
  43. ##
  44. api.add_resource(TodoList, '/todos')
  45. api.add_resource(Todo, '/todos/<todo_id>')
  46. if __name__ == '__main__':
  47. app.run(debug=True)

Example usage

  1. $ python api.py
  2. * Running on http://127.0.0.1:5000/
  3. * Restarting with reloader

GET the list

  1. $ curl http://localhost:5000/todos
  2. {"todo1": {"task": "build an API"}, "todo3": {"task": "profit!"}, "todo2": {"task": "?????"}}

GET a single task

  1. $ curl http://localhost:5000/todos/todo3
  2. {"task": "profit!"}

DELETE a task

  1. $ curl http://localhost:5000/todos/todo2 -X DELETE -v
  2. > DELETE /todos/todo2 HTTP/1.1
  3. > User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
  4. > Host: localhost:5000
  5. > Accept: */*
  6. >
  7. * HTTP 1.0, assume close after body
  8. < HTTP/1.0 204 NO CONTENT
  9. < Content-Type: application/json
  10. < Content-Length: 0
  11. < Server: Werkzeug/0.8.3 Python/2.7.2
  12. < Date: Mon, 01 Oct 2012 22:10:32 GMT

Add a new task

  1. $ curl http://localhost:5000/todos -d "task=something new" -X POST -v
  2. > POST /todos HTTP/1.1
  3. > User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
  4. > Host: localhost:5000
  5. > Accept: */*
  6. > Content-Length: 18
  7. > Content-Type: application/x-www-form-urlencoded
  8. >
  9. * HTTP 1.0, assume close after body
  10. < HTTP/1.0 201 CREATED
  11. < Content-Type: application/json
  12. < Content-Length: 25
  13. < Server: Werkzeug/0.8.3 Python/2.7.2
  14. < Date: Mon, 01 Oct 2012 22:12:58 GMT
  15. <
  16. * Closing connection #0
  17. {"task": "something new"}

Update a task

  1. $ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v
  2. > PUT /todos/todo3 HTTP/1.1
  3. > Host: localhost:5000
  4. > Accept: */*
  5. > Content-Length: 20
  6. > Content-Type: application/x-www-form-urlencoded
  7. >
  8. * HTTP 1.0, assume close after body
  9. < HTTP/1.0 201 CREATED
  10. < Content-Type: application/json
  11. < Content-Length: 27
  12. < Server: Werkzeug/0.8.3 Python/2.7.3
  13. < Date: Mon, 01 Oct 2012 22:13:00 GMT
  14. <
  15. * Closing connection #0
  16. {"task": "something different"}

Logo

Table Of Contents

Related Topics

This Page

Quick search