Tidying-Up-Existing-REST-APIs

What if, one morning, you discover that every internal REST API endpoint of your web application is suddenly displayed as-is in your public REST API documentation? Your Developer Portal is overflowing with messages from eager API users struggling to make integrations with the exciting new functionality the endpoints provide.

  • “Is the name property required on this GET request?”
  • “What is the request body supposed to look like to create a new Blog object?”
  • “I tried to update a User, and now I’m seeing null pointer exceptions everywhere!”

On top of an overflowing portal, not only are the newly posted internal endpoints causing confusion but regressions are being discovered in preexisting public API endpoints too! Whether this scenario feels like a distant bad dream or resonates a little too close to reality, as time and development tickets go by, the quality and conciseness of some existing API endpoints may slowly decline.

From older public endpoints to internal endpoints that may become public, how can you tidy up existing REST API endpoints for public usage? Let’s get tidying!

Strategies for Tidying REST APIs

Rescope the Data

Request and response data can often be closely tied to internal database resources. It can be tempting to include all properties that are available on a resource in the API to support more integration possibilities. However, some resource properties may not be relevant to an API user.

Data Transfer Objects (DTOs), which provide a decoupled representation of your database resources, are particularly useful for making more concise request and response payloads for REST API endpoints. In addition to conciseness, DTOs also improve maintainability and flexibility, allowing for database and service level resources to be updated independently from their corresponding API representations.

DTOs and Database Resources

Using a User resource as an example, a JSON representation of a User database resource may contain the following properties.

json
{
  "id": "string",
  "email": "string",
  "password": "string",
  "roleId": "string",
  "companyId": "string",
  "firstName": "string",
  "lastName": "string",
  "loginAttempts": 0
}

A JSON representation of a User DTO could contain a scoped-down, API-friendly representation of the data, as shown below.

json
{
  "id": "string",
  "email": "string",
  "firstName": "string",
  "lastName": "string"
}

What Properties Should Be Included in the DTO?

For a given resource (e.g., a User), consider the following process for crafting a DTO representation:

  • Begin with an empty DTO.
  • Consider each property and relationship of the resource individually (e.g. User.email, User.loginAttempts, etc…).
  • Reflect on the value of including the property or relationship in the API.
  • User.email is high value in an API endpoint for both identifying the user or creating an integration to email the user.
  • User.loginAttempts may only be relevant to the internal web application and omitting it from the API may make the endpoint more concise.

It can be difficult to decide to omit an available property from a resource’s DTO representation in an API. However, as API users build out integrations, it’s less complicated to add a property to an API endpoint by popular demand rather than having to risk breaking backward compatibility by removing a potentially unused existing property.

If introducing a DTO on an existing API endpoint’s request or response would break API compatibility, consider creating a separate endpoint for the DTO implementation and coordinating a migration or deprecation strategy with API users.

Observe the UI

A single front-end change that works with what is available can be more valuable to a team in the short term than multiple changes across the stack, saving time and precious story points. However, over time, this can cause the alignment between the front-end and back-end to decline, which could call for a reassessment of the existing API endpoint.

For example, a radio button component with three options in a UI may be represented by three corresponding boolean properties in the API, where each option was added individually over time in separate code contributions. However, after taking a look at the current state of the functionality, the radio button component as a whole may be better represented in the API via a single enum property with values for each option.

If your web app has a user interface, observe how an existing endpoint is used in the frontend:

  • Are there unused properties on the endpoint?
  • Is data being transformed in the frontend to accommodate the endpoint, where the endpoint could be modified itself?
  • Are other endpoints being referenced where a concise, composite endpoint that supports the same functionality would be better suited?

Once these questions have been addressed, consider updating the API endpoint accordingly to align it closer to how it’s currently being used.

Reference a Guideline

If you have existing API guidelines for your public endpoints, dust them off! If you don’t have API guidelines, consider modeling existing API guidelines (e.g. Zalando, Microsoft, Google) or creating your own from API best practices.

Some examples of API guidelines to improve the consistency and clarity of an API could include:

  • Are the API documentation descriptions concise, accurate, or relevant?
  • Are URL paths aligned (using camelCase vs. kebab-case)?
  • When should query parameters be used vs. request body?

Once you have API guidelines in place, pass through your API and capture any notable deviations in some API maintenance tickets. With defined API guidelines, there is also an opportunity to integrate the guidelines into code review automation to ensure that the guidelines are preserved going forward.

Notate Required-Ness

As API endpoints may expand over time, identifying what request body properties or query parameters are actually required can become daunting. It can be incredibly valuable to take a second look at an existing endpoint, test it, and even dig into the underlying code to determine what is truly required. Once the required properties on an endpoint have been identified, ensure that the properties are noted as being required in the API documentation as well.

Break It Down

Some endpoints can carry a lot of responsibility, perhaps even snowballing in scope over time. In particular, endpoints that update resources can have large request body payloads containing multiple related objects, making it difficult to break down and simplify the endpoint.

While CRUD (Create, Read, Update, Delete) does not necessarily match the HTTP methods of REST 1-to-1, the CRUD methodology does provide a widely adopted and straightforward framework for breaking down a resource’s endpoint functionality into a handful of more concise endpoints.

Let’s use the example of an update endpoint for a User resource that has a Blog relationship resource in the request payload.

json
{
  "email": "string",
  "firstName": "string",
  "lastName": "string",
  "blogs": [
    {
      "id": "string",
      "title": "string",
      "content": "string"
    }
  ]
}

The existing endpoint allows an API user to update a User while also creating or updating an attached Blog.

  • Consider the unique resources in the endpoint’s request.
  • If each resource had its own Create, Read, Update, or Delete API endpoints, how could similar functionality be achieved (albeit with more requests)?
  • Could an existing object be represented as an ID instead of the full object?

After answering these questions, a decision could be made to:

  • Remove the Blog from the User update endpoint.
  • Write new endpoints for updating or creating a Blog that accept the User.id to establish the relationship.
  • Achieve similar functionality through the new, more concise endpoints.

The new create or update endpoints for a Blog could then have a payload similar to the following.

json
{
  "title": "string",
  "content": "string",
  "userId": "string"
}

Additionally, it may be valuable to include usage documentation to accompany the new endpoint flow. While there is a case to be made that multiple endpoints could be expensive for paid APIs or less performant, the introduction of new concise endpoints can additionally provide more flexibility to your API and potential integrations.

Next Steps

As development moves forward and edge cases arise, it can be worth considering these tips when refactoring or reviewing API changes.

  • Rescope the data: Evaluate the necessity of resource properties.
  • Observe the UI: Leverage the current UI to inform API decisions.
  • Reference a guideline: Align your API and adopt best practices with a guide.
  • Notate required-ness: Ensure that optional and required properties are up-to-date.
  • Break it down: Evaluate how endpoint functionality could be replicated with scoped-down endpoints.

REST API maintenance is a continuous process. When there is routine attention to the accuracy, relevance, and clarity of existing API endpoints, API users and developers alike can be more confident in the use cases and integrations they create and support.