Skip to content

Instantly share code, notes, and snippets.

@ppizarro
Last active May 28, 2024 23:01
Show Gist options
  • Save ppizarro/17ec2da1f1a6e48726b5 to your computer and use it in GitHub Desktop.
Save ppizarro/17ec2da1f1a6e48726b5 to your computer and use it in GitHub Desktop.
Pragmatic REST

Pragmatic REST

  • Nouns are good; verbs are bad
  • Keep your base URL simple and intuitive
  • Only 2 base URLs per resource
  • For a collection
/dogs
  • For a specific element
/dogs/1234
  • Keep verbs out of your base URLs
  • Use HTTP verbs to operate on the collections and elements
POST, GET, PUT and DELETE: CRUD (Create-Read-Update-Delete)
Resource POST (create) GET (read) PUT (update) DELETE (delete)
/dogs Create a new dog List dogs Bulk update dogs Delete all dogs
/dogs/1234 Error Show Dog If exists update Dog If not error Delete Dog
  • Use plural nouns (avoid a mixed model in wich you use singular for some resources and plural for others)
  • Concrete names are better than abstract (/items, /assets)
 /blogs
 /videos
 /news
 /articles

Associations - Relationships to other resources

  • Simplify associations - sweep complexity under the '?'

  • To get all the dogs belonging to a specific owner

GET /owners/5678/dogs
  • To create a new dog for that owner
POST /owners/5678/dogs
  • Now, the relationships can be complex. Owners have relationships with veterinarians, who have relationships with dogs, who have relationships with food, and so on. It's not uncommon to see people string these together making a URL 5 or 6 levels deep. Remember that once you have the primary key for one level, you usually don't need to include the levels above because you've already got your specific object. In other words, you shouldn't need too many cases where a URL is deeper than what we have above.
/resource/identifier/resource
  • Make it simple for developers to use the base URL by putting optional states and attributes behind the HTTP question mark. To get all red dogs running in the park:
GET /dogs?color=red&state=running&location=park

Handling Errors

  • Use HTTP status codes
  • Use a small subset of the HTTP status codes:
200 - OK (Everything worked - success)
400 - Bad Request (The application did something wrong - client error)
500 - Internal Server Error (The API did something wrong - server error)
  • If you're not comfortable reducing all your error conditions to these 3, try picking among these additional 5:
201 - Created
304 - Not Modified
404 - Not found
401 - Unauthorized
403 - Forbidden
  • Make messages returned in the payload as verbose as possible
{"developerMessage" : "Verbose, plain language description of
the problem for the app developer with hints about how to fix
it.", "userMessage":"Pass this message on to the app user if
needed.", "errorCode" : 12345, "more info":
"http://dev.teachdogrest.com/errors/12345"}

In summary, be verbose and use plain language descriptions. Add as many hints as your API team can think of about what's causing an error.

Versioning

  • Never release an API without a version and make the version mandatory
/v1/dogs
/v2/dogs
  • Use a simple ordinal number. Don't use the dot notation like v1.2 because it implies a granularity of versioning that doesn't work weel with APIs -- It's an interface not an implementation. Stick with v1, v2, and so on.

  • How many versions should you maintain? Maintain at least one version back.

  • For how long should you maintain a version? Give developers at least one cycle to react before obsoleting a version.

Partial response

  • Add optional fields in a comma-delimited list:
/dogs?fields=name,color,location
  • It's simple to read
  • A developer can select just the information an app needs at a given time
  • It cuts down on bandwidth issues, wich is important for mobile apps.
  • The partial selection syntax can also be used to include associated resources cutting down on the number of requests needed to get the required information.

Pagination

  • Use limit and offset
/dogs?limit=25&offset=50
  • You must include metadata with each response that is paginated that indicated to the developer the total number of records available.
  • Defaults values: limit=10&offset=0

Actions not be dealing with a "resource"

  • Use verbs not nouns
  • For example: Calculate, Translate, Convert
/convert?from=EUR&to=CNY&amount=100
  • Make it clear in your API documentation that these "non-resource" scenarios are different.

Data format

  • Adds a sufix to define the format
  • Use JSON as default
GET /dogs (returns JSON)
GET /dogs.json (returns JSON)
GET /dogs.xml (returns XML)

Attribute names - JSON

  • Follow JavaScript conventions for naming attributes.
  • Use medial capitalization - CamelCase.
  • Use uppercase or lowercase depending on type of object.
"createdAt": 1320296464

Tips for search

  • Global search: (?q represents the query)
/search?q=fluffy+fur
/search.xml?q=fluffy+fur
  • Scoped search
/owners/5678/dogs?q=fluffy+fur

Example - Making request on your API

Lets take a look at what some API requests and responses look like for our dogs API. Create a brown dog named Al

POST /dogs
name=Al&furColor=brown

Response

200 OK
{
"dog":{
"id": "1234",
"name": "Al",
"furColor": "brown"
}
}

Rename Al to Rover - Update

PUT /dogs/1234
name=Rover

Response

200 OK
{
"dog":{
"id":"1234",
"name": "Rover",
"furColor": "brown"
}
}

Tell me about a particular dog

GET /dogs/1234

Response

200 OK
{
"dog":{
"id":"1234",
"name": "Rover",
"furColor": "brown"
}
}

Tell me about all the dogs

GET /dogs

Response

200 OK
{
"dogs":
[{"dog":{
"id":"1233",
"name": "Fido",
"furColor": "white"}},
{"dog":{
"id":"1234",
"name": "Rover",
"furColor": "brown"}}]
"_metadata":
[{"totalCount":327,"limit":25,"offset":100}]
}

Delete Rover :-(

DELETE /dogs/1234

Response

200 OK

Documentation - API Mock - Collaborative design

Material

References

MULLOY, Brian. Web API Design: Crafting Interfaces that Developers Love. Apigee.

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