Quickly Prototype APIs with Apiary


February 19, 2014
Written by
Elmer Thomas
Contributor
Opinions expressed by Twilio contributors are their own

ApiaryI love the idea of API Driven Development (ADD). It provides great flexibility and by default makes your app developer friendly from the beginning. The challenge is that it's hard to demo an API without an application tied to it. The goal of this post is to help you get past the API design and creation phase as quickly as possible, so you can focus on creating killer apps and encourage others to build upon your exposed data. This tutorial should be especially useful for hackathons and prototypes.

With the APIary.io platform we’ll create documentation that your whole team can interact with and contribute to. Then, we’ll create a working version of the API using Python/Flask. To demonstrate the process in a tangible way, we’ll go through the process of creating a simple GTD ToDo list API.

Define the Dataset

First, we must define what data needs to be exposed via the API. My tool of choice for this type of planning is a mind map. Our Lead Developer Evangelist, Brandon West, has written a great post on Why Mind Maps are Awesomesauce. I am using square brackets to denote arrays.

gtd-todo-mindmap

Once you’ve defined your data, use the resource/attribute paradigm to group the data points. A resource is any item that will be referenced or acted upon. Each resource may have attributes that help define the resource. The mind map above demonstrates this visually. The first level of nodes are resources and the second level is attributes.

Translate the Dataset to API Endpoints

Now that we know what data we are exposing, we need to organize it such that we can create endpoints that developers can use. This is simply a matter of listing out the final URLs as demonstrated below.

/folder /project /task /context

For the base URL, it’s good practice to use a format similar to api.yourdomain.com. You can then host your docs at docs.yourdomain.com and serve your developer community at developer.yourdomain.com. Reserve yourdomain.com for your marketing.

Use Apiary.io to Create a Prototype

Now that we know the endpoints to build, we will use Apiary.io to rapidly prototype and begin getting feedback. In this example, I’ll only define the folder resource and its attributes. Go ahead and grab a free account if you want to follow along.

With Apiary.io, you first create an API Blueprint in markdown, then using their tools, that markdown is converted to interactive documentation, complete with a mock server. For our example, the following is the API Blueprint:



Here is the generated documentation and the mock server. Go ahead and click around and explore.

Now, let’s go through each section and understand what each piece of markdown is doing.

Metadata

The FORMAT label is used to define the version of the API blueprint and the HOST defines the official URL of the API. The full details are at APIary.io’s GitHub.

Define the Folder Resource

Note that in many cases, you’ll have a heading before the resource called a Group, used to help organize related resources. In our case, we start at heading two (as defined in markdown by to hash tags). In square brackets we describe the endpoint. The curly braces are used to describe any parameters. If you have more than one, separate them with commas. Prepend a question mark if your parameter does have its own endpoint, such as in the case where you want to provide an operator on your resource. For example, you may want to have a limit parameter to define how many folders you want to return.

It’s good practice to separate and define the required and optional attributes.

After the attributes are described, we formally define the parameter and describe whether or not it is required and the data type.

Optionally, we can define a Model. Think of a Model as a template. Later in our API Blueprint, we can simply use the name of the Model and in the docs the Model name will be expanded.

Defining the Operations

For each operation, you will define the responses and optionally, the requests.

Returning arrays of data

To represent the retrieval of a list of your resources, use a Collection. You may want to add parameters that limit the results, such as a start and end date or a limit.

At this point, you can share the API with stakeholders and get some feedback before you start writing any code. If you have not been following along, check the results here.

If your API needs are more complex that this example, be sure to check out the API Blueprint examples on GitHub.

From Prototype to Live API with Flask

Now that we have reached a “final” version of the prototype, let's translate that data to a functional API using Python and Flask. You can find the source code on Github and the steps to get the API setup on your local machine. Following is the Python/Flask implementation of the prototype we developed with APIary.io:



Finally, you will want to use Dredd to test your API Docs against your implementation.

Resources and References

This is only the beginning. If you want to go deeper, I suggest you check out the following resources. To get that last one, you’ll have to join our awesome team :) Now that you know how to rapidly prototype an API, take a moment and share what you build with us.

Happy Hacking!

Send With Confidence

Partner with the email service trusted by developers and marketers for time-savings, scalability, and delivery expertise.

