IRI vs. Embedded Objects
IRI vs. Embedded Objects
~14 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Chapters 37-38 always showed relationships as IRI STRINGS ("/api/tasks/1") – this chapter explains WHY, and HOW that can be changed to embedded objects ON PURPOSE.
Why IRIs are the default
- EFFICIENCY: a list of 45 projects with EACH one's tasks embedded would DRASTICALLY inflate the response size, even if the frontend doesn't need the tasks AT ALL.
- CONSISTENCY: an IRI is ALWAYS the CURRENT, COMPLETE representation ONE click away – no risk of STALE embedded data.
- HYPERMEDIA PRINCIPLE: JSON-LD/Hydra is BUILT ON clients navigating resources via LINKS instead of embedded copies (EXACTLY like HTML links on the web).
Embedding on purpose with nested groups
#[ORM\ManyToOne(inversedBy: 'tasks')]
#[ORM\JoinColumn(nullable: false)]
#[Groups(['task:read', 'task:write', 'task:read:embedded'])]
private ?Project $project = null;ONE additional group like task:read:embedded is NOT enough by itself – the TARGET entity (Project) needs that SAME group on ITS OWN properties (id, name) TOO, so they become VISIBLE once embedded.
The context option per operation
new Get(
normalizationContext: ['groups' => ['task:read', 'task:read:embedded']],
),ONLY on GET /api/tasks/{id} (a SINGLE item) does the project get EMBEDDED, on GetCollection it STAYS an IRI – a COMMON practice: an item's details deliver MORE, a list stays LEAN.
Comparing the result
| Request | project field |
|---|---|
GET /api/tasks (collection) | "project": "/api/projects/1" |
GET /api/tasks/1 (item, WITH embedded group) | "project": {"@id": "/api/projects/1", "id": 1, "name": "Website Relaunch"} |
Achtung: AVOID embedded objects when the relationship could become CIRCULAR (a project embeds tasks, which would in TURN embed their project) – chapter 45 shows SPECIFICALLY how to AVOID that.
Tipp: React (block 9) benefits from IRIs ALSO being valid URLs – a fetch(task.project) works DIRECTLY, WITHOUT having to first extract the ID out of an object.