David ten Hove
David ten Hove

Reputation: 2826

Conflicting REST urls

So I'm building a REST api and need to make some urls. The problem is, I'm running into some conflicting paths. For example:

This simplified example shows a problem occurs when an object has id "summary". What is the best way to solve this? From a REST puritan perspective, what should be the solution?

Here's some of my ideas:

Upvotes: 6

Views: 1763

Answers (6)

The cleanest way in my opinion to make REST and RPC endpoints work together looks like this:

POST ./api/objects - Create object

GET ./api/objects - Search (filters and pagination in query)

GET ./api/objects/{id} - Get by id

PATCH ./api/objects/{id} - Update by id (you can also PUT, but PATCH is easier to version)

DELETE ./api/objects/{id} - Delete by id

Everything else, literally everything else, it is better to register by RPC rules

POST ./api/objects/clear - Delete objects by list

POST ./api/objects/analyse - Get analytics

POST ./api/objects/{id}/summary - Your example.

Additionally, I'll leave a few other good options:

  1. ./api/objects/clear -> ./api/objects:clear (Beautiful, but different family)
  2. ./api/objects/clear -> ./api/objects-clear (Often seen, but layered + different family)

Upvotes: 0

Brandon Demeria
Brandon Demeria

Reputation: 236

This isn't perfectly conventional for how some like to define their rest endpoints.

But I would would enforce a pattern where "id" cannot be any string. Instead I would use a uuid and define my routes as such.

GET /books/{id:uuid}

GET /books/{id:uuid}/summary

And if you really want a verb in the URL without an identifier it is still technically possible because we know the {id:uuid} in the path must conform to the uuid pattern.

With that GET /books/summary is still distinct from GET /books/{id:uuid}

Upvotes: 0

trans
trans

Reputation: 1441

I have the same issue. And all the solutions seem a little off b/c REST best practices seem to suggest none of them are ideal.

You could have just one off-limit id, like all.

  • GET <type>/<id>
  • GET <type>/all/summary

It might even be possible to use a single symbol instead, such as ~ or _.

  • GET <type>/<id>
  • GET <type>/~/summary

How satisfying this solution seems is of course very subjective.

The singular/plural approach seems more elegant to me but despite most REST best practice guides saying not to do this. Unfortunately some words don't have distinct singular and plural forms.

Upvotes: 1

Christiaan Willemsen
Christiaan Willemsen

Reputation: 141

I may have an alternative to this. What if we have both book as wel as the plural books. Then you can have:

/book/{id}

and

/books/summary

or

/books/count

Upvotes: 4

laurent
laurent

Reputation: 90863

The URL structure is not quite right to begin with so it's difficult to solve it in a clean way.

For the sake of discussion, let's assume <type> is a books resource. So the first URL is fine - you get a book of the given ID:

GET /books/<id>

However this is not:

GET /books/summary

Because it's a bespoke URL, which I guess has a use in your application but is not restful. A GET call should return one or more resources. However a "summary" is not a resource, it's a property of a resource and that's why you end up in this situation of having IDs mixed up with book properties.

So your best option would be to change this URL to something like this:

GET /books?fields=summary

By default GET /books would return all the resources, while GET /books?fields=<list_of_fields> will return the books but with only the chosen properties.

That will be similar to your previous URL but without the ID/property conflict, and will also allow you later on to retrieve resources with specific fields (without having to create new custom URLs).

Edit:

Regarding the count of books, it's still useful to reason in terms of resources. /books gives you one or more books, but it should not be used for meta-information about the collection, such as count, but also things like "most read book", or "books that start with the letter 'A'", etc. as that will make the resource more and more complex and difficult to maintain.

Depending on what you want to achieve I think there'd be two solutions:

  1. Create a new resource that manages the collection of books. For example:

    GET /bookcase

And that will give you information about the collection, for example:

{
    "count": 1234,
    "most_read": "<isbn>",
    // etc. - any information that might be needed about the book collection
}
  1. Or a search engine. You create a resources such as:

    GET /book_search_engine/?query=

which would return a search result such as:

{
    "count": 123,
    "books": [
        // Books that match the query
    ]
}

then a query like this would give you just the count:

// Search all the books, but provide only the "count" field
GET /book_search/?query=*&fields=count

Obviously that's a more involved solution and maybe not necessary for a simple REST API, however it can be useful as it makes it easier to create queries specific to a client.

Upvotes: 2

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57387

This simplified example shows a problem occurs when an object has id "summary". What is the best way to solve this? From a REST puritan perspective, what should be the solution?

As far as REST is concerned, the URI are opaque. Spelling is absolutely irrelevant. You could use URI like

/a575cc90-2878-41fe-9eec-f420a509e1f0
/f871fff6-4c4e-48f7-83a4-26858fdb3096

and as far as REST is concerned, that's spot on. See Stefan Tilkov's talk REST: I Don't Think It Means What You Think It Does.

What you are asking about is URI design, how to adapt conventions/best practices to your particular setting.

One thing that will help is to recognize is that summary is a resource, in the REST/HTTP sense -- it is a document that can be represented as a byte sequence. All you need to do is figure out where that resource belongs (according to your local spelling conventions).

Continuing to borrow the "books" example used by others

# Here's the familiar "URI that identifies a member of the books collection"
/books/<id>

# Here's the summary of the /books collection
/summaries/books

Put the in query parameters. From what I understand this is against standards

Not as much as you might think. REST doesn't care. The URI spec expresses some views about hierarchical vs non hierarchical data. HTTP supports the notion of a redirect, where one resource can reference another.

GET /books?id=12345

302 Found
Location: /books/12345

You also have options for skipping a round trip, by returning the representation you want immediately, taking advantage of Content-Location

GET /books?summary

200 OK
Content-Location: /summaries/books

...

Upvotes: 2

Related Questions