I have a path like this:
GET http://aaa.com/invoices/{someType}/<bunch of optional query parameters>
It is okay to mix path parameters with regular query parameters in rest api?
Or better use required query parameters maybe there is better approach?
It is okay to mix path parameters with regular query parameters in rest api?
Yes. REST doesn't care what spelling you use for your resource identifiers -- any spelling that complies with the production rules described by RFC 3986 is fine. Information encoded into the URI is done at the server's discretion and for its own use.
From the perspective of a general purpose client, the identifier is the entire URI.
/a/b/c/d
/a/b/c/d?hasOptionalParameter=true
As far as REST is concerned, these are two different identifiers, and therefore two different resources. That you have a single endpoint for them is an implementation detail.
URI Templates allow you to describe "a range of Uniform Resource Identifiers through variable expansion." General purpose templates support variable expansion on both path segments and the query part.
But: one of the most familiar URI Templates is an HTML form; the processing rules for GET perform a replacement of the query part of the form action, but leaves unchanged the path segments. In effect, the path part of the form action URI is protected from change by the client, but the query part gets changed.
Related
What's the most appropriate way in REST to export something as PDF or other document type?
The next example explains my problem:
I have a resource called Banana.
I created all the canonical CRUD rest endpoint for that resource (i.e. GET /bananas; GET /bananas/{id}; POST /bananas/{id}; ...)
Now I need to create an endpoint which downloads a file (PDF, CSV, ..) which contains the representation of all the bananas.
First thing that came to my mind is GET /bananas/export, but in pure rest using verbs in url should not be allowed. Using a more appropriate httpMethod might be cool, something like EXPORT /bananas, but unfortunately this is not (yet?) possible.
Finally I thought about using the Accept header on the same GET /bananas endpoint, which based on the different media type (application/json, application/pdf, ..) returns the corresponding representation of the data (json, pdf, ..), but I'm not sure if I am misusing the Accept header in this way.
Any ideas?
in pure rest using verbs in url should not be allowed.
REST doesn't care what spelling conventions you use in your resource identifiers.
Example: https://www.merriam-webster.com/dictionary/post
Even though "post" is a verb (and worse, an HTTP method token!) that URI works just like every other resource identifier on the web.
The more interesting question, from a REST perspective, is whether the identifier should be the same that is used in some other context, or different.
REST cares a lot about caching (that's important to making the web "web scale"). In HTTP, caching is primarily about re-using prior responses.
The basic (but incomplete) idea being that we may be able to re-use a response that shares the same target URI.
HTTP also has built into it a general purpose mechanism for invalidating stored responses that is also focused on the target URI.
So here's one part of the riddle you need to think about: when someone sends a POST request to /bananas, should caches throw away the prior responses with the PDF representations?
If the answer is "no", then you need a different target URI. That can be anything that makes sense to you. /pdfs/bananas for example. (How many common path segments are used in the identifiers depends on how much convenience you will realize from relative references and dot segments.)
If the answer is "yes", then you may want to lean into using content negotiation.
In some cases, the answer might be "both" -- which is to say, to have multiple resources (each with its own identifier) that return the same representations.
That's a normal thing to do; we even have a mechanism for describing which resource is "preferred" (see RFC 6596).
REST does not care about this, but the HTTP standard does. Using the accept header for the expected MIME type is the standard way of doing this, so you did the right thing. No need to move it to a separate endpoint if the data is the same just the format is different.
Media types are the best way to represent this, but there is a practical aspect of this in that people will browse a rest API using root nouns... I'd put some record-count limits on it, maybe GET /bananas/export/100 to get the first 100, and GET /bananas/export/all if they really want all of them.
In the long documentation page (https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/routing?view=aspnetcore-5.0) I see information that seems a bit contradictory to me:
Conventional routing is order-dependent.
In general, routes with areas should be placed earlier as they're more
specific than routes without an area. Dedicated conventional routes
with *catch-all route parameters like {article} can make a route too
greedy, meaning that it matches URLs that you intended to be
matched by other routes. Put the greedy routes later in the
route table to prevent greedy matches.
So that seems to suggest clearly that order is important and if you put a catch-all template first, it will match all.
Then we continue to the URL matching section:
The routing implementation does not guarantee a processing order for matching endpoints. All possible matches are processed at once. The URL matching phases occur in the following order. ASP.NET Core:
Processes the URL path against the set of endpoints and their route
templates, collecting all of the matches.
Takes the preceding list and removes matches that fail with route constraints applied.
Takes the preceding list and removes matches that fail the set of MatcherPolicy instances.
Uses the EndpointSelector to make a final decision from the preceding list.
The list of endpoints is prioritized according to:
The RouteEndpoint.Order
The route template precedence
All matching endpoints are processed in each phase until the EndpointSelector is
reached. The EndpointSelector is the final phase. It chooses the
highest priority endpoint from the matches as the best match. If there
are other matches with the same priority as the best match, an
ambiguous match exception is thrown.
And a bit further below we see the precedence mechanism explained
Route template precedence is a system that assigns each route template a value based on how specific it is. Route template precedence:
Avoids the need to adjust the order of endpoints in common cases.
Attempts to match the common-sense expectations of routing behavior.
For example, consider templates /Products/List and /Products/{id}. It
would be reasonable to assume that /Products/List is a better match
than /Products/{id} for the URL path /Products/List. This works
because the literal segment /List is considered to have better
precedence than the parameter segment /{id}.
The details of how precedence works are coupled to how route templates
are defined:
Templates with more segments are considered more specific.
A segment with literal text is considered more specific than a parameter
segment.
A parameter segment with a constraint is considered more specific than one without.
A complex segment is considered as specific as a parameter segment with a constraint.
Catch-all parameters are the least specific.
See catch-all in the Route template reference for important information on catch-all routes.
So, to me it seems like precedence is more important than the order (perhaps the order matters only when two route templates have the same precedence score?)
My quick tests seemed to prove that point, but perhaps there's something wrong about my understanding. Or perhaps, the Conventional routing order should be updated to reflect the current behavior.
I am trying to design a REST API that will return detailed information about an identifier that I pass in. For the sake of an example, I am passing in an identifier and returning information about a specific vehicle. The problem that I am facing is that there are many different kinds of vehicles, each with different unique properties. I am wondering if there is a way so that I can only return the relevant details with the REST API.
Currently I plan on having one endpoint /vehicles and passing in the identifier as a parameter.
My current request will consist of something like this GET /vehicles?id=123456
My current response structure will be something like this:
{
"vehicleDetails" : {
"color": "someColor",
"make: "someMake",
"model: "someModel",
"year: "someYear",
"carDetails": {
// some unique car fields
},
"motorcycleDetails" : {
// some unique motorcycle fields
},
"boatDetails" : {
// some unique boat fields
}
}
}
As you can see, there are some fields that are common to all vehicle types, but there are also fields that are unique to a certain type of vehicle, for example boatDetails. As far as I understand, I will have to return the entire resource which will have many empty fields. For example, when I request information about a car, I will still have boat and motorcycle details returned as part of the JSON response, even though they will all be empty. My concern with this is that the response payloads will be rather large when only a small subset of the fields will actually be used by the consumer. Would it make sense to add another parameter to filter the fields that come back? Something like /vehicles?id=123456&type=Car? Then in my code I could manipulate the response structure based on the type parameter? I feel that this violates REST best practices. Any advice into how I could change the design of my API would be appreciated.
Note: I cannot use GraphQL for this and would appreciate input about how I can improve this REST API design
Sure,query parameters (as well as matrix and path parameters) are fine from a REST standpoint. You'll end up with a unique URI that identifies a resource. Responses will be cacheable regardles what type of parameters you use. It is though questionable whether exposing the parameter as query parameter has any advantages over exposing it directly as path paramter, i.e. /vehicles/12345 in that case.
What IMO is more important than the actual form of the URI is the representation format you return. The proposed response format bears the potential of being treated as typed resource instead of utilizing a general, multi-purpose format and propper content-type negotiation. Think of HTML i.e. it defines the syntax and semantics of certain hyper-media controls you can use and any arbitrary Web client will be able to present it and operate on the response format. With custom formats, however, you will miss out on that feature.
If you only have one client, that moreover is under your control, the additional overhead you need to put in is probably not worth aiming for a REST environment in general, as it is easier to change the client once the server stuff changed. Though if you aim for a long-living environment that may be utilized by clients not under your control, this is for sure a thing to consider.
Can I use parameters to change the response structure?
Yes.
Example: https://www.merriam-webster.com/dictionary/JPEG
Notice that -- even though the URI says JPEG, the server is actually returning a text/html document, and the browser knows that from the Content-Type header in the HTTP response.
Identifiers are semantically opaque, in the same way that variable names are. We tend to choose spellings that make things easy for humans, but the machines don't care what spelling conventions we use.
So there's no constraint that says that /vehicles?id=1 and /vehicles?id=2 have to have the same "structure".
So you could have application/prs.rbanders-car, application/prs.rbanders-boat and application/prs.rbanders-rocketship, each with its own specific processing rules.
More likely, though, you'll want to piggyback on some other more common representation; so it's common to see a structured syntax suffix: application/prs.rbanders-car+json`, etc. Effectively, you are promising a json document, but with a more specific schema.
Of course, there's nothing stopping you from creating a application/prs.rbanders-vehicle+json schema, and then describing some fields as optional that will only appear if some condition is met.
Different options, different trade offs.
I feel that this violates REST best practices.
Not really - the important ideas are to handle metadata in a standard way (so that general purpose components can understand what is going on) and to use common formats where it makes sense to do so (to leverage the libraries that are already available).
There's routing for my server:
/api/debts/:id
to get info about particular debt and
/api/debts/search?searchData=something
to search debts by something.
And first route always handles search requests. I want to change it and know how to do it in several ways (just remove /api/debts in the second route etc) but I want to know a good practice for this.
UPD: There's also the route
/api/debts
to get all debts
For search or filter purposes /api/debts?searchData=something is preferred.
In /api/debts/search?searchData=something, this is not considered good, because it has search which it verb and the searchData is outside the resources.
For details please refer
here
I want to change it and know how to do it in several ways (just remove /api/debts in the second route etc) but I want to know a good practice for this.
REST doesn't care what spelling conventions you use for your resource identifiers.
So if you decide, in the support of other concerns, that you want to use a different path, then go ahead and do that.
/debts/search?searchData=something
/search/debts?searchData=something
Those are both fine. Sticking additional path-segments like api on the front is also fine. General purpose clients don't care, because they are just copying the identifier into the request line
this is not considered good, because it has search which it verb
Consider the URI below
https://www.merriam-webster.com/dictionary/search
This works, and exactly the way you would expect, even though "search" happens to be a verb (sometimes) in the English language.
It's even ok to use registered HTTP methods in your identifiers:
GET /dictionary/delete HTTP/1.1
Host: www.merriam-webster.com
You can try that one in your web browser by clicking on
https://www.merriam-webster.com/dictionary/delete
What doesn't work particularly well is assuming that the identifier defines the request semantics; the request method is the appropriate mechanism for communicating the request semantics, not the identifier. In other words
GET /delete
is a request that promises safe semantics; if you handle that request by making a bunch of destructive edits, then that's a fault of the implementation
The confusion here is not Raj's fault, of course -- there is a LOT of literature on the web that (a) describes arbitrary spelling constraints for resource identifiers and (b) cites "REST" as an authority for those constraints.
Part (b) has no factual basis - it's just folklore that has gained traction.
REST doesn't care about spelling conventions for your identifiers in the same way that compilers don't care about spelling conventions for your variable names.
REST cares a lot about when two identifiers are the same, because caching, and particularly cache invalidation are important things for the general purpose components to be able to do correctly.
But the machines don't distinguish between nouns, verbs, hmac codes, rot13 ciphers, etc.
As I understood after reading these links:
How to find out what does dispatcher cache?
http://docs.adobe.com/docs/en/dispatcher.html
The Dispatcher always requests the document directly from the AEM instance in the following cases:
If the HTTP method is not GET. Other common methods are POST for form data and HEAD for the HTTP header.
If the request URI contains a question mark "?". This usually indicates a dynamic page, such as a search result, which does not need to be cached.
The file extension is missing. The web server needs the extension to determine the document type (the MIME-type).
The authentication header is set (this can be configured)
But I want to cache url with parameters.
If I once request myUrl/?p1=1&p2=2&p3=3
then next request to myUrl/?p1=1&p2=2&p3=3 must be served from dispatcher cache, but myUrl/?p1=1&p2=2&p3=3&newParam=newValue should served by CQ for the first time and from dispatcher cache for subsequent requests.
I think the config /ignoreUrlParams is what you are looking for. It can be used to white list the query parameters which are used to determine whether a page is cached / delivered from cache or not.
Check http://docs.adobe.com/docs/en/dispatcher/disp-config.html#Ignoring%20URL%20Parameters for details.
It's not possible to cache the requests that contain query string. Such calls are considered dynamic therefore it should not be expected to cache them.
On the other hand, if you are certain that such request should be cached cause your application/feature is query driven you can work on it this way.
Add Apache rewrite rule that will move the query string of given parameter to selector
(optional) Add a CQ filter that will recognize the selector and move it back to query string
The selector can be constructed in a way: key_value but that puts some constraints on what could be passed here.
You can do this with Apache rewrites BUT it would not be ideal practice. You'll be breaking the pattern that AEM uses.
Instead, use selectors and extensions. E.g. instead of server.com/mypage.html?somevalue=true, use:
server.com/mypage.myvalue-true.html
Most things you will need to do that would ever get cached will work this way just fine. If you give me more details about your requirements and what you are trying to achieve, I can help you perfect the solution.