DEV Community

Cover image for How to correctly design REST APIs
Muhammad Bin Zafar
Muhammad Bin Zafar

Posted on • Edited on

How to correctly design REST APIs

REST stands for "Representational State Transfer". This is an architectural style. A web API conforming to this style is a REST API.

REST stands on 6 guiding principles, important among them are:

  • Client-server: separate backend and frontend.
  • Stateless: each http request contains enough data (e.g. auth, session, user data) to understand it without knowing what the previous request was.
  • Cacheable
    • GET: always
    • POST: using http headers expire, cache-control, etag, last-modified
    • PUT, DELETE: never

🧠 Concepts

The following are some important, concise, and simplified REST concepts a good backend dev must be familiarized with.

Resource

REST APIs are modeled as a resource hierarchy - where each node is either a collection, or a single resource. A single resource has some state, or sub-resources.

Example of a stateful resource is the following, where a user resource has data/states:

POST /users/:id

{
  id: 4e7d418a70f,
  name: 'muhammad',
  country: 'earth',
  verified: true
}
Enter fullscreen mode Exit fullscreen mode

Example of a resource with sub-resources is the following, where a user has multiple projects, each of which are a resource.

GET /users/:id/projects
content-type: application/json

[
  {
    id: 'postman cli',
    desc: 'postman implemented in command-line'
    url: 'github.com/midnqp/postman-cli'
  },
  {
    id: 'typescript',
    desc: 'typescript is a superset of javascript',
    url: 'github.com/microsoft/typescript'
  }
]
Enter fullscreen mode Exit fullscreen mode

Idempotency

API Idempotency means "a client can make a request multiple times, returning same response - without having any side-effect".

Making the following requests multiple times produces the same result, and has no further side-effect - thus are "idempotent" APIs:

  • GET /users/:id - just retrieving same user data
  • PUT /users - just updating same json data
  • DELETE /users/:id - just deleteing the same user

A non-idempotent API:

  • POST /users/:id - because a new user will be created everytime this request is made

🎨 Designs

Some constraints in REST API naming ensures a design of scalable API endpoints:

  • Use plural nouns, e.g. users, orders, categories.
  • Use hyphen, not underscores. Use lowercase, never camel-case, e.g. GET /food-categories, not GET /foodCategories.
  • Never use CRUD function names, e.g. GET /users/list or POST /users/create.

Some perfect API endpoints:

  • GET /users - get a collection/list of users.
  • GET /users/:id - get a single user.
  • POST /users/:id - create a single user.
  • PUT /users/:id - update a single user.
  • DELETE /users/:id - delete a single user.

For sub-resources:

  • GET /users/:id/projects - get projects of given user
  • DELETE /users/:id/projects/:id - remove a project of given user

Adding some features:

  • GET /users?country=earth&verified=true - search users by country and verified
  • GET /users?$fields=name,country - list users, but return only 2 data attributes: name and country
  • GET /users?$sort=name&$order=asc - list users and sort by name in ascending order
  • GET /users/$page=1&$limit=10 - list users and paginate with page and limit

🚫 Errors

Most Google APIs use resource-oriented API design. Instead of defining different NOT_FOUND errors, the server uses one standard NOT_FOUND status code, and tells the client which specific resource was not found.

The smaller error space has advantages:

  • reduces the complexity of documentation
  • better mapping
  • reduces client logic complexity

Model

type Error = {
  code: number
  message: string
  details: any[]
}
Enter fullscreen mode Exit fullscreen mode
  • Error.code: simple code, easily handled by client
  • Error.message: Developer-facing human-readable error
  • Error.details: Additional error information for client, such as retry info, help link, etc.

Code

  • Individual APIs must avoid defining additional error codes.
  • Developers must use canonical error codes.
  • Standard error codes for Google APIs:
    • 200 OK, not an error; returned on success.
    • 500 UNKNOWN, internal server error; unexpected and insufficient info
    • 400 INVALID_ARGUMENT, invalid/problematic user data
    • 404 NOT_FOUND, something doesn't exist globally, for everyone
    • 409 ALREADY_EXISTS, something already exists
    • 403 PERMISSION_DENIED, access denied for a user; relevant people have access
    • 401 UNAUTHENTICATED, no bearer token; no valid auth creds
    • 429 RESOURCE_EXHAUSTED, too many requests; rate-limit exceeded
    • 400 FAILED_PRECONDITION, the operation was rejected; business logic unmet
    • 400 UNAVAILABLE, the operation was rejected; user should retry

Message

Error messages should help users understand & resolve API errors easily.

  • Do not assume the user to an expert user of the API.
  • Do not assume user knows anything about service implementation.
  • Should be constructed such that technical users can respond, and correct it.
  • Keep the message brief. If needed, provide a link to more info, questions, feedback. Otherwise, use "details" field.

Top comments (0)