REST Resource Naming Conventions
Fast recall
Quick revision
Core answer
/orders), identifiers for items (/orders/{id}), kebab-case for multi-word segments (/payment-intents), and sub-resources for clear ownership (/orders/{id}/items). Keep URLs noun-based — verbs belong in the HTTP method, not the path — except for bounded "controller" actions like /invoices/{id}:send where no CRUD mapping fits. Use query parameters for filtering/sorting/paginating, not for identity. Version at the prefix (/v1), not the query string. All of these rules exist to make the URL a stable, discoverable contract; breaking them silently is the single biggest cause of painful API migrations.Whether you can design URLs that reflect resources (not actions), pluralise consistently, express ownership via sub-resources, and separate filter/sort/pagination (query params) from identity (path).
Encoding verbs in the path (`/getOrder`, `/createOrder`), mixing plural and singular (`/order` and `/orders`), using snake_case or camelCase in URLs, or cramming filters into the path (`/orders/by-user/123`) instead of `/orders?userId=123`.
Discuss Stripe's `:action` controller pattern, Google's AIP-121 resource-oriented design, when HATEOAS reduces naming churn, and how cursor-based pagination interacts with URL stability (opaque cursors in query params, never in the path).
Interview-ready guide
Interview answer
REST naming conventions exist because URLs are long-lived contracts. Once a client ships, you can't rename the path without breaking them.
The four rules that matter
- Plural nouns for collections —
/orders,/users,/payment-intents - Identifier after the collection for items —
/orders/{id} - Kebab-case for multi-word segments —
/payment-intents, not/paymentIntentsor/payment_intents - Verbs in the HTTP method, not the path —
GET /orders/{id}, notGET /getOrder/{id}
Ownership via sub-resources
/orders/{id}/itemssays items belong to this order- Flat
/items?orderId=...says items are a catalog filtered by order - Pick based on who owns the authorisation check
Filters, sorting, pagination — query parameters
/orders?status=paid&sort=-createdAt&limit=50&cursor=...- Identity goes in the path; filters go in the query string
- Opaque cursors for pagination so the client doesn't depend on internals
Versioning at the prefix
/v1/orders,/v2/orders— one switch point for client code- Media-type versioning is a valid alternative but harder to test with curl
Controller actions when CRUD doesn't fit
- Stripe's convention:
POST /invoices/{id}:send - Use sparingly; most actions should map to a method on a resource
Singleton resources
/users/{id}/profile— exactly one exists per parent; use the singular noun
Common mistakes
- Mixing
/orderand/ordersin the same API - Verbs in paths —
/getOrder,/createOrder,/deleteOrder - Identity in query strings —
/orders?id=42instead of/orders/42 - Deep nesting —
/organizations/{}/teams/{}/projects/{}/issues/{}becomes unusable - No version at launch — every later change is a breaking change
The meta-rule
- Consistency beats theology; pick conventions, write them down, apply them universally across the entire API surface
REST Resource Naming Conventions
Direct Answer
Use plural nouns for collections (/orders), identifiers for items (/orders/{id}), kebab-case for multi-word segments (/payment-intents), and sub-resources for clear ownership (/orders/{id}/items). Keep URLs noun-based — verbs belong in the HTTP method, not the path — except for bounded "controller" actions like /invoices/{id}:send where no CRUD mapping fits. Use query parameters for filtering/sorting/paginating, not for identity. Version at the prefix (/v1), not the query string. All of these rules exist to make the URL a stable, discoverable contract; breaking them silently is the single biggest cause of painful API migrations.
Code Example
# --- COLLECTION & ITEM ---
# BAD # GOOD
GET /getAllOrders GET /orders
GET /order/42 GET /orders/42
POST /createOrder POST /orders
PUT /orders/42/update PUT /orders/42
DELETE /deleteOrder?id=42 DELETE /orders/42
# --- OWNERSHIP ---
GET /items?order_id=42 GET /orders/42/items
POST /addItem POST /orders/42/items
# --- FILTERING & SEARCH ---
GET /orders/by-status/paid GET /orders?status=paid
GET /searchOrders?q=foo GET /orders?search=foo
GET /orders/page/2 GET /orders?cursor=eyJ...&limit=50
# --- ACTIONS THAT DON'T FIT CRUD ---
POST /sendInvoice/42 POST /invoices/42:send
POST /jobs/42/start POST /jobs/42:start # or POST /jobs/42/runs
# --- CASING ---
GET /paymentIntents GET /payment-intents
GET /payment_intents GET /payment-intents
# --- VERSIONING ---
GET /orders?version=2 GET /v2/orders
GET /api/orders (no version) GET /v1/orders
# --- ALTERNATE LOOKUP ---
GET /users/by-email/a@b.com GET /users?email=a@b.com
# --- CONTROLLER MAPPING (Spring) ---
# BAD (verbs in path):
@GetMapping("/getOrder/{id}")
# GOOD (nouns in path, verb in HTTP method):
@GetMapping("/orders/{id}")
Common Mistakes & Pitfalls
Encoding verbs in the path (/getOrder, /createOrder), mixing plural and singular (/order and /orders), using snake_case or camelCase in URLs, or cramming filters into the path (/orders/by-user/123) instead of /orders?userId=123.
Check your understanding
Answer these without looking back at the article.
- When do you flatten a nested resource like `/orders/{id}/items` into a top-level `/items?orderId=...`?
- How do you name actions that don't map to CRUD (send invoice, publish draft)?
- What are the trade-offs of URL-prefix versioning vs media-type versioning?
- Why is kebab-case preferred over camelCase or snake_case in URL paths?
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.