when /orders/1/items is genuinely the right URL
Nested resources such as order items under an order look intuitive at first glance, but if used without thought they quickly lead to cluttered URL structures and duplicated authorization logic. This article shows how to cleanly model subresources in API Platform through uriTemplate and when a standalone resource is the better choice.
Table of contents
- 1. Why nested resources are not a free win
- 2. Defining subresources through uriTemplate
- 3. IRI reference versus embedded relation
- 4. How deep nesting should actually go
- 5. Inheriting authorization along the parent resource
- 6. Normalization groups for different nesting depths
- 7. When a standalone resource is the better choice
- 8. Performance with nested collections
- 9. Subresource versus standalone resource compared
- 10. Summary
- 11. FAQ
1. Why nested resources are not a free win
Once a domain consists of several related entities, for example orders and their items, it is tempting to also reflect this relationship in the URL: /orders/1/items instead of a flat list under /order-items with a filter parameter. Such nested resources, called subresources in API Platform, communicate the business relationship directly in the URL structure and are intuitively readable for many consumers.
The appeal of nested resources has limits, though. Every additional nesting level increases the complexity of the routing configuration, the authorization logic, and the overall URL structure. A URL like /customers/1/orders/2/items/3/refunds is technically possible, but neither pleasant for humans nor for generic API clients to handle, and often a sign that the resource modeling should be rethought.
API Platform offers the uriTemplate argument as a flexible tool for nested resources, but it should be used deliberately, not reflexively for every parent child relationship. Deciding whether a relationship is modeled as a subresource, as an embedded relation in the response body, or as a fully standalone resource is one of the most important design decisions when building an API.
2. Defining subresources through uriTemplate
API Platform does not model subresources through a dedicated attribute, but through the regular uriTemplate argument of a GetCollection operation, combined with a placeholder for the parent resource's id. Internally, API Platform automatically translates this placeholder into a Doctrine filter that only returns the children of the referenced parent resource, without any manually written query code.
These nested resources appear in addition to the regular flat collection route, as long as both are configured. A client can then use either /order-items?order=1 or /orders/1/items, with the latter being especially practical for frontend applications that are already navigating in the context of a specific order.
<?php
declare(strict_types=1);
namespace App\ApiResource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use App\Entity\Order;
/**
* Order item resource, exposed both as a flat collection
* and as a nested subresource under its parent order.
*/
#[ApiResource(
operations: [
new GetCollection(),
new GetCollection(
uriTemplate: '/orders/{orderId}/items',
uriVariables: [
'orderId' => new \ApiPlatform\Metadata\Link(
fromClass: Order::class,
toProperty: 'order',
),
],
),
new Get(),
],
)]
final class OrderItem
{
public int $id;
public string $productName;
public int $quantity;
}
3. IRI reference versus embedded relation
Independently of subresources, every relation between two resources has to decide how it is represented in the JSON response. The default case in API Platform is the IRI reference, a URL as a string that the client can separately fetch when needed. That keeps every single response small and performant, but requires an extra request whenever the client needs the linked data immediately.
Through serialization groups, a relation can instead be fully embedded, so the nested data is delivered right in the first response. These embedded relations save extra requests but enlarge every response and can quickly lead to unwieldy, hard to cache JSON structures on deeply nested object graphs if activated uncontrolled for every relation.
<?php
declare(strict_types=1);
namespace App\ApiResource;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use Symfony\Component\Serializer\Attribute\Groups;
/**
* Order resource with items embedded directly in the response,
* avoiding an extra request for the client.
*/
#[ApiResource(
operations: [
new GetCollection(normalizationContext: ['groups' => ['order:read']]),
],
)]
final class Order
{
#[Groups(['order:read'])]
public int $id;
// Embedded relation: full item data instead of just an IRI
#[Groups(['order:read'])]
public array $items = [];
}
4. How deep nesting should actually go
A practical rule of thumb: nested resources beyond one level are rarely the right choice. Instead of offering /customers/1/orders/2/items, a flatter access pattern usually works better, for example /orders/2/items combined with a filter parameter at the customer level, if that is even needed. Every additional level in the URL means an additional link definition, an additional Doctrine join, and an additional potential source of error for non existent intermediate resources.
A second reason against deep nesting is the bookmark and cache friendliness of URLs. A URL like /orders/2/items/5 can be bookmarked and referenced directly in a second application without needing to know the full parent path. Once several parent levels are encoded in the URL, every reference to a deeper resource becomes unnecessarily long and coupled to the existence of all intermediate resources.
5. Inheriting authorization along the parent resource
An often overlooked aspect of nested resources is authorization. When /orders/1/items is queried, it must be checked whether the current user has access to order 1 at all before the associated items are returned. Without this check, a user could view data through the subresource route that would be correctly protected through the regular route, a classic example of a broken object level authorization gap.
In API Platform this can be solved through the security argument of the subresource operation, which, in addition to checking the child resource, can also reference the parent resource through the injected URI variable. The security expression then explicitly checks whether the user has access to the referenced parent order, and consistently denies access to the subresource otherwise.
6. Normalization groups for different nesting depths
Depending on whether a resource is output as a top level endpoint or as an embedded relation of another resource, often different fields are relevant. A customer fetched directly through /customers/1 needs full contact data, while the same customer reference embedded in an order usually only needs name and id. Normalization groups solve this problem by defining different serialization contexts for the same entity type.
This context dependent serialization prevents every embedded relation from automatically carrying all fields of the target resource, which reduces response size and avoids accidental data leaking, for example when a sensitive field should only appear in the top level context, not in embedded relations.
7. When a standalone resource is the better choice
Not every relationship justifies a subresource. As soon as a nested object needs its own filters, its own pagination, or its own write operations beyond the parent context, that speaks for a standalone, flat resource instead of a true subresource. An example: order items that also need to be searchable independently of a specific order, for example for a company wide revenue analysis by product, should primarily exist as a standalone /order-items resource with an optional filter, while the nested route remains a convenient additional path.
Another signal for a standalone resource: when a nested object has a meaningful, independent lifecycle even without its current parent context, for example an invoice that must stay archived even after the original order has been deleted. Such objects do not conceptually belong permanently under the parent resource, even though they originate from it at creation time.
8. Performance with nested collections
Nested collections can lead to the same N plus 1 problems as regular relations if implemented without care: if the item subresource is queried separately for every order in an outer list, this results in unnecessarily many database queries. In practice this mostly affects frontend applications that first load the order list and then trigger an additional subresource request for every visible order, instead of delivering the items directly embedded.
The solution lies in a deliberate choice between embedded relations for the list use case and separate subresource requests only when actual detail data of a single order is needed. This decision should be made based on the frontend's actual usage pattern, not on a blanket rule for all relations in the project.
9. Subresource versus standalone resource compared
The table below summarizes when a subresource makes sense and when a standalone, flat resource is preferable.
| Criterion | Subresource | Standalone resource | Recommendation |
|---|---|---|---|
| Only exists in the parent context | Fitting | Unnecessary detour | Subresource for strictly dependent children |
| Needs its own filters and pagination | Limited support | Fully flexible | Standalone resource for complex search |
| Bookmark and direct reference | Long, coupled path | Short, stable URL | Flat resource for direct access |
| Authorization | Must explicitly check the parent resource | Its own, clearly scoped voter | Configure subresource security carefully |
| Lifecycle independent of the parent | Conceptually mismatched | Correct representation | Standalone resource for its own lifecycle |
In practice, well modeled API Platform projects combine both approaches deliberately: true subresources for objects strictly bound to their parent context, and standalone, flat resources wherever custom filters, an independent lifecycle, or direct referenceability are required. Making this decision per relation, rather than applying a blanket rule to the entire project, is the crucial difference between a maintainable and an overly nested API structure.
Mironsoft
Symfony and API Platform architecture for demanding APIs
A clean resource structure for your API Platform project?
We model subresources and relations so URLs stay stable and referenceable, authorization along the parent resource correctly applies, and N plus 1 problems in nested collections never arise in the first place.
Resource design
Cleanly separating subresources and standalone resources by domain
Security audit
Checking for broken object level authorization in nested routes
Performance review
N plus 1 analysis for nested collections and relations
10. Summary
Nested resources via uriTemplate are a powerful tool in API Platform for objects strictly bound to their parent context, but they should not be applied reflexively to every relationship. More than one nesting level, missing authorization checks along the parent resource, and uncontrolled embedded relations are the most common mistakes in practical implementation.
Anyone who deliberately decides between a subresource, an embedded relation, and a standalone resource for every relation, instead of applying a single rule across the whole project, ends up with an API structure that stays readable for humans and maintainable technically. Making this decision early in the project saves later, hard to reverse restructurings of the URL structure.
Nested resources in API Platform: the essentials
uriTemplate subresources
Nested collection routes via a link to the parent resource, no manual query code required.
IRI vs embedded
IRI reference by default for small responses, embedded relation only deliberately via serialization groups.
Authorization
The subresource's security expression must explicitly check the parent resource, otherwise BOLA risk arises.
When standalone
Custom filters, an independent lifecycle, or bookmarkability speak for a flat, standalone resource.