Implementing Sub-Collections
What Are Sub-Collections in a REST API?
Section titled “What Are Sub-Collections in a REST API?”- 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:
- 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.
- The sub-collection data: An array of items representing the sub-collection.
- Pagination metadata: Information about the sub-collection, such as total count, pagination details, or filters applied.
- Links: Hypermedia links (optional but recommended) for navigation (e.g., to the parent resource or next page).
Guidelines for Sub-Collection Responses
Section titled “Guidelines for Sub-Collection Responses”- Use plural keys for collections: Name the endpoint and array to reflect the collection (e.g.,
players
, notplayer
). - Use descriptive keys: Name the sub-collection clearly (e.g.,
"goals"
instead of just"data"
if it adds context). - Embed parent context: Include parent details only if necessary.
- Support pagination: For large sub-collections, add pagination metadata (e.g.,
page
,limit
,total
). - Use standard HTTP status codes: Return 200 for success, 404 if the parent resource isn’t found, etc.
- Follow a naming convention: Stick to camelCase, snake_case, or kebab-case based on your API’s style.
- Error handling: If the sub-collection is empty, return an empty array (
"data": []
) rather thannull
or an error (unless the parent resource doesn’t exist, which might result in a 404 case).
How to Produce a Sub-Collection Response?
Section titled “How to Produce a Sub-Collection Response?”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.
// 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
Examples: Sub-Collection Responses
Section titled “Examples: Sub-Collection Responses”Empty Sub-Collection
Section titled “Empty Sub-Collection”- Endpoint:
GET /players/{player_id}/goals
- URI:
GET /players/P-00020/goals
{ "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.
{ "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