0

I have some REST API endpoints set up to create a blog post. Each blog Post is owned by a user. I have the following routes:

/users [GET, POST]: Get a list of all users, POST to this endpoint to create a new user
/users/<id> [GET]: Get info for the specified user
/users/<id>/posts [GET]: Get all posts for the specified user

Now the question I have is, when creating a new Post, do I post to this endpoint and provide a {'user_id': <user_id>} in the POST payload:

/posts

Or use this endpoint which would not require the user_id in the payload:

/users/<id>/posts

My instinct tells me it should POST to /posts and provide the user_id as part of the payload but if I'm making a post for a specific user, it should use the latter case. \

4 Answers 4

1

From the perspective of creating a new post, the user_id is just another attribute, like the title or body text. The fact that GET /users/:id/posts returns a list of posts for that user is irrelevant when creating a new post. Remember that REST endpoints are a conceptual representation of something, not actual files on a disk.

If GET /users/:id/posts returns a collection of objects, I would be surprised to find a POST endpoint to the same path when creating a new post. I typically feel a PUT to a collection is more appropriate, which then modifies the collection rather than creating a whole new resource.

Instead, think about the GET, POST, PUT, and DELETE verbs being a cohesive set of actions on a resource. I would expect:

  • POST /posts to create a new post.
  • PUT /posts/:id to update a post.
  • GET /posts/:id to return a single post.
  • DELETE /posts/:id to delete a single post.
  • GET /posts to return a collection of posts.
    • GET /posts?user_id=NNN can just as easily return posts for a single user as GET /users/:id/posts. That is a matter of documentation for your API.
0

You should use

/GetUserPostsById?id={id}
/AddPosts
[{
  id:
  userid: 
  content:
}]

The problem with parameters in the path, especially with nested objects is that it becomes hard to disambiguate the endpoint.

for example consider:

/GetPostsByUserIdAndCategoryId?id=ewan&category=hottake
/users/ewan/posts/hottake

/GetUsersWithIdStartingWithAndPostsInCategoryId?idStartsWith=e&category=hottake
/users/e/posts/hottake

You are identifying parameters by ordinal and mixing in route identifiers in the same path string. Its a hot mess

0
/users/<id>/posts [GET]: Get all posts for the specified user

The first thing that I notice is that we would expect the representation of this resource to change when a new post is added. Therefore, I would normally want the HTTP request that causes this resource to change to reference the same target URI, so that we can take advantage of general-purpose cache invalidation.

Therefore, I would argue for

POST /users/<id>/posts

The key idea here being that the URI we use is the same - the actual spelling of the URI doesn't matter very much. The same principle would hold for

GET /ff1fe3f4-de8e-4fbc-b7e9-c43f5a9a6859
POST /ff1fe3f4-de8e-4fbc-b7e9-c43f5a9a6859

If caching isn't important to you, then you can reasonably design your interface so that read and write use different resource identifiers. I'd expect a PR that makes another choice to include a decision record (see Nygard 2011, or Kruchten 2009) describing the analysis done to understand the tradeoff balance.


Looking at Laiv's answer, I'm reminded that I should probably be more clear about the mechanism I'm suggesting.

Assuming that the server wants to announce the creation of a new resource, and that the server is going to choose the identifier of the new resource, the interaction would look like:

POST /users/<id>/posts 

...
201 Created
Location: /somewhere-chosen-by-the-server

General purpose HTTP components aren't going to care what spelling conventions are used in the URI of the new resource. So /users/123/posts/6789 would be fine, as would /posts/6789 or /91ca7e41-8298-450d-8d8f-9327f8d64867. It's an opaque identifier.

(Which isn't to say that it doesn't matter - humans will want identifiers that are sensible, in much the same way that we want variable names to be sensible.)


If all you are really doing is modifying a single resource (ie: posts aren't represented by individual resources, but instead are data embedded within the /user//posts resource), then we certainly want to be using /user//posts as the target URI, because (again) that's the important document we are changing, and general purpose caches will know what to do.

0

In addition to the answers above.

Assuming posts are created by the blog's owner and owners MUST be authenticated and authorized.1

POST /posts 
Authorization: xxxxxxxxxxxxx

No user_id. Neither in the path nor the payload.

The user's ID is implicit. It's known by the server because the authorization is attached to the request (stateless server) or it's already in the session (stateful server).

Them you can allow bloggers to search their blog posts

<!-- mines -->
GET /posts
Authorization: xxxxxxxxxxxxx

Again, the user_id is already implicit. It's not like I will search/list someone else's post. So why do I need /users/{id}/post?

By the same reasoning, authenticated user's data could be accessible at

GET /me
Authorization: xxxxxxxxxxxxx

Once more, no id whatsoever.

A reason to support user_id in previous routes could be if we had a back office or a browser to navigate through bloggers. But that would be a good reason to make another API. Managing blogs and posts with the browser's API is possible and we could argue that it's convenient but, IMO, it's a bad decision. Command-query responsibility segregation (as a design strategy) exists for a reason.

Finally, it has been already commented but I will stress it a big more.

It's not mandatory that the resource sent by POST is (1:1) the same resource retrieved by GET. Otherwise, we couldn't create resources with data unknown by the client.


/blog/{bid}/posts if owners manage different blogs from the same account or|and session

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.