Learn by Directing AI
All materials

rest-conventions.md

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 farms
  • GET /farms/3 -- get farm with id 3
  • POST /farms -- create a new farm
  • PUT /farms/3 -- update farm with id 3
  • DELETE /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 inconsistent
  • POST /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 3
  • GET /batches/7/orders -- all orders containing items from batch 7
  • GET /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/orders is unreadable. Instead, use /orders?product_id=15 or /products/15/orders.
  • If the child resource has a globally unique ID, you can access it directly: GET /batches/7 instead of GET /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 it
  • GET /farms/:id/batches -- trace from a farm to all its batches
  • GET /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).