How to correctly design REST APIs

midnqp

Muhammad Bin Zafar

Posted on August 18, 2022

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.
πŸ’– πŸ’ͺ πŸ™… 🚩
midnqp
Muhammad Bin Zafar

Posted on August 18, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related