REST Naming and Relationship Conventions
Resource-based URLs
A REST API organises its URLs around resources (nouns), not actions (verbs). The HTTP method communicates the action; the URL communicates what you're acting on.
Good:
GET /farms-- list all farmsGET /farms/3-- get farm with id 3POST /farms-- create a new farmPUT /farms/3-- update farm with id 3DELETE /farms/3-- delete farm with id 3
Bad:
GET /getFarms-- the verb is redundant (GET already means "get")POST /createFarm-- the verb is redundant (POST already means "create")GET /getAllFarmsList-- verbose and inconsistentPOST /delete-farm-- using POST for a delete operation hides the intent
The URL should tell anyone reading it what resource they're working with. The HTTP method tells them what operation is being performed. Together, GET /farms/3 is self-documenting: "retrieve the farm with id 3."
Nested resources for relationships
When one resource belongs to another, the URL structure communicates the relationship.
One-to-many relationships:
GET /farms/3/batches-- all batches from farm 3GET /batches/7/orders-- all orders containing items from batch 7GET /customers/12/orders-- all orders for customer 12
The nesting communicates: "batches belong to a farm." Anyone reading /farms/3/batches understands the data relationship without reading documentation.
When not to nest:
- Don't nest more than one level deep.
/farms/3/batches/7/products/15/ordersis unreadable. Instead, use/orders?product_id=15or/products/15/orders. - If the child resource has a globally unique ID, you can access it directly:
GET /batches/7instead ofGET /farms/3/batches/7.
Traceability endpoints: For Marco's system, the traceability chain is: farm to batches to products to order_items to orders to customers. The API exposes the relationships the client needs:
GET /batches/:id/orders-- trace from a batch to all orders that include itGET /farms/:id/batches-- trace from a farm to all its batchesGET /customers/:id/orders-- a customer's own orders
Each of these communicates a specific navigable relationship.
Status codes for relational operations
HTTP status codes communicate what happened. They are part of the API's contract.
200 OK: The request succeeded. Use for successful GET, PUT, and DELETE operations. For GET requests that return an empty result (e.g., a batch with no orders), return 200 with an empty array [] -- the request succeeded, there's just nothing to show.
201 Created: A new resource was created. Use for successful POST operations.
400 Bad Request: The request was invalid. Use when validation fails -- for example, the request body is missing required fields, or a foreign key reference is invalid (the customer_id doesn't exist). Return a helpful error message: { "error": "Customer 999 does not exist" }.
404 Not Found: The requested resource doesn't exist. Use when the parent resource in a nested URL is missing: GET /batches/999/orders where batch 999 doesn't exist. This is different from a batch that exists but has no orders (which returns 200 with an empty array).
409 Conflict: The request conflicts with the current state. Use for operations that violate business rules -- for example, trying to create an order that would double-allocate inventory.
The difference between 400 and 500 matters. A 400 means "your request was invalid" and includes a message the consumer can act on. A 500 means "something broke on the server" and usually includes a stack trace that helps no one. When a foreign key reference is invalid, catch it at the route level (400) before it reaches the database (which would produce a 500 constraint violation).