Path Nesting, Routing & Actions — Depth Limits & Sub-resources
Nesting feels natural until clients need cross-org search, gateways rewrite paths, or authz must check every segment. Cap depth around 2–3 resource pairs, keep a canonical item URL, and model non-CRUD verbs as :customAction or /actions — not controller soup.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
How you place the child resource
Prefer
Nest for ownership; flatten first-class lists; colon-actions for verbs
/orgs/{orgId}/members for scoped membership. /projects?orgId= when product search is global. POST /invoices/{id}:void instead of /doVoid.
- Canonical /tasks/{taskId} even if create is nested.
- Templates stay stable for OpenAPI and codegen.
- Gateway rewrite keeps every identity segment the handler authorizes on.
Alternative
Mirror the foreign keys, or RPC soup
/orgs/o1/projects/p1/tasks/t1/comments/c1 because the DB looks like that. POST /projects/p1/doDeploy with no convention. Rewrite strips orgId.
- URL explosion, long cache keys, 404 archaeology.
- Cross-org search becomes N nested calls.
- Overlapping /users/{id} vs /users/me without priority.
Choose nested, flat, or action
Foreign keys are a storage fact. URLs are a product fact.
- 1
Is the child first-class?
Listed globally with its own id → flat collection + required filter. - 2
Is authz parent-scoped?
Child meaningless outside parent → nest 1–2 pairs. - 3
Is the verb CRUD?
No → :customAction on the resource or /actions for cross-resource workflows. - 4
Rewrite safely
Strip /public prefix only. Keep {orgId} for authz.
Overview
Nesting feels natural (/orgs/{orgId}/projects/{projectId}/tasks) until clients need cross-org search, gateways rewrite paths, or authz must check every segment.
When to nest vs flatten:
- Nest when the child is meaningless without the parent and authz is scoped by parent:
/orgs/{orgId}/members. - Flatten + filter when the child is a first-class resource often listed globally:
/projects?orgId=or/tasks?projectId=. - Max practical depth ~2–3 resource pairs; deeper trees hurt readability, caching keys, and SDKs.
- Always expose a canonical item URL if the resource has a stable id:
/tasks/{taskId}even if also nested.
Custom actions (not CRUD)
- Anti-pattern: controller soup
/OrdersController/Voidor verbs as path segments without convention. - Prefer Google-style custom methods:
POST /invoices/{id}:voidorPOST /invoices/{id}:send. - Or an
/actionsprefix:POST /actions/void-invoicewith body{ "invoiceId": "..." }for cross-resource workflows. - Prefer
POSTfor non-safe actions; document idempotency expectations and point to the Idempotency cluster for retries — do not re-teach keys here.
Routing tables and gateways
- Match on method + path template; keep templates stable for OpenAPI and client codegen.
- Gateway path rewrite (
/public/v1/orders→/orders) must preserve identity segments and not strip authz context. - Avoid overlapping templates that only differ by trailing slash or case.
/users/{id}vs/users/meneeds explicit priority someis not parsed as an id.
| Shape | Pros | Cons |
|---|---|---|
| Deep nesting | Mirrors ownership | URL explosion, hard cross-queries, long cache keys |
| Flat + query | Flexible filters | Weaker “in this org” ergonomics unless filters are required |
:customAction | Clear verb on a resource | Less REST-purist; need OpenAPI extensions |
Pure /rpc/* | Simple mental model | Weak HTTP caching and uniform interface |
Nested read, then a custom action
Keep sequence participants inside the card. Rewrite is the failure mode.
Sequence
- 1
Client
1 Nested read
- 2
Client → Gateway
GET project under org
- 3
Gateway → Router
Strip /public prefix
- 4
Router → Handler
Match orgs/:id/projects/:id
- 5
Handler → Client
200 project JSON
- 6
Client
2 Custom action
- 7
Client → Gateway
POST invoices/:id:void
- 8
Gateway → Router
Template :void
- 9
Router → Handler
Authz on inv_3
- 10
Handler → Client
200 or 409 already void
Lesson map
Path Nesting, Routing & Actions — Depth Limits & Sub-resources
Nesting feels natural until clients need cross-org search, gateways rewrite paths, or authz must check every segment. Cap depth around 2–3 resource pairs, keep a canonical item URL, and model non-CRUD verbs as :customAction or /actions — not controller soup.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB c["Client"] gw["Gateway"] r["Router"] h["Handler"] c -->|GET project| gw gw -->|Strip /public| r r -->|Match| h h -->|200 project JSON| c c -->|POST| gw gw -->|Template :void| r
Sandbox: depth and action parse (Python)
Count concrete resource nouns; ignore :actions. Depth 4+ on a comment URL is the interview smell.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Routing decision sketch (TypeScript)
Press Run. Snippets must be self-contained — no network, files, or native modules.
Pitfalls
Org members are never listed across orgs. Projects are searched company-wide. Draw both URLs. Where does POST ...:archive live? What does the gateway see after stripping /public/v1?
Interview Q&A
Max nesting depth?
Answer
Soft rule 2–3 resource pairs. Beyond that prefer flat collections + filters and links. Interviews fail on unreadable URLs, not on missing a fourth segment.
Nested and top-level both?
Answer
Yes — nested for scoped create/list; canonical /resources/{id} for get/update. Clients should not be forced to remember the whole parent chain to fetch one task.
How to model approve?
Answer
POST /requests/{id}:approve or /actions/approve-request with a clear body. Document side effects. Prefer POST; retries need an idempotency pointer — keys.
Why care about gateway rewrite?
Answer
Rewrites change what handlers and authz see. Preserve identity segments. Kong/Envoy route matching is how this bug ships.
Relation to retries?
Answer
Custom POST actions need an idempotency strategy — see api-idempotency-keys and fingerprinting. Pointer only; this page does not teach key TTLs.
When is /actions better than :void?
Answer
When the workflow spans resources or has no single owner URL. :void stays on the invoice. /actions/close-quarter is a cross-resource job.
/users/me vs /users/{id}?
Answer
Register me as a literal with higher priority. Otherwise me is an id and you 404 or, worse, look up a user named me.
Does a foreign key mean nest?
Answer
No. Product access patterns do. Tasks with global search want /tasks?projectId=.
Pure RPC instead?
Answer
Fine for internal non-CRUD. You give up uniform HTTP caching. Public APIs usually hybrid: nouns + a few actions.
What does 409 mean on :void?
Answer
Already voided — a state conflict, not a validation error. Status-code depth: methods.