REST isn’t a standard — it’s a set of architectural constraints. Understanding these constraints helps you make informed decisions about when to follow them and when to deviate.

The Six Constraints

  1. Client-Server — Separation of concerns
  2. Stateless — Each request contains all information needed
  3. Cacheable — Responses must define themselves as cacheable or not
  4. Uniform Interface — Consistent resource identification
  5. Layered System — Client shouldn’t know if it’s connected directly to the server
  6. Code on Demand (optional) — Server can extend client functionality

HATEOAS in Practice

Hypermedia as the engine of application state — linking related resources:

{
  "data": {
    "id": 42,
    "type": "article",
    "attributes": { "title": "REST Patterns" },
    "links": {
      "self": "/api/v1/articles/42",
      "author": "/api/v1/users/7",
      "comments": "/api/v1/articles/42/comments"
    }
  }
}

Content Negotiation

Use headers, not URL extensions:

GET /api/v1/articles HTTP/1.1
Accept: application/json
class ArticlesController < ApplicationController
  def show
    article = Article.find(params[:id])

    respond_to do |format|
      format.json { render json: article }
      format.xml  { render xml: article }
    end
  end
end

These patterns form the backbone of a well-designed REST API.