πŸ“‚API client version using headers

Enable multiple responses from the same instance of the API using a simple mechanism.

One typical way to define expectations on an API is to use versioning. While there are several ways to do thisβ€”for example, refer to this article from Nordic APIsβ€”we are going to use a header to decide which API backend to actually activate for the request.

GraphQL is a different beast when it comes to versioning. See this section as REST-specific in its details, but a good idea overall as long as you adapt to what versioning means for your protocol.

🎯 Example: See src/FakeUser/controllers/FakeUserController.ts and note how the header is handled in checkInput().

src/FakeUser/controllers/FakeUserController.ts
/**
 * @description Check and validate input.
 */
function checkInput(event: APIGatewayProxyEvent): string {
  const clientVersion =
    event?.headers["X-Client-Version"] || event?.headers["x-client-version"];
  const isClientVersionValid = validateClientVersion(clientVersion || "");
  const userId = event?.requestContext?.authorizer?.principalId;
  const isUserValid = validateUserId(userId || "");

  if (!isClientVersionValid || !isUserValid)
    throw new Error("Invalid client version or user!");

  return clientVersion || "";
}

An alternative and perhaps more commonly used approach would be to deploy a new instance of the APIβ€”like api.com/v2β€”but of course, this would create further hardware segregation (having a separate v1 and v2 instance), which we want to avoid.

So, while it may be non-standard, in this context version 2 of the API represents the beta, meaning that version 1 represents the current (or "stable", "old") variant.

With this, we have created a way to dynamically define our response simply through a header, without resorting to separate codebases or separate deployments. No need for anything more complicated, as long as we handle this logic in a well-engineered way.

Last updated