I am working on the backend of a web application that exposes a REST-like API to the frontend of the application.

Currently, I am exposing a "Users" resource, where each user can be represented in JSON notation as:

  "id": "asdfzxcv",
  "name": "Morris",
  "preferences": {
    "dark_mode": true,
    "tags": [  "tagA",  "tagB" ]

I currently expose this user at the endpoint GET /api/v1/users/<user-id>.

Now, I have been given the requirement that the frontend should be able to:

  • Submit a list of tags to be added to the user's preferences
  • Submit a list of tags to be removed from the user's preferences
  • Submit a list of tags to replace the user's preferred tags

Originally, I was thinking the client could do PUT (or DELETE) /api/v1/users/<user-id>/preferences/tags/tagA, but this would entail making multiple requests to add/remove multiple new preferences, which would be very wasteful.

How should I design the URL paths to handle this case?

One possible solution is to use query parameters to change the behavior:

  • POST /api/v1/users/<user-id>/preferences/tags (default action: update/merge)
  • POST /api/v1/users/<user-id>/preferences/tags?action=replace
  • POST /api/v1/users/<user-id>/preferences/tags?action=delete

Or I could have 3 separate URLs:

  • POST /api/v1/users/<user-id>/preferences/tags (default action: update/merge)
  • POST /api/v1/users/<user-id>/preferences/tags/replace
  • POST /api/v1/users/<user-id>/preferences/tags/delete

1 Answer 1


If you want to be "more REST-ish", you can model tags as a single "collection" resource, and support multiple HTTP methods on it:

  • The resource at /api/v1/users/XXX/preferences/tags represents the list of tags for user XXX
  • GET /api/v1/users/XXX/preferences/tags returns a representation of all the current tags for that user
  • PUT /api/v1/users/XXX/preferences/tags replaces the list with a completely new list
  • POST /api/v1/users/XXX/preferences/tags adds a tag to the list
  • DELETE /api/v1/users/XXX/preferences/tags removes all tags from the list
  • PATCH /api/v1/users/XXX/preferences/tags allows changes to the collection

It's that last one which gives you the power to do multiple adds or deletes in one request, even in the same request. All you need is to pick a representation for the changes, which could be something like this:

   "add": ["foo", "bar"],
   "remove": ["tagA"]

(You could also use a standard format, like JSON Patch but it seems less suitable for managing lists.)

  • Thanks for the thoughtful answer. I'm a bit surprised that there isn't a more general-purpose JSON-based spec for submitting modifications to nested data structures. Maybe something like { "tags[add]": ["foo", "bar"], "tags[remove]": ["baz"] }. Although this entails some extra parsing of attribute names. Feb 17, 2021 at 17:35
  • @shadowtalker Well, JSON Patch is exactly that, but it relies on JSON Pointer, which can only reference array elements by position, so it's a bit "clunky" for the current case. A variant using a more powerful selection language like JMESPath might work, but it will probably be easier all around to implement something bespoke but simple, as long as you test and document it carefully.
    – IMSoP
    Feb 17, 2021 at 17:45

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.