FORMAT: 1A
HOST: http://api.gtdtodoapi.com
# GTD TODO API
This is an example API, written as a companion to a blog post at SendGrid.com
## Folder [/folder{id}]
A single Folder object, it represents a single folder.
Required attributes:
- `id` Automatically assigned
- `name`
- `description`
Optional attributes:
- `parent` ID of folder that is the parent. Set to 0 if no parent
- `meta` A catch-all attribute to add custom features
+ Parameters
+ id (required, int) ... Unique folder ID in the form of an integer
+ Model (application/hal+json)
+ Body
{
"id": 1,
"name": "Health",
"description": "This represents projects that are related to health"
"parent": 0,
"meta": "NULL"
}
## Retrieve a single Folder [GET]
+ Response 200 (application/json)
[Folder][]
+ Response 404 (application/json)
{
"error": "Resource not found"
}
### Edit a Folder [PATCH]
+ Request (application/json)
{
"description": "A collection of health related projects",
}
+ Response 200
[Folder][]
+ Response 404
{
"error": "Resource not found"
}
+ Response 400
{
"error": "Resource modification failed"
}
## Delete a Folder [DELETE]
+ Response 200
{
"result": True
}
## Create a Folder [POST]
+ Request (application/json)
{
"name": "Diet",
"description": "A collection of projects related to Diet",
"parent": 1
}
+ Response 201
[Folder][]
+ Response 400
{
"error": "Resource modification failed"
}
# Folder Collection [/folder]
Get all of the Folders.
+ Model (application/hal+json)
+ Body
{
"folders": [
{
"id": 1,
"name": "Health",
"description": "This represents projects that are related to health"
"parent": 0,
"meta": "NULL"
},
{
"id": 1,
"name": "Diet",
"description": "A collection of projects related to Diet",
"parent": 1,
"meta": "NULL"
}
]
}
## List all Folders [GET]
+ Response 200
[Folder Collection][]
#!flask/bin/python
from flask import Flask, jsonify, abort, make_response, request
app = Flask(__name__)
folders = [
{
"id": 1,
"name": "Health",
"description": "This represents projects that are related to health",
"parent": 0,
"meta": "NULL"
},
{
"id": 2,
"name": "Diet",
"description": "A collection of projects related to Diet",
"parent": 1,
"meta": "NULL"
}
]
@app.route('/folder/<int:folder_id>', methods = ['GET'])
def get_folder(folder_id):
folder = filter(lambda t: t['id'] == folder_id, folders)
if len(folder) == 0:
abort(404)
return jsonify( folder[0] )
@app.route('/folder', methods = ['GET'])
def get_folders():
return jsonify( { 'folders': folders } )
@app.route('/folder', methods = ['POST'])
def create_folder():
if (not request.json) or (not 'name' in request.json) or (not 'description' in request.json):
abort(400)
folder = {
'id': folders[-1]['id'] + 1,
'name': request.json['name'],
'description': request.json['description'],
'parent': request.json['parent'],
'meta': request.json.get('meta', "NULL")
}
folders.append(folder)
return jsonify( folder ), 201
@app.route('/folder/<int:folder_id>', methods = ['PATCH'])
def update_folder(folder_id):
folder = filter(lambda t: t['id'] == folder_id, folders)
if len(folder) == 0:
abort(404)
if not request.json:
abort(400)
if 'name' in request.json and type(request.json['name']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is not unicode:
abort(400)
if 'parent' in request.json and type(request.json['parent']) is not int:
abort(400)
if 'meta' in request.json and type(request.json['meta']) is not str:
abort(400)
folder[0]['name'] = request.json.get('name', folder[0]['name'])
folder[0]['description'] = request.json.get('description', folder[0]['description'])
folder[0]['parent'] = request.json.get('parent', folder[0]['parent'])
folder[0]['meta'] = request.json.get('meta', folder[0]['meta'])
return jsonify( folder[0] )
@app.route('/folder/<int:folder_id>', methods = ['DELETE'])
def delete_folder(folder_id):
folder = filter(lambda t: t['id'] == folder_id, folders)
if len(folder) == 0:
abort(404)
folders.remove(folder[0])
return jsonify( { "result": True } )
@app.errorhandler(404)
def not_found(error):
return make_response(jsonify( { "error": "Resource not found" } ), 404)
@app.errorhandler(400)
def create_failed(error):
return make_response(jsonify( { "error": "Resource modification failed" } ), 400)
if __name__ == '__main__':
app.run(debug = True)
view raw app.py hosted with ❤ by GitHub