A resource is identified by its type and ID. Types should be plural, and they should be identical to the resource type in the path. IDs should be strings, but they can be numeric strings if that's what you like. Don't try to encode data in your IDs; your clients should not be able to infer anything about your resources from the IDs alone, except for the fact that they identify a specific resource.
GET /posts/1
{
"data": {
"type": "posts",
"id": "1"
}
}
The goal here, as always, is to be boring and explicit.
Your POSTs should work the same way, but in reverse. You send this same kind of object (without an ID), and you get back the saved version of that object, which is usually identical to what you sent but now includes an ID.
Besides your root object, the only JSON objects in your responses should be resources, meaning an object should never appear without type
and id
attributes. If I'm a client and I have an object saved somewhere, I should be able to confidently fetch a new copy of it by taking its type
and id
, building a path, and making a GET request to it.
In addition to type
and id
, a resource has two kinds of data: attributes and relationships. Most of the data in a resource will be attributes, which are flat and primitive; no arrays or objects. Things like names, creation timestamps, and whatever else your clients may need are available here.
Dates and times should be strings that conform to RFC3339, by the way. All this means is dates are in the format "YYYY-MM-DD" and times are in UTC in the format "YYYY-MM-DDTHH:MM:SSZ".
Relationships are a little more complicated, but no less predictable. A to-one relationship is defined just like an attribute, and it ends with _id
or Id
(depending on if your keys are snake_case
or camelCase
). The value is just a string, and it's the ID of the related resource. If you're dealing with a lot of polymorphic relationships, you may want to represent this as a minimal resource instead of a string (i.e. an object with "type"
and "id"
keys, but nothing else). Your client will know how to fetch those resources if they need to. Avoid the tempation of embedding an entire related resource inside your primary resource.
To-many relationships will usually not be defined directly on the resource. Instead, they're retrievable with that third path pattern we specified.
If my Article resource has a relationship called categories, which is a to-many relationship to a collection of Category resources, I can retrieve them like so:
GET /articles/1/categories
{
"data": [
{ "type": "categories", "id": "1", ... },
{ "type": "categories", "id": "2", ... },
{ "type": "categories", "id": "3", ... }
]
}
The format of this response is identical to the response for GET /categories
. However, /categories
returns all Categories, while /articles/1/categories
returns only the Categories that relate to Article 1.
You might want to expose to-one relationships like this as well. If an Article can only relate to one Category, and Article 1 relates to Category 2, then GET /articles/1/category
should return an identical response to GET /categories/2
. Note that the first one uses category
, singular, while the second one uses categories
, plural; that's because category
is used here not as a resource type but as a relationship name. The type
attribute on the data returned will still be "categories"
.
If you really need to, you can embed the IDs of a to-many relationship on the resource as an attribute whose value is an array of strings. You should only do this if the client will have to specify these related resources at the time of creating the primary resource, like so:
POST /articles
{
"data": {
"title": "How to write a cool program",
"category_ids": ["1", "2", "3"]
}
}