I started creating an application which uses strings as key, in order to have readably, non-guessable API values:
GET https://myApi.com/docs/obuxn6xhzg
GET https://myApi.com/docs/qxfj1g40xf
PUT https://myApi.com/docs/jgtw2vsqqh (--> to update the item, for example)
as opposed to having https://myApi.com/docs/1
.. https://myApi.com/docs/99
However, I'm now struggling to find a descent API endpoint to pass in actions. For example an archiveActive action could be represented by POST https://myApi.com/docs/archiveActive
.
With this URL however, I'm starting to think there might be an ambiguity where the application might consider "archiveActive" as a document id.
So basically, I guess I'm asking if this is something I really should avoid doing, and if so, if there is an alternative way of approaching this when using string ids.
3 Answers 3
What you're describing would technically work and would not contradict any hard rules.
However there are some conventions. For example that the path structure reflects the object structure a bit. That means GET https://myApi.com/docs
should normally list all documents, just like a directory.
Also, by convention URIs should be things (resources), not verbs, so findAll
sounds off a bit.
Also, if you want to retrieve data from the server (read-only operation), those should be GET
operations, not POST
.
Again, these are just conventions, and you can safely ignore them if you have reason to do so. URIs can be basically anything you wish. They can contain IDs of any sort, numbers or strings, they can "overload" the ID namespace with special values (like findAll
) or none of the above. It is up to the server.
-
findAll
was indeed a bad example; consider for example a/docs/archiveActive
action, which archives all active documents I have access toNsevens– Nsevens2020年01月09日 15:43:22 +00:00Commented Jan 9, 2020 at 15:43 -
1@Nsevens I think what Robert is saying is that you the URI should be a noun, not a verb. The verbs are your operations (
PUT
,POST
,GET
, etc.) The simple solution is you add another level to your path e.g.POST
'/docs/archive/active'. The framework should be able to distinguish between the two paths.JimmyJames– JimmyJames2020年01月09日 15:51:54 +00:00Commented Jan 9, 2020 at 15:51 -
@Nsevens JimmyJames is right, the URIs should be normally nouns (things). What exactly they are is irrelevant. Can be
POST /docs/archiveOptions
,/docs/archive
,/docsarchive
,/docs/archive/active
. Again, it could be anything, there are no rules against any URIs. It may however indicate poor style and that some behind-the-scenes concepts may not be followed properly.Robert Bräutigam– Robert Bräutigam2020年01月09日 16:00:35 +00:00Commented Jan 9, 2020 at 16:00 -
But in that case
/docs/archive
could indicate either the action to archive something, or a document with idarchive
? The chance of that being an actual id are quite low, but still...Nsevens– Nsevens2020年01月09日 16:03:36 +00:00Commented Jan 9, 2020 at 16:03 -
1@Nsevens Yes, if you would choose this URI, you would have to make sure the server doesn't assign
archive
as a document id. I would choose something outside of the/docs
prefix frankly, but it is still quite possible and as I said, there are no hard rules against it. As the client should receive all URIs from the server it is irrelevant for the client, or in worst case it can check for it too.Robert Bräutigam– Robert Bräutigam2020年01月09日 17:15:25 +00:00Commented Jan 9, 2020 at 17:15
I guess I'm asking if this is something I really should avoid doing
No, you don't need to avoid doing this -- a URI is an identifier; clients and intermediate components should not be trying to extract any semantic information from them (think "surrogate key" in a database).
Mark Seemann writes about avoiding hackable urls.
Pay attention to the replies, especially Dan Kubb's suggestion to use HMAC with a server side secret to distinguish server generated identifiers from others without sacrificing readability. It may be an easier fit for your use case than hashing/encrypting the entire target-uri.
There are also some edge cases you need to consider - like what you do with a URI template? Or (equivalently) a GET form in HTML?
That said, you might want to consider carefully whether opaque URI are really going to give you a competitive advantage in your domain? Is the time you are going to invest in bespoke request routing going to be paid back somewhere?
Several people have suggested that instead of having POST /docs/findAll
to list all documents the URL schema should be GET /docs
. While true, sometimes a GET
request simply does not cut it due to its length limitations. In a case where you would like to have a very complex search engine on a certain entity type, e.g. the documents, accepting nested objects,... representing searching criteria, it's completely valid to have an endpoint for searching represented using POST
HTTP method because it allows you to pass in large body (instead of passing it as a query parameter).
This problem aside, while following at least some good REST practices, you should never really encounter the issue you're worried about, that is unless you start generating identifier containing slashes (which I strongly recommend you not to do).
In your example, by following some basic REST principles:
- creating a new document should be
POST /api/docs
, - getting a specific document should be
GET /api/docs/(doc-id)
, - updating a specific document should be
PUT /api/docs/(doc-id)
orPATCH /api/docs/(doc-id)
(depending on the use case).
As you can see, the endpoint with the format POST /api/docs/(something...)
is still free and not conflicting with anything previously created. Therefore the mismatched resolution of endpoint you're worried about cannot happen.
In a scenario where you would e.g. allow adding attachments to documents, you would have a new endpoint, ideally looking like this: POST /api/docs/(doc-is)/attachments
, so your POST /api/docs/findAll
wouldn't be conflicting with this one either, because the schema differs. Unless, as I have mentioned, you actually allow generating identifiers with slashes in them and on the extremely rare occasion you would end up with an identifier looking for example like this: x6bFAK6/attachments
. If you decided to read or update the document with such strange id, you could end up hitting the following endpoints:
GET /api/docs/x6bFAK6/attachments
,PUT /api/docs/x6bFAK6/attachments
,
which any solid REST framework should refuse to process, because neither GET
nor PUT
method is acceptable by the POST /api/docs/(doc-id)/attachments
endpoint, and a standalone POST /api/docs/(doc-id)
shouldn't exist either (as mentioned earlier in this post).
/api/docs/obuxn6xhzg
is a document, while/api/findAll
is a verb.POST /docs/findAll
, you would useGET /docs/
. Maybe you used FindAll as an example action, but then it is not a very good example. In fact, under REST rules, all actions should be indicated by the HTTP verb and the URL only indicates the (sub-)resource to take the action on./docs/archiveActive
, which archives all active documents I have access toarchiveActive
being confused for a document id, most frameworks evaluate explicit paths before they evaluate templated ones. If you've built your own framework, that's how you solve the problem. So there should be little reason for that naming conflict.