Representational Design

The output of your API is extremely important. The goal of representational design is to create some level of consistency in order to facilitate the client's consumption of your service. This section goes over thoughts and considerations that should be made when designing the output response of your RESTful API.

Choosing a Format: JSON vs XML

JSON has definitely taken over XML in popularity when it comes to building internal RESTful APIs. Most newer systems by companies will most likely only support JSON for the client to consume. It isn't uncommon now for newer developers to have never even worked with XML in their life. Larger companies who provide a public RESTful API will generally support both JSON and XML, Twitter is a great example of this, but that is quickly changing day by day.

At the end of the day, supporting JSON and/or XML is a decision that only you can make. My recommendation is that if you are building an internal RESTful API, pick one format and stick with it, it will most likely be JSON. If you are creating a public RESTful API, prioritize JSON over XML.

Using a Consistently Wrapped Response

Consistency is key when building a RESTful API. A consistent output response makes the lives of both the consumer and other developers building the API much easier, because a consistently wrapped response is self-documenting for both the client and server. There are really only 2 types of responses, a success response and an error response. Below are examples of each.

Success Response

I personally use this format for all of my RESTful APIs I build.

  1. Code – This can be the HTTP status code or some sort of internal status code.

  2. Status – This can be either "success" or "error" depending on the state of the call.

  3. Message – This is for both the client consuming the API and the developers creating the API so that it may be use for documenting and debugging purposes.

  4. Data – That contains the meat and potatoes of the API, usually it is the specified resource or a collection of resources that is being asked from the client.

A successful response in a consistently wrapped style may look something like this:

{
  "status": "success",
  "code": 200,
  "message": "Here is the user with the specified id.",
  "data": {
    "id": 12345,
    "first_name": "John",
    "last_name": "Doe",
    "height": "1.8m"
  }
  "errors": null
}

Error Response

Here is an example of an error response in a consistently wrapped style output:

{
  "status": "error",
  "code": 404,
  "message": "Unable to fetch entitiy with the specified id.",
  "data": null,
  "errors": ["The user with the id {user_id} is was not found."]
}

Whatever style of output response you choose to have, the key thing here is to always keep it consistent.

Data Masking, Transformers and DTOs

Security is a big aspect when creating RESTful APIs. A common mistake I've seen from developers is when an output response mirrors the internals of a database schema. This is extremely common due to the increasingly popular powerful ORMs (Object Relational Mapper) that developers have access to today. Instead of exposing every single possible field from the database, it is important to mask the data via transformers and DTOs.

Do note that we will talk more about DTOs, also known as Data Transfer Objects, in the Input Request section of chapter 5.

Example, consider the following User model created in mongoDB.

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const UserSchema = new Schema({
  first_name: {
    type: String,
    required: true
  },
  last_name: {
    type: String,
    required: false
  },
  email: {
    type: String,
    required: true,
    unique: true
  },
  password: {
    type: String,
    required: true
  },
  phone_number: {
    type: String,
    required: true
  }
})

If we were to simply use the ORM as follows.

const UserModel = require('models/UserModel')

const UsersController = async (req, res) => {
  const users = await User.find() // this fetches all the users in our database.
  res.status(200).json([...users])
}

We would expose the password field in out output.

[
    {
      "first_name": "John",
      "last_name": "Doe",
      "email": "john@doe.com",
      "password": "exposed_password_here_even_though_it_may_be_hashed",
      "phone_number": "123456780"
    }
    ...
]

When following HATEOAS and creating links in our RESTful API, a consistent and intuitive representational form should be used to depict the resource's links and relations.

Take for example the following resource:

{
  "user_id": 23,
  "first_name": "Michael",
  "lat_name": "Jordan",
  "_links": {
    "self": {
      "rel": "self",
      "href": "http://nba.com/api/teams/chicago_bulls/players/23"
    }:
    "parent": {
      "rel": "team",
      "href": "http://nba.com/api/teams/chicago_bulls"
    },
    "fire_player": {
      "rel": "self",
      "href": "http://nba.com/api/teams/chicago_bulls/players/23",
      "method": "DELETE",
      "description": "Removes this player from the team's roster."
    }
  }
}

A "_links" key-value pair is provided. Each key in "_links" provides some sort operation on the resource. In this case, "self", "parent", and "fire_player" are operations of the player. The "self" represents the resource itself whereas "rel" and "href" represent the relationship of the link relative to the current resource and the link it goes to. Other optional fields like the HTTP verb "method" and "description" can also be included to provided further self-documentation. Later in this book, we will go deeper in the topic of HATEOAS and linking.

Whatever format or types of representation you choose for links in your RESTful API, as long as it is easy to understand and self-explanatory for the client, then everything should be fine.

Last updated