RESTful URL design for search

2018-12-31 22:58发布

I'm looking for a reasonable way to represent searches as a RESTful URLs.

The setup: I have two models, Cars and Garages, where Cars can be in Garages. So my urls look like:

/car/xxxx
  xxx == car id
  returns car with given id

/garage/yyy
  yyy = garage id
  returns garage with given id

A Car can exist on its own (hence the /car), or it can exist in a garage. What's the right way to represent, say, all the cars in a given garage? Something like:

/garage/yyy/cars     ?

How about the union of cars in garage yyy and zzz?

What's the right way to represent a search for cars with certain attributes? Say: show me all blue sedans with 4 doors :

/car/search?color=blue&type=sedan&doors=4

or should it be /cars instead?

The use of "search" seems inappropriate there - what's a better way / term? Should it just be:

/cars/?color=blue&type=sedan&doors=4

Should the search parameters be part of the PATHINFO or QUERYSTRING?

In short, I'm looking for guidance for cross-model REST url design, and for search.

[Update] I like Justin's answer, but he doesn't cover the multi-field search case:

/cars/color:blue/type:sedan/doors:4

or something like that. How do we go from

/cars/color/blue

to the multiple field case?

标签: rest
12条回答
姐姐魅力值爆表
2楼-- · 2018-12-31 23:49

To expand on Peter's answer - you could make Search a first-class resource:

POST    /searches          # create a new search
GET     /searches          # list all searches (admin)
GET     /searches/{id}     # show the results of a previously-run search
DELETE  /searches/{id}     # delete a search (admin)

The Search resource would have fields for color, make model, garaged status, etc and could be specified in XML, JSON, or any other format. Like the Car and Garage resource, you could restrict access to Searches based on authentication. Users who frequently run the same Searches can store them in their profiles so that they don't need to be re-created. The URLs will be short enough that in many cases they can be easily traded via email. These stored Searches can be the basis of custom RSS feeds, and so on.

There are many possibilities for using Searches when you think of them as resources.

The idea is explained in more detail in this Railscast.

查看更多
忆尘夕之涩
3楼-- · 2018-12-31 23:49

I use two approaches to implement searches.

1) Simplest case, to query associated elements, and for navigation.

    /cars?q.garage.id.eq=1

This means, query cars that have garage ID equal to 1.

It is also possible to create more complex searches:

    /cars?q.garage.street.eq=FirstStreet&q.color.ne=red&offset=300&max=100

Cars in all garages in FirstStreet that are not red (3rd page, 100 elements per page).

2) Complex queries are considered as regular resources that are created and can be recovered.

    POST /searches  => Create
    GET  /searches/1  => Recover search
    GET  /searches/1?offset=300&max=100  => pagination in search

The POST body for search creation is as follows:

    {  
       "$class":"test.Car",
       "$q":{
          "$eq" : { "color" : "red" },
          "garage" : {
             "$ne" : { "street" : "FirstStreet" }
          }
       }
    }

It is based in Grails (criteria DSL): http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html

查看更多
千与千寻千般痛.
4楼-- · 2018-12-31 23:50

This is not REST. You cannot define URIs for resources inside your API. Resource navigation must be hypertext-driven. It's fine if you want pretty URIs and heavy amounts of coupling, but just do not call it REST, because it directly violates the constraints of RESTful architecture.

See this article by the inventor of REST.

查看更多
高级女魔头
5楼-- · 2018-12-31 23:50

Though I like Justin's response, I feel it more accurately represents a filter rather than a search. What if I want to know about cars with names that start with cam?

The way I see it, you could build it into the way you handle specific resources:
/cars/cam*

Or, you could simply add it into the filter:
/cars/doors/4/name/cam*/colors/red,blue,green

Personally, I prefer the latter, however I am by no means an expert on REST (having first heard of it only 2 or so weeks ago...)

查看更多
怪性笑人.
6楼-- · 2018-12-31 23:53

Although having the parameters in the path has some advantages, there are, IMO, some outweighing factors.

  • Not all characters needed for a search query are permitted in a URL. Most punctuation and Unicode characters would need to be URL encoded as a query string parameter. I'm wrestling with the same problem. I would like to use XPath in the URL, but not all XPath syntax is compatible with a URI path. So for simple paths, /cars/doors/driver/lock/combination would be appropriate to locate the 'combination' element in the driver's door XML document. But /car/doors[id='driver' and lock/combination='1234'] is not so friendly.

  • There is a difference between filtering a resource based on one of its attributes and specifying a resource.

    For example, since

    /cars/colors returns a list of all colors for all cars (the resource returned is a collection of color objects)

    /cars/colors/red,blue,green would return a list of color objects that are red, blue or green, not a collection of cars.

    To return cars, the path would be

    /cars?color=red,blue,green or /cars/search?color=red,blue,green

  • Parameters in the path are more difficult to read because name/value pairs are not isolated from the rest of the path, which is not name/value pairs.

One last comment. I prefer /garages/yyy/cars (always plural) to /garage/yyy/cars (perhaps it was a typo in the original answer) because it avoids changing the path between singular and plural. For words with an added 's', the change is not so bad, but changing /person/yyy/friends to /people/yyy seems cumbersome.

查看更多
琉璃瓶的回忆
7楼-- · 2019-01-01 00:01

For the searching, use querystrings. This is perfectly RESTful:

/cars?color=blue&type=sedan&doors=4

An advantage to regular querystrings is that they are standard and widely understood and that they can be generated from form-get.

查看更多
登录 后发表回答