REST APIs: Why I think of resources as of collections

I love simplicity, trust me. I love when things make sense. I love when things are easy to understand. This article, however, denies everything stated. Continue at your own risk.

Take for example URL like this: http://socialnetwork.com/martin_adamko. It’s clear that by loading such URL, you’ll see my profile on the social network. As a user, I surely am assigned the user ID, probably a number like 123456789.

These URLs could be therefor effectively the same resource:

  • http://socialnetwork.com/123456789
  • http://socialnetwork.com/martin_adamko
  • http://socialnetwork.com/me

What could be different is the context depending whether I’m logged in or not, whether it’s me looking at the resource with the id 123456789.

Lets look at the more REST way, you might say. The martin_adamko endpoint does not reflect the fact it’s my user profile. Some API’s prepend /users endpoint before such resources to distinguish types of resources. It also creates a namespace, so the collision of using the same username and the endpoint such as /users is unlikely to happen.

There are downsides to this approach, but I start to realize how useful it could be. Imagine each endpoint to be unique.

Endpoints vs. Collections

Users service example

Imagine a service to manage users, which would reside at any of URLs like: http://myawesomeapp.com/users or http://users.myawesomeapp.com or at http://myawesomeapp.com/services/users. Whatever the combination of hostname, path, port, the endpoint is a service, and for the simplicity, it only manages nothing but users and provides some basic functionality, like to publish short posts.

The URL could be be http://users as well. Service requires user only to pick a username. To create a user you POST required data about the user to the service, sth. like this:

POST http://users

{
  "username": "martin_adamko"
}

Let’s say such service would provide UUID v4 for each new user created, e.g. 72c2c78a-3e14-46fb-9b5d-e4426a3f9574. Response from service is a user object and it would look like this:

HTTP/1.1 201 Created
Location: http://users/72c2c78a-3e14-46fb-9b5d-e4426a3f9574

{
  "id": "72c2c78a-3e14-46fb-9b5d-e4426a3f9574",
  "username": "martin_adamko",
  "href": [...]
}

Bam! There’s your own profile resource you can bookmark, send to your friends to check out. It’s complicated but it’s unique. It would be prettier to use URL like http://users/martin_adamko? It would be also easier to remember.

Such service, it’s a good one, deeply considering it’s users feelings and user experience. It supports both URI to access your profile:

  • http://users/martin_adamko
  • http://users/72c2c78a-3e14-46fb-9b5d-e4426a3f9574

The full response with hrefs of the user object would look like:

HTTP/1.1 201 Created
Location: http://users/72c2c78a-3e14-46fb-9b5d-e4426a3f9574

{
  "id": "72c2c78a-3e14-46fb-9b5d-e4426a3f9574",
  "username": "martin_adamko",
  "href": [
    "http://users/72c2c78a-3e14-46fb-9b5d-e4426a3f9574",
    "http://users/martin_adamko"
  ]
}

Using these URLs you’ll be able to access your profile. Let’s think of them as aliases to the same resource.

Publishing your first post

Understanding the attribute collections

As you can see, the user object already has 3 attributes: id, username and href. Using REST way to access the attributes would look like this:

  • GET http://users/martin_adamko/id
  • GET http://users/martin_adamko/username
  • GET http://users/martin_adamko/href

To change/replace any of the resource parameters (besides the id from obvious reasons), we could use PUT method like this:

PUT http://users/martin_adamko/username

"martinadamko"

Response with the matching payload reflecting the change took place:

HTTP/1.1 200 OK

"martinadamko"

You may ask: What would happen if I use POST method instead of PUT?

If the service could create a new username for me, provided the internals allow it. Let’s see the result:

POST http://users/martin_adamko/username

"martinadamko"

Response:

HTTP/1.1 200 OK

["martin_adamko", "martinadamko"]

OK, I see your eyebrows twist. It’s unusual, because you’re rational about one user having just one username.

But by default, posting to any resource suggests that something new is created in that collection.

So from now on we’re able to think of a user attribute as of a collection too.

Users posts is an attribute, but also it’s a collection

POST http://users/martin_adamko/posts

{
  "title": "My first post ever!",
  "content": "Hello world! This is my first post ever."
}

Result:

HTTP/1.1 201 Created
Location: http://users/c352870c-022f-4fd3-b327-ea03646bf0ef

{
  "id": "c352870c-022f-4fd3-b327-ea03646bf0ef",
  "title": "My first post ever",
  "content": "Hello world! This is my first post ever",
  "slug": "my-first-post-ever",
  "href": [
    "http://users/c352870c-022f-4fd3-b327-ea03646bf0ef"
    "http://users/martin-adamko/posts/my-fist-post-ever"
  ]
}

Now I see your face twisted: “How in the world this could be OK? There’s no difference in the user object URL and post object URL!”

I see, the URLs for user and it’s post don’t say much about the object type:

  • user: “http://users/72c2c78a-3e14-46fb-9b5d-e4426a3f9574”
  • post: “http://users/c352870c-022f-4fd3-b327-ea03646bf0ef”

You might say: “Hey buddy, the URLs look the same and if you read it it says:

  • USER with ID 72c2c78a-3e14-46fb-9b5d-e4426a3f9574
  • USER with ID c352870c-022f-4fd3-b327-ea03646bf0ef

Let me remind you, the name of the service – users could be anything. Would the problem arrise? See:

  • “http://myunnamedservice/72c2c78a-3e14-46fb-9b5d-e4426a3f9574”
  • “http://myunnamedservice/c352870c-022f-4fd3-b327-ea03646bf0ef”

Basicly, what I say, the resource can be identified. Only NO assumptions are made in your head.

Now let’s complicate things a little bit and rename the service to something you might have already seen some places:

  • “http://socialnetwork.com/users/72c2c78a-3e14-46fb-9b5d-e4426a3f9574”
  • “http://socialnetwork.com/posts/c352870c-022f-4fd3-b327-ea03646bf0ef”

This looks reasonable. What are the assumptions we can make here:

  1. The service is based at http://socialnetwork.com/
  2. The service can create new users and posts (plus other methods of CRUD)
  3. Posts and users reside under /users, /posts endpoints respectively

Lets loo at it from another perspective:

  1. There are two services:
    1. http://socialnetwork.com/users
    2. http://socialnetwork.com/posts
  2. Each service handles CRUD separatelly

From this perspective:

  1. What is endpoint?
  2. What is collection?
  3. What is the service?

Could it be that the resource/service/collection is simply the same thing?

If it’s true, there’s something wrong about saying some part of the URI has some specific meaning. It would be like making assumptions, which cold be proved wrong.

API that embrances collections

Post about WIP