How to properly name REST endpoints
REST endpoint naming defines the public contract between clients and backend services. In RESTful architecture, Uniform Resource Identifiers (URIs) must strictly identify resour...
🟢 Junior Level
REST endpoint naming defines the public contract between clients and backend services. In RESTful architecture, Uniform Resource Identifiers (URIs) must strictly identify resources (nouns), while operations on those resources are governed by HTTP methods (verbs).
Core URI Naming Rules
- Use Nouns, Never Verbs:
- The HTTP method supplies the verb; placing verbs in the URI creates redundant, procedural endpoints.
- ✅
GET /api/v1/users(Fetch users) - ❌
GET /api/v1/getUsersorPOST /api/v1/createUser
- Use Plural Nouns for Collections:
- A collection endpoint represents a set of resources:
/users,/orders,/products. - An individual resource is addressed as an item within that collection by its ID:
/users/42,/orders/101. - ❌ Mixing singular and plural (
/user/42vs/users) creates schema confusion.
- A collection endpoint represents a set of resources:
- Use
kebab-caseand Strict Lowercase (RFC 3986):- URIs are case-sensitive on POSIX web servers (Linux, NGINX).
- Hyphens (
-) are standard word separators. Underscores (_) can be obscured by hyperlink underlines in documentation or browsers. - ✅
/user-profiles,/audit-logs - ❌
/user_profiles(snake_case) or/userProfiles(camelCase)
- Represent Hierarchical Ownership (Sub-Resources):
- When a resource strictly belongs to another resource:
- ✅
GET /api/v1/users/42/orders(Orders belonging to user 42) - ✅
GET /api/v1/users/42/orders/10(Specific order 10 of user 42)
🟡 Middle Level
Path Variables vs. Query Parameters
A frequent architectural flaw is encoding filtering or optional parameters into URI path segments:
❌ Bad (Artificial Path Segments): /api/v1/users/status/active/country/de
✅ Good (Query Parameter Filtering): /api/v1/users?status=ACTIVE&country=DE
Rule of Separation:
- Path Variable (
@PathVariable): Used strictly for unique identification of a resource or its direct hierarchical parent (/users/{userId},/departments/{deptId}/employees/{empId}). - Query Parameter (
@RequestParam): Used for filtering, sorting, searching, and pagination (?status=ACTIVE&sort=createdAt,desc&page=0&size=20).
Designing Non-CRUD Business Actions
Real-world enterprise domain logic includes operations that do not cleanly align with simple CRUD verbs: “Cancel order”, “Lock user account”, “Approve loan”.
The industry adopts three primary REST patterns:
1. Resource State Mutation via PATCH (Preferred for Simple Transitions)
PATCH /api/v1/orders/100 HTTP/1.1
Content-Type: application/json
{
"status": "CANCELLED",
"reason": "Customer changed mind"
}
2. Subordinate Action Resource (Domain Event Modeling)
Treat the business action as the creation of an immutable domain record or event:
POST /api/v1/orders/100/cancellations HTTP/1.1
POST /api/v1/users/42/password-resets HTTP/1.1
POST /api/v1/transfers HTTP/1.1
3. Custom Action Methods (Google Cloud API Design Standard)
For complex workflow triggers where creating an entity is unnatural, append a colon and the action verb:
POST /api/v1/orders/100:cancel HTTP/1.1
POST /api/v1/documents/50:publish HTTP/1.1
The Trailing Slash Invariant (RFC 3986)
Under RFC 3986, /users and /users/ are technically two distinct URIs:
- In Spring Boot 3, trailing slash matching (
TrailingSlashMatch) is disabled by default to eliminate duplicate routing ambiguity, cache fragmentation, and security bypasses. - Calling
/users/when the endpoint is registered as/usersreturns404 Not Found(or requires explicit Gateway redirection to/usersvia308 Permanent Redirect).
🔴 Senior Level
The Deep Nesting Anti-Pattern & Flat Resource URLs
Creating deeply nested URI chains is a severe architectural anti-pattern:
❌ Deep Nesting Anti-Pattern:
/api/v1/companies/1/departments/5/teams/12/projects/99/tasks/1001
Why Deep Nesting Degrades Architecture:
- URI Length Bloat: Unwieldy and hard to maintain across client integrations.
- Database Overhead: Requires redundant validation of every foreign key in the chain across multiple relational tables.
- Authorization Complexity: Requires checking user security clearance across 5 hierarchical levels simultaneously.
The Senior Solution: Flat URL Design Pattern
If the child resource possesses a globally unique identifier (UUID or database sequence Primary Key), restrict URI nesting to a maximum of 2 levels:
✅ Collection Scope (2 Levels):
GET /api/v1/projects/99/tasks (List tasks within Project 99)
POST /api/v1/projects/99/tasks (Create new task inside Project 99)
✅ Direct Instance Scope (1 Level - Flat):
GET /api/v1/tasks/1001 (Retrieve task directly)
PUT /api/v1/tasks/1001 (Update task directly)
DELETE /api/v1/tasks/1001 (Delete task directly)
IDOR Prevention via the /me Pseudo-Resource
Exposing mutable user IDs in URLs (GET /api/v1/users/123/profile) introduces Insecure Direct Object Reference (IDOR) vulnerabilities if a controller forgets to verify that {userId} matches the authenticated token subject.
Production Solution: Expose a canonical /me pseudo-resource for the authenticated principal:
GET /api/v1/users/me
GET /api/v1/users/me/orders
GET /api/v1/users/me/cart
The server resolves the user identity directly from the verified security context (SecurityContextHolder), preventing identity tampering. Administrative access to arbitrary users remains isolated under /api/v1/users/{id} guarded by @PreAuthorize("hasRole('ADMIN')").
Spring Boot 3 REST Controller Implementation
package com.example.controller;
import com.example.dto.OrderResponseDto;
import com.example.dto.UserResponseDto;
import com.example.service.OrderService;
import com.example.service.UserService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1")
public class ApiController {
private final UserService userService;
private final OrderService orderService;
public ApiController(UserService userService, OrderService orderService) {
this.userService = userService;
this.orderService = orderService;
}
// 1. Secure Principal Context via /me (IDOR-safe)
@GetMapping("/users/me")
public ResponseEntity<UserResponseDto> getCurrentUser(@AuthenticationPrincipal Jwt jwt) {
Long currentUserId = Long.valueOf(jwt.getSubject());
return ResponseEntity.ok(userService.findById(currentUserId));
}
// 2. Nested Sub-Resource: Orders belonging to a specific user (2 Levels max)
@GetMapping("/users/{userId}/orders")
public ResponseEntity<Page<OrderResponseDto>> getUserOrders(
@PathVariable Long userId,
@RequestParam(required = false, defaultValue = "COMPLETED") String status,
Pageable pageable) {
return ResponseEntity.ok(orderService.findOrdersByUserId(userId, status, pageable));
}
// 3. Flat Resource Access: Directly address entity by its unique ID
@GetMapping("/orders/{orderId}")
public ResponseEntity<OrderResponseDto> getOrderById(@PathVariable Long orderId) {
return ResponseEntity.ok(orderService.findById(orderId));
}
}
RFC 9110 Protocol Compliance: 405 Method Not Allowed
If an endpoint /api/v1/users/42 supports GET, PUT, and DELETE, but a client issues a POST /api/v1/users/42:
- The server must not return
404 Not Found(the resource URI exists!). - Under RFC 9110 Section 15.5.6, the server must return
405 Method Not Allowedand include the mandatoryAllowheader:HTTP/1.1 405 Method Not Allowed Allow: GET, PUT, DELETE, OPTIONS
4 Tricky Questions
1. Why are plural nouns (/users) preferred over singular nouns (/user) in REST URI naming?
Answer: Plural nouns ensure mathematical consistency and orthogonality across the API design:
- The base path
/usersdesignates the complete collection resource. - Appending an ID (
/users/42) identifies a specific member within that collection. - Using singular nouns leads to unnatural syntax:
GET /userreturns a collection of many items, which is semantically contradictory. Standardizing on plural nouns provides a uniform pattern across all entities.
2. How should a financial transfer or payment be designed in REST without using verbs like /transferMoney?
Answer: Model the operation as a first-class transactional resource:
- Dispatch a
POST /api/v1/transferswith a request payload:{ "sourceAccountId": 101, "destinationAccountId": 202, "amount": 500.00, "currency": "USD" } - The server responds with
201 Createdand aLocation: /api/v1/transfers/txn-9941header. This preserves resource-oriented architecture, provides a stable audit record, and enables idempotent polling viaGET /api/v1/transfers/txn-9941.
3. What is the Flat URL design pattern, and what specific architectural problems does it solve?
Answer: The Flat URL pattern restricts resource nesting to at most 2 levels:
- Nested collections are used only for scoping collections by parent:
GET /departments/10/employees. - For manipulating individual resources, the API provides flat top-level URIs:
GET /employees/450,PUT /employees/450. - Problems Solved:
- Prevents URI explosion (e.g.,
/companies/1/divisions/2/departments/3/teams/4/employees/5). - Eliminates costly multi-table database authorization queries validating the full hierarchy.
- Decouples sub-entities from rigid parent routing, simplifying future refactoring.
- Prevents URI explosion (e.g.,
4. What must the server return if a client sends a POST request to an existing endpoint that only supports GET?
Answer:
The server must return 405 Method Not Allowed and must include the Allow response header listing all permitted methods (RFC 9110 §15.5.6):
Allow: GET, HEAD, OPTIONS
- Returning
404 Not Foundin this scenario is a serious protocol error because it falsely indicates to the client that the URI does not exist, obscuring the true issue (an unsupported verb).
🎯 Interview Cheat Sheet
30-Second Elevator Pitch
Proper REST endpoint naming adheres to Resource-Oriented Architecture (ROA):
- Plural Nouns:
/users,/orders,/products. (Verbs belong strictly in HTTP methods).- Formatting: Lowercase
kebab-case(/audit-logs), hyphens as separators, no trailing slashes.- Path vs. Query: Path variables identify resources (
/users/{id}); Query parameters filter, sort, and paginate (?status=ACTIVE&page=0).- Nesting Limit: Keep hierarchy $\le 2$ levels (
/projects/{id}/tasks), utilizing the Flat URL pattern (/tasks/{id}) for direct instance operations.- Security: Use the
/mepseudo-resource (/users/me) to prevent IDOR vulnerabilities.- HTTP Protocol: Return
405 Method Not Allowed+Allowheader if an unsupported verb targets an existing resource.
Red Flags (What NOT to Say)
- ❌ “Using verbs in URIs like
/getAllUsersor/saveUsermakes endpoints clearer.” (Violates fundamental REST principles; HTTP verbs declare the action). - ❌ “Filter parameters should be part of the URI path:
/orders/status/shipped.” (Filters belong in query strings). - ❌ “If an endpoint does not support POST, return 404 Not Found.” (Must return
405 Method Not Allowedwith theAllowheader). - ❌ “Freely mix camelCase and snake_case in URIs.” (REST URIs standardise on lowercase
kebab-case).
Related Topics
- What is RESTful API design — Comprehensive API design rules
- Should you use verbs in URL — Action endpoints and RPC exceptions
- What are the main HTTP methods used in REST — HTTP verb semantics
- What is the difference between 401 and 403 — Authentication vs. authorization
- How to organize REST API versioning — URI versioning patterns