Skip to content

Implementing Sub-Collections

  • In REST API design, resources are typically organized hierarchically.
  • A sub-collection typically represents a nested resource under a parent resource (e.g., /players/{player_id}/goals goals scored by a specific player).

Response Structure of a READ operation on a Sub-Collection

Section titled “Response Structure of a READ operation on a Sub-Collection”

A well-structured JSON response for a sub-collection typically includes:

  1. Parent context: Details about the parent resource for clarity, if relevant. Optional but recommended. This reinforces the hierarchical relationship and can reduce the need for additional requests.
  2. The sub-collection data: An array of items representing the sub-collection.
  3. Pagination metadata: Information about the sub-collection, such as total count, pagination details, or filters applied.
  4. Links: Hypermedia links (optional but recommended) for navigation (e.g., to the parent resource or next page).

  1. Use plural keys for collections: Name the endpoint and array to reflect the collection (e.g., players, not player).
  2. Use descriptive keys: Name the sub-collection clearly (e.g., "goals" instead of just "data" if it adds context).
  3. Embed parent context: Include parent details only if necessary.
  4. Support pagination: For large sub-collections, add pagination metadata (e.g., page, limit, total).
  5. Use standard HTTP status codes: Return 200 for success, 404 if the parent resource isn’t found, etc.
  6. Follow a naming convention: Stick to camelCase, snake_case, or kebab-case based on your API’s style.
  7. Error handling: If the sub-collection is empty, return an empty array ("data": []) rather than null or an error (unless the parent resource doesn’t exist, which might result in a 404 case).

Below is a model method that demonstrates an implementation of properly structured response of a READ operation on a sub-collection.

  • Endpoint: GET /players/{player_id}/goals
  • Description: Get the goals scored by a specific player.
Example: Producing a structure response for a sub-collection with parent resource context
// This method was implemented in the PlayersModel class.
public function getGoalsByPlayerID(string $player_id): mixed
{
// 1) Fetch the details of the player parent resource.
$player = $this->getPlayerById($player_id);
//NOTE: The content of the PHP heredoc must be properly indented.
// @see: https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
$goals_query = <<<SQL
SELECT * FROM goals g, tournaments t,
matches m
WHERE g.tournament_id = t.tournament_id
AND g.match_id = m.match_id
AND player_id = :player_id
SQL;
// 2) Fetch the items of the goals sub-collection along with the tournament and match info.
$goals = $this->paginate(
$goals_query,
["player_id" => $player_id]
);
// 3) Produce a well structured response for the goals sub-collection.
$results = [
"player" => $player,
"meta" => $goals["meta"],
"goals" => $goals["data"],
];
return $results;
}
php

  • Endpoint: GET /players/{player_id}/goals
  • URI: GET /players/P-00020/goals
Example: Structure of an empty sub-collection response with parent resource context
{
"player": {...}, // Parent resource context
"meta": { // Sub-collection pagination metadata
"total_items": 0,
"offset": 0,
"current_page": 1,
"page_size": 5,
"total_pages": 0
},
"goals": [] // Sub-collection data items
}
json
  • Empty array indicates no goals.
  • Returns 200 status (assuming the player exists).

Example: Sub-Collection with Parent Context

Section titled “Example: Sub-Collection with Parent Context”
  • Endpoint: GET /players/{player_id}/goals
  • URI: GET /players/P-28154/goals

Note:

  • "player" provides context about the parent resource.
  • "goals" is the sub-collection (named specifically instead of "data") and contains the data items.
Example: Structure of a sub-collection response with parent resource context
{
"player": { // Parent resource context
"key_id": 6557,
"player_id": "P-28154",
"family_name": "Müller",
"given_name": "Thomas",
"birth_date": "1989-09-13",
"female": 0,
"goal_keeper": 0,
"defender": 0,
"midfielder": 1,
"forward": 1,
"count_tournaments": 4,
"list_tournaments": "2010, 2014, 2018, 2022",
"player_wikipedia_link": "https://en.wikipedia.org/wiki/Thomas_M%C3%BCller"
},
"meta": { // Sub-collection pagination metadata
"total_items": 10,
"offset": 0,
"current_page": 1,
"page_size": 5,
"total_pages": 2
},
"goals": [ // Sub-collection data items
{
"goal_id": "G-2614",
"tournament_id": "WC-2010",
"match_id": "M-2010-08",
"team_id": "T-31",
"home_team": 1,
"away_team": 0,
"player_id": "P-28154",
"shirt_number": 13,
"player_team_id": "T-31",
"minute_label": "68'",
"minute_regulation": 68,
"minute_stoppage": 0,
"match_period": "second half",
"own_goal": 0,
...
}
]
}
json