7 Jan 2020

REST API Guidelines

What is REST

REST is an acronym for REpresentational State Transfer — an architectural style for distributed hypermedia systems, first presented by Roy Fielding in his 2000 dissertation.

Like any architectural style, REST has its own 6 guiding constraints, which must all be satisfied for an interface to be called RESTful.

Guiding principles of REST

  1. Client-server — separating the user interface concerns from the data storage concerns improves portability of the UI across platforms and improves scalability by simplifying server components.
  2. Stateless — each request from the client to server must contain all the information needed to understand the request; session state is kept entirely on the client.
  3. Cacheable — responses must be labelled, implicitly or explicitly, as cacheable or non-cacheable, so a client cache can reuse cacheable responses for later equivalent requests.
  4. Uniform interface — a set of constraints (resource identification, manipulation through representations, self-descriptive messages, and hypermedia as the engine of application state) that simplify overall architecture and improve visibility.
  5. Layered system — components can only “see” the immediate layer they interact with, not beyond it.
  6. Code on demand (optional) — client functionality can be extended by downloading and executing code (applets, scripts), reducing what needs to be pre-implemented.

Best practices for implementing REST APIs

These practices apply regardless of stack or framework: key requirements, URL and action design, SSL, versioning, authentication, query parameters for filtering, field selection, resource representation on POST/PUT/PATCH, JSON vs XML, pagination, HTTP status codes, and documentation.

Key requirements

An API is a developer’s UI. It should:

  • use web standards where they make sense.
  • be friendly to the developer and be explorable.
  • be simple, intuitive, and consistent, so adoption is easy and pleasant.
  • provide enough flexibility to power the majority of the client UI.
  • be efficient and scalable, while balancing the other requirements.

URLs and actions

Separate your API into logical resources, manipulated via HTTP methods (GET, POST, PUT, PATCH, DELETE) with specific meanings. A resource should be a noun, not a verb:

  • GET /users — retrieves a list of users
  • GET /users/12 — retrieves user #12
  • POST /users — creates a new user
  • PUT /users/12 — updates user #12
  • PATCH /users/12 — partially updates user #12
  • DELETE /users/12 — deletes user #12

Naming format

There’s no strict naming convention, but keep the URL structure clean and consistent — always use a plural, even for a single instance, to avoid odd pluralization (person/people, goose/geese) and to match how most frameworks natively handle a /users and /users/12 pair under one controller.

Relations

If a relationship only exists within another resource, nest it:

  • GET /users/12/messages — messages for user #12
  • GET /users/12/messages/5 — message #5 for user #12
  • POST /users/12/messages — create a message for user #12
  • PUT /users/12/messages/5 — update message #5 for user #12
  • PATCH /users/12/messages/5 — partially update message #5 for user #12
  • DELETE /users/12/messages/5 — delete message #5 for user #12

If a relationship can exist independently, just include an identifier for it in the resource’s representation, and let the consumer hit the relation’s own endpoint — unless it’s commonly requested alongside the resource, in which case consider embedding it to avoid a second round trip.

Actions that aren’t CRUD

  • Restructure the action as a resource field, if it takes no parameters — e.g. map an activate action to a boolean activated field, updated via PATCH /users/12/activate.
  • Treat it as a RESTful sub-resource — e.g. GitHub’s API lets you star a gist with PUT /gists/:id/star and unstar with DELETE /gists/:id/star.

SSL

Always use SSL — one of the basic security measures in API design. Expose every endpoint via SSL.

Versioning

Always version your API. It helps you iterate faster, prevents invalid requests from hitting updated endpoints, and smooths over major version transitions by letting you keep old versions running for a period. Put the version number in the URL, e.g. /v1/users/.

Authentication

A RESTful API should be stateless — authentication shouldn’t depend on cookies or sessions; each request should carry its own credentials.

With SSL everywhere, credentials can be simplified to a randomly generated access token delivered in the username field of HTTP Basic Auth — fully browser-explorable, since the browser prompts for credentials on a 401 Unauthorized.

Where it’s not practical to have users copy a token manually, use OAuth 2 instead, which provides secure token transfer to a third party via Bearer tokens, also over SSL.

Query parameters for advanced filtering

Keep base resource URLs lean; implement filtering, sorting, and searching as query parameters:

  • Filtering — a unique query parameter per filterable field, e.g. GET /users?status=active.
  • Sorting — a generic sort parameter taking comma-separated fields, each with an optional leading - for descending order, e.g. GET /users?sort=-priority or GET /users?sort=-priority,created_at.
  • Searching — a q parameter for full-text search, passed straight to the search engine, with output in the same format as a normal list result.

Combined:

  • GET /users?sort=-updated_at — recently updated users
  • GET /users?status=closed&sort=-updated_at — recently closed users
  • GET /users?q=return&status=active&sort=-priority,created_at — highest-priority active users mentioning “return”

You can also package common filter combinations into aliased paths, e.g. GET /users/recently_closed.

Return fields

Let consumers select which fields come back, via a fields query parameter taking a comma-separated list — e.g. GET /users?fields=id,subject,customer_name,updated_at&status=active&sort=-updated_at.

Resource representation for POST, PUT, and PATCH

A PUT, POST, or PATCH call may modify fields beyond the ones provided (e.g. created_at/updated_at timestamps). Return the updated (or created) representation as part of the response, so the consumer doesn’t need a follow-up call. For a POST that creates a resource, use 201 Created with a Location header pointing to the new resource.

JSON and XML

Return JSON or XML — both easily parsable, with JSON recommended. Follow the target language’s naming conventions for field names: camelCase for C#/Java, snake_case for Python/Ruby.

Pagination

Use query parameters like page (page number) and pageSize (items per page). Return the total record count and total page count, either in the body or via custom headers like X-Total-Count / X-Total-Page, plus previous/next page URLs if useful.

HTTP status codes

  • 200 OK — successful GET, PUT, PATCH, or DELETE (or a POST without creation).
  • 201 Created — successful POST that creates a resource; pair with a Location header.
  • 204 No Content — successful request with no response body (e.g. DELETE).
  • 304 Not Modified — used with HTTP caching headers.
  • 400 Bad Request — the request is malformed.
  • 401 Unauthorized — missing or invalid authentication.
  • 403 Forbidden — authenticated but not authorized for the resource.
  • 404 Not Found — resource doesn’t exist.
  • 405 Method Not Allowed — HTTP method not allowed for this user.
  • 410 Gone — resource permanently unavailable (useful for retired API versions).
  • 415 Unsupported Media Type — wrong content type on the request.
  • 422 Unprocessable Entity — validation errors.
  • 429 Too Many Requests — rate limited.

Documentation

An API is only as good as its documentation. Tools like Swagger help document REST APIs. Docs should show complete request/response examples. Once a public API ships, you’ve committed to not breaking it without notice — include deprecation schedules, and announce externally visible updates via a change-log or mailing list, ideally both.