This is a cross-post of a guest post I did for APIUX.  Click here for the original..

URL design discussions for RESTful web services often degrade into debates over pluralization and parameter names. There are a couple of principles I like to use to keep things simple.

1) Using your API should feel like using a filesystem

  • Endpoints used to create, list, and search for entities should look like directories, e.g. /users
  • Use a plural noun so it feels like a directory of users, not a user controller
  • Endpoints used to read, update, and delete individual entities should look like files, e.g. /users/charlie

2) All calls to a given endpoint should return the same type

  • Either apples, or oranges, or a list of oranges, don’t mix them up
  • File-looking endpoints should return individual entities
  • Directory-looking endpoints should return lists of entities


We may be bikeshedding here but I think your API will be more intuitive to newcomers if you model it this way.

  • Consistent response type per endpoint simplifies deserialization for clients, no switching needed
  • Once you agree on a contract it’s easy to mock up with static files on a server
  • Clients can start working with your mockup before your code is finished

Example Operations on Endpoints that look like Files

Read

GET /users/charlie
200 OK
{username: charlie, state: VA}

Update

PUT|PATCH /users/charlie
[password=1111]

Delete

DELETE /users/charlie

Example Operations on Endpoints that Look Like Directories

List

GET /users?start=40&count=20
200 OK
[{username: charlie, state: VA}, ...]

Search

GET /users?q=cha

Create

POST /users
{username=charlie&password=1234}
201 CREATED
Location: /users/charlie

Directory endpoints are supposed to return lists (or nothing).  So send back a pointer to the new user record, rather than the user data itself.

POST /users/charlie

Also ok IFF you allow clients to generate entity ids

Serious Bikeshedding


Here’s other stuff I like:

Use a filename extension instead of the Accept header to express the response format:

http://api.example.com/users/charlie.json

  • Not pure REST but it makes your API easier for devs to poke with curl and the browser. Easier to use means better adoption!
  • Friendlier on stupid caches that improperly handle headers since the endpoint always sends back the same bytes (within the ttl)
  • You can use .html (or no extension) to request the web representation and serve your API and web views with the same controller logic (though this can turn into a rabbit hole)

Another vote against the Accept header: version your endpoints in the URL, at web-application level:

http://q.addthis.com/feeds/1.0/trending.json?pubid=atblog

  • feeds.war is the webapp and 1.0 is the version of the released artifact (which is 1.0.3 internally)
  • Version numbers should correspond to deployable artifacts so they’re easier to manage
  • You’ll need to keep old versions online in real-world use cases

Dropwizard has a nice model for organizing your projects.

Some javascript/flash API consumers will pressure you to add a suppress_response_codes parameter and always send them a 200. You’ll hate it but will end up giving in (just wait).

  • You’ll need to define a standard error response envelope that they can switch on to check for errors and extract the reason. It will get messy for them and they will lose a lot of the benefits of this design.
  • Anyone have better ideas here?