Resource Design
Objectives
By the end of this lesson, you should be able to:
- Name resource URLs consistently, using plural nouns
- Structure nested resources to express a real relationship between them
- Avoid common URL design mistakes that make an API harder to predict
💡 Why this matters: Consistent naming is what makes an API guessable, once a developer sees
/products, they should be able to correctly guess/ordersand/usersfollow the same pattern, without checking documentation for each one individually.
⚠️ A note on verification: every snippet and every response shown below was actually run and tested with real HTTP requests.
Plural Nouns, Consistently
Good: /products /products/5
Also seen: /product /product/5 (inconsistent, avoid)
Resource collections use plural nouns, /products (the collection), /products/5 (one specific product within it), consistently across an entire API. Mixing /product/5 in one place and /orders (plural) elsewhere makes an API’s naming unpredictable, pick one convention, plural is the overwhelming norm, and apply it everywhere.
Nested Resources for Real Relationships
const users = [{ id: 1, name: 'Erin' }];
const orders = [
{ id: 101, userId: 1, total: 45 },
{ id: 102, userId: 1, total: 89 }
];
app.get('/users/:userId/orders', (req, res) => {
const userOrders = orders.filter(o => o.userId === Number(req.params.userId));
res.json(userOrders);
});
app.get('/users/:userId/orders/:orderId', (req, res) => {
const order = orders.find(
o => o.userId === Number(req.params.userId) && o.id === Number(req.params.orderId)
);
if (!order) return res.status(404).json({ error: 'Not Found' });
res.json(order);
});
curl http://localhost:4502/users/1/orders
[{"id":101,"userId":1,"total":45},{"id":102,"userId":1,"total":89}]
curl http://localhost:4502/users/1/orders/101
{"id":101,"userId":1,"total":45}
/users/:userId/orders reads as “the orders belonging to this user,” expressing a genuine ownership relationship directly in the URL structure, this is the right call when a resource only makes sense in the context of its parent (an order without a user is meaningless). A resource that stands on its own, /products, doesn’t need artificial nesting.
When Not to Nest
Nesting too deeply (/users/:userId/orders/:orderId/items/:itemId/reviews/:reviewId) becomes unwieldy fast, a common convention is nesting one level, then referencing deeper resources by their own top-level, flat endpoint instead (/reviews/:reviewId, rather than the full nested chain). If a resource can be looked up meaningfully on its own (an order has its own unique ID, regardless of which user it belongs to), a flat /orders/:orderId alongside the nested /users/:userId/orders is a reasonable, common pattern, giving two valid ways to reach the same resource.
Avoiding Verbs in URLs
Avoid: POST /products/5/activate
Better: PATCH /products/5 { "status": "active" }
A URL like /products/5/activate reintroduces a verb (Lesson 1’s core distinction), a PATCH updating a status field expresses the same change while staying resource-oriented. This isn’t an absolute rule, some actions genuinely don’t map cleanly to CRUD (sending an email, triggering a batch job), and a well-named action endpoint is sometimes the pragmatic, honest choice, but it should be the exception, not the default.
Try It
- Take an API with inconsistent naming (mixing singular and plural resource names) and rewrite every URL consistently.
- Design (in words, then code) a nested resource relationship for a blog: posts and their comments, and decide whether a flat
/comments/:idendpoint should exist alongside the nested one. - Find a URL using a verb (
/users/5/deactivate, for example) and redesign it as a resource-orientedPATCHinstead. - Explain, in your own words, when nesting a resource under its parent makes sense, and when a flat, top-level resource is the better choice.
Recap
- Resource collections use plural nouns consistently,
/products,/orders,/users, applied the same way everywhere in an API. - Nested resources (
/users/:userId/orders) express a genuine ownership relationship, avoid nesting more than one or two levels deep. - Prefer expressing an action as a resource update (
PATCHwith a changed field) over a verb baked into the URL, reserving verb-like endpoints for actions that genuinely don’t map to CRUD.
Next lesson: API versioning, changing an API without breaking existing clients.