Skip to content

Instantly share code, notes, and snippets.

@jstnjns
Last active August 20, 2018 20:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jstnjns/5305120 to your computer and use it in GitHub Desktop.
Save jstnjns/5305120 to your computer and use it in GitHub Desktop.
A flexible design for a RESTful API

Request

HTTP Methods

POST

  • Without ID will create a new item in a collection, and return that new item
  • Without ID, with reference_id in payload will create a new item in a collection with the same attributes as the item with a matching id as the reference_id (and use any other attributes in payload as overrides), and return that new item (cloning)
  • With an ID will return error code 405

GET

  • Without ID will return a list of elements in a collection
  • With an ID with an ID will return the single item from the collection

PUT

  • Without ID will return error code 405
  • With an ID will update the specific item, and return that updated item

DELETE

  • Without ID will return error code 405
  • With an ID will remove the existence of an item, and simply return success code 200

URL Structure

The URL should always have a very defined structure. The less we stray from this structure, the quicker we can make assumptions about what the API is doing, or should be doing. Below is the most verbose structure the URL can be:

/api/{version}/{object}/{id}/{related}?{fields|sort|limit|offset|search|filter|expand}

All of the available actions will be stored as their own query parameters, but the values of those will be stringified JSON.

Versioning

/api/v2/organizations

The second parameter of the API is the version of the API.

Object

/api/v2/organizations

Passing the API a data object will return you a list of stored values of that type.

ID

/api/v2/organizations/5

If you are requesting a specific data object (in this case a single organization), pass the ID after the data object.

Related Data

/api/v2/organizations/5/users

In order to get a specific object's related data (in this example an organization's users), pass that object an additional parameter after the ID.

Fields

/api/v2/organizations/5?fields=['id','name','address']

If you would like to only be returned a certain amount of attributes, you can specify them in parenthesis as the value of 'fields' in the query string.

Sorting

/api/v2/organizations?sort=name

When requesting a collection of objects, you may want them sorted alphabetically, by date, or some other parameter. Use the 'sort' parameter in the query string to specify the attribute to sort by. You can pass a single attribute, or multiple if you need secondary sorting.

/api/v2/organizations?sort=['date_created','name']

Pagination

/api/v2/organizations?limit=10&offset=2

If you are requesting a collection of data, and would like to paginate your results, pass the limit (items per page/request) and optional offset (page number, 1 based, defaults to 1).

Search

Getting the results of a search requires that you have a search parameter in the query string with at least key:value pair, key being attribute to search and value being argument to search against.

/api/v2/organizations?search={name:'car'}

Search parameters are fuzzy, so you only need a partial match. Above, searching for 'car' in the name will return results like car, racecar, card and the like.

You can filter by multiple attributes by delimiting them with commas:

/api/v2/organizations?search={name:'car',type:'electric'}

If you want to get data matching two values for a single attribute, you can simply divide those two values with a pipe:

/api/v2/organizations?search={name:'car|boat'}

Filter

Filtering is similar to search, except that it explicitly filters results with the values you give (i.e. not fuzzy). Similar to search, you pass in a key:value pair.

/api/v2/vehicles?filter={type:'car'}

This will only match if the organization type is car.

Alternately, you can filter objects that match a certain range as well by using a double-dot or triple-dot notation. The difference between double and triple dot is:

Double Dot: first argument <# value < second argument

Triple Dot: first argument <# value <# second argument

Date

/api/v2/appointments?filter={start_date:'2012-05-13..2012-06-13'}

Alphabetical

/api/v2/users?filter={first_name:'Coby...Sarah'}

Numeric

/api/v2/organizations?filter={user_count:'5..10'}

Expansions

/api/v2/organizations/5?expand=true

In order to cut down on the amount of requests needed to be able to access data related to the actual data we've requested, we are able to require that one level of related data is also returned in our data set. In this example, we are requiring that all related objects of a specific organization are returned (contacts, issues, deals, projects).

If you'd like to be more selective in the returned data (i.e. only need the organization and it's contacts and deals), you could make a request like:

/api/v2/organizations/5?expand=['contacts','deals']

Response

HTTP Status Codes

Success

For non-error responses, we will simply respond with a 200 code. This means there is no reason to add additional values to the body to indicate success other than the data objects requested.

Error

There are several ways for the API to error, but in order to have the application respond with the correct messages, very specific error codes are required. We will be using HTTP error codes to tell the application what exactly occurred.

  • 400 - Bad input parameter. Validation on a specific parameter(s) failed. Error message should indicate which one(s) and why. This is the only instance that more than the error code is required.
 {
   "parameters" : {
     "email": "Email is incorrectly formatted",
     "occupation" : "Occupation is required"
   }
 }
  • 401 - Bad or expired token. This can happen if the user or Dropbox revoked or expired an access token. To fix, you should re-authenticate the user.
  • 404 - * Not found.* File or folder not found at the specified path.
  • 405 - Request method not expected. If the requested HTTP method does not exist.
  • 500 - Server failure. No error message required.
  • 503 - Rate limiting. Your app is making too many requests and is being rate limited. 503s can trigger on a per-app or per-user basis.

General Notes

Content Theory

When requesting an object or collection from the API, we need to be able to make a few assumptions of the data returned for more rapid development on the application side.

Related Data

When requesting objects that are related to other objects (one-to-one, one-to-many, many-to-many) the root level data returned should be the object the application is requesting. Example:

/api/v2/organizations/4/users

This request is requesting a collection of users that belong to an organization. If the relationship between organization and users is many-to-many, there is probably a corresponding record of the connection (OrganizationUsers). That record should exist nowhere in the returned data, only the collection of users as an array. However, the user records may contain data of the organizations they belong to, minus the connection record.

Expansions

When making a request for an item, or items, with related data it is sometimes beneficial to include the related object as a nested value. Using the organization users example above, we may include the organization as a nested object of the user (instead of just the ID, which we would have to make a separate query to the API to return more data like "name" or other arbitrary values).

The application can then parse the data on return from the API server, creating instances of those object models...giving the application the ability to quickly modify yet another data object from that single API request.

Content Formatting

We need to be able to assume that certain values are going to be returned as certain data types as well. Numbers should not be strings, and dates should be formatted a certain way so that we may quickly parse them. Below is a list of the basic data types we will expect.

  • String - If you don't know what a string is, you're fired. ** like this? -penner http://upload.wikimedia.org/wikipedia/en/thumb/1/1f/Spool_of_string.jpg/220px-Spool_of_string.jpg ** yes, penner...just like that. -justin
  • Int/Float - Integers and floats should not be encapsulated in quotes (those are STRICTLY for strings).
  • Date/Datetime - Sat, 21 Aug 2010 22:31:20 +0000 - Supported by all languages, alleviates issues with timezones.
  • Enums - Enumerable values (values that must be from a set of predetermined values) should ALWAYS being a string, never an integer, that describes the value you are trying to portray. Integers do not describe value, and can easily be misconceived. Strings, however, are easily readable, understandable, and do not require a dictionary to describe the value stored.

Public vs Private

A public API can be requested by any client, token or not. If the API is private and no token (or bad token) is given, returns HTTP Error Code 401.

===Example===

[http://coenraets.org/blog/2011/12/restful-services-with-jquery-and-java-using-jax-rs-and-jersey/]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment