Search code examples
restrestful-architecturerestful-url

Should I pass variables through the path section or query section of my REST API?


I'm building an app that will allow users to manage groups. Each group contains a name, campus, and data type. They are not in any particular hierarchy - semantically, a campus could be considered to have many groups, but it would be also natural to consider a group at the top of the hierarchy, spread out over many campuses.

A combination of name/campus/data_type is unique

NAME          CAMPUS          DATA_TYPE
---------------------------------------
LABS          WEST            IPv4
LABS          WEST            IPv6
LABS          EAST            IPv4
USERS         NORTH           userids
USERS         WEST            userids
USERS         EAST            userids

So for example, the LABS group for the WEST campus with DATA_TYPE of IPv4 will contain all the IP subnets related to west-campus labs.

Now, the only requirement for drilling down to this data is by group. It is not a requirement to gather a list of all WEST campus groups, for example, or all groups that have a "IPv6" data type. However it is necessary to get a list of all campuses that have a "LABS" group, and it is also necessary to get all the data_types for LABS.

So how should I create my endpoints?

Option 1

Long, but fairly clear URLs.

GET  /groups/LABS/                 (returns LABS groups across all campuses and data_types)
GET  /groups/LABS/data_type/IPv4   (returns all IPv4 LABS groups across all campuses)
GET  /groups/LABS/campus/WEST      (returns all WEST LABS groups across all data_types)
POST /groups/LABS/campus/NORTH/data_type/IPv4    (create a new group)
POST /groups/LABS/campus/NORTH/data_type/userids (another new group)

ADVANTAGE:

  • avoids any query parameters

DISADVANTAGES:

  • it represents a certain hierarchy that is not actually necessary.
  • It also requires the app to support both group->campus->data_type and group->data_type->campus hierarchies.

Option 2

Treat the group as the only part of the hierarchy, and treat "campus" and "data_type" as non-hierarchical identifiers:

GET  /groups/LABS
GET  /groups/LABS?campus=WEST
GET  /groups/LABS?data_type=IPv4
GET  /groups/LABS?campus=WEST&data_type=IPv4
POST /groups/LABS  (POST data: {campus: "WEST", data_type: "IPv4})
POST /groups/LABS  (POST data: {campus: "WEST", data_type: "IPv4})

ADVANTAGE:

  • it seems to reflect the non-hierarchy of "data_types" and "campus", and makes it easy to specify (say) campus without a data_type (or vice versa).

DISADVANTAGES:

  • I'm not totally sure this is an appropriate use of query parameters.
  • Furthermore it does not provide a very pretty "permalink" to any unique combination of group/campus/data_type.

I'm leaning towards option 2. Is that the best way to represent this data? Or am I thinking about it wrong?


Solution

  • I would suggest instead supporting two endpoints. It's clear from your description that a group is not uniquely defined by a name, but your URI structure implies that it is. Instead, use a synthetic id to uniquely identify a group.

    GET /groups
      ?name={}
      ?campus={}
      ?data_type={}
    <- some collection of all groups that match whichever criteria are specified
    
    POST /groups
    -> { "name": "LABS", "campus": "WEST", "data_type": "IPv4" }
    
    GET /groups/{id}
    <- { "name": "LABS", "campus": "WEST", "data_type": "IPv4" }
    

    This approach gives you more flexibility for in the future when they decide to add a new property, or want to search by data_type across all groups.

    You can either include unique ids in responses from the server (meh) or include hypermedia links to give you interesting relationships, such as all other groups with the same name, or on the same campus.