Understanding REST APIs - A Guide for the Impatient

REST (Representational State Transfer) APIs are the backbone of modern web development. This article will break down how to create and use modern REST APIs, what design decisions to take into consideration when making one, and the theory being the foundation of REST.

Practical Guide

This section dives into using REST APIs with HTTP covering endpoints, methods, requests, and responses. You'll find everything you need to start making API calls and applying REST in your projects.

How to Structure Your URIs

In general there are two main ways you can treat a URI:

  1. As an action
  2. As a resource

Consider the two following URIs:

  1. https://example.com/getUserData?id=1 (action)
  2. https://example.com/users/1 (resource)

Both examples show us retrieving user data of a user with an id of 1. The difference being that in the first example the route /getUserData is performing an action while in the second example the route /users/1 is the location of an asset, it does not indicate what action is being performed. We could say that this type of URI is acting as a noun (as it is a thing instead of an action i.e. a verb).

The REST pattern dictates that we strictly use URIs like the second example. We want our URIs to be nouns so that we can use HTTP Methods as our verbs to perform actions upon those nouns. For example, we can use the HTTP method GET to retrieve information about /users/1, but we could use PUT to update that corresponding user's information, or DELETE to delete the user entirely.

The last thing to note about URIs is that, as with the example above, when referencing an individual resource (e.g. a single user in this case) the URI should end with the unique identifier for that resource. When referencing all resources in a given category, the unique identifier should be omitted.

  1. https://example.com/users/1 - References a particular user with an id of 1
  2. https://example.com/users - References all users regardless of id

What Actions to Support

There are 4 main actions to support in REST, we use the acronym CRUD to remember them: Create, Read, Update, Delete. Each of these actions maps to an HTTP Method that we can use to perform that action. The mapping is as follows:

Action HTTP Method
Create POST
Read GET
Update PUT / PATCH
Delete DELETE

All Action + URI Combinations to Support

Every REST API is really just (at a minimum) 5-6 routes. In our example, the base endpoint will be /users and we'll pretend to host it on https://example.com.

  • GET https://example.com/users
    • Action: Return all user assets (each asset is one user)
    • Request Body: Empty
    • Response Body: List of user assets (as a JSON array)
  • GET https://example.com/users/[id] ([id] is a variable)
    • Action: Returns just the singular requested user asset
    • Request Body: Empty
    • Response Body: Just the user asset with the matching id (as JSON)
  • POST https://example.com/users
    • Action: Adds one user asset to the collection
    • Request Body: All data needed to create the new user asset (no specific format required, JSON recommended)
    • Response Body: The newly created asset that was inserted + a unique ID (as JSON)
  • PUT https://example.com/users/[id] ([id] is a variable)
    • Action: Fully replaces just one existing user's data with the given data
    • Request Body: All data needed to replace an existing user's data, whether or not it has changed (minus the id - no specific format required, JSON recommended)
    • Response Body: The newly updated asset with the matching id (as JSON)
  • (Optional) PATCH https://example.com/users/[id] ([id] is a variable)
    • Action: Partially replaces just one existing user's data with the given data
    • Request Body: Only the data that needs updating (minus the id - no specific format required, JSON recommended)
    • Response Body: The newly updated asset with the matching id (as JSON)
  • DELETE https://example.com/users/[id] ([id] is a variable)
    • Action: Deletes just one record from the users table
    • Request Body: None
    • Response Body: None (just HTTP response code) OR The data from the asset that was just deleted with matching id (as JSON)

Design Considerations

Outside of what defines an endpoint as using the REST pattern or not, there are many things to take into consideration before you start building one. Is there a chance that you might want to update your endpoint in the future? Should your output give helpful hints to users? Is REST even the right pattern to use in your situation? Let's answer some of these questions.

Versioning Your Endpoint

It might be a good idea to start thinking about versioning of your API right from the start so that making changes is easier in the future. There are a few different methods for determining what API version your users are choosing to use:

  • URI Versioning
    • The version numbers are incorporated into a URL path, usually at the base
    • Examples:
      • https://example.com/v1/users/1
      • https://example.com/v2/users/1
  • Query Parameter
    • The version number is appended as a query parameter in the API endpoint
    • Examples:
      • https://example.com/users/1?apiVersion=1
      • https://example.com/users/1?apiVersion=2
  • Header based
    • The version number is a specific and unique header field
    • Examples (Request Headers):
      • x-api-version: 1
      • x-api-version: 2
  • Content Negotiation
    • The version is determined based on the representational state or media type.
    • In the example below, the server code would know that firstName is for the first version and that it was changed to givenName in the next version.
    • Examples (Request Body):
      • { firstName: 'Henry' }
      • { givenName: 'Henry' }

Mocking a Quick REST API

Sometimes playing around with one is the best tool to learn how they work. One of my favorite libraries to demo REST is json-server. Setting it up is pretty simple - just a few steps required.

  1. Install JSON Server
npm install json-server
  1. Create a simple data store
{
  "users": [
    { "id": "1", "username": "gorwell", "email": "gorwell@gmail.com" },
    { "id": "2", "username": "cdickens", "email": "cdickens@gmail.com" },
    { "id": "3", "username": "jausten", "email": "jausten@gmail.com" },
    { "id": "4", "username": "vwoolf", "email": "vwoolf@gmail.com" },
    { "id": "5", "username": "sking", "email": "sking@gmail.com" }
  ]
}
  1. Start up the server
npx json-server db.json
  1. Make an HTTP request against your local server
curl -X GET http://localhost:3000/users/1

An Easy CRUD Data Grid

A fully functioning REST endpoint can be hooked up to a data grid easily with ZingGrid, just point the base REST URI at the <zing-grid> element's src attribute like below

<zing-grid
  src="http://localhost:3000/users"
  editor-controls
></zing-grid>
0:00
/0:18

Final Thoughts

REST APIs come in many shapes and sizes across the web, each tailored to meet specific needs. By structuring URIs thoughtfully, choosing the right actions, and keeping versioning in mind, you can create a straightforward, flexible API that developers will find a pleasure to work with. With these foundational steps, even a quick prototype can evolve into a robust, reliable API that stands the test of time.