REST API Design: Health Check Endpoint

A health-check or simply health endpoint can be very useful for testing and inspecting a running API, especially when rather implementation-specific information is used. It can also be used as the endpoint called by automated monitoring and alerting.

For a microservice, the health often depends on several dependencies. The health endpoint can list the status of the dependencies.

An example call:

GET /api/health

{
  "healthy": true,
  "dependencies": [
    {
      "name": "serviceA",
      "healthy": true
    },
    {
      "name": "serviceB",
      "healthy": true
    }
  ]
}

Some other options for naming: “status”, or “isHealthy”.

The response status code is 200 OK for a healthy API, and a good choice for unhealthy is 503 Service Unavailable.

Getting more specific, we can design sub-resources for health information pertaining to subdomains or functionality of the API.
For a concrete example, imagine an API which can report on the status of a data ingest service as part of its own health:

GET /api/health/data-ingest

{
  "isHealthy": false,
  "databaseUpdatedAt": 1592716496,
  "memoryUsage": "255MB"
}

This sub-resource gives us specific information about a data ingest subsystem.

We can use this sort of design to make monitoring more granular: imagine an alert being fired only when certain specific health sub-resources return an error status, but not all.

Using Headers for Health Status

Another option is to use HTTP headers for specific details and keep the JSON result body small, showing only the status. For example:

GET /api/health/data-ingest

Content-type: application/json
X-Health-Database-Updated-At: 1592716496
X-Health-Memory-Usage": 255MB

{
  "healthy": true
}

 

Get Current Date in Unix Epoch Time in JavaScript

We can get the current Unix epoch timestamp in JavaScript (e.g. Node.js) with the following:

const epoch = Math.round(new Date().getTime() / 1000) 
console.log(epoch)

Result:

1601941415

We can also use valueOf:

const epoch = Math.round(new Date().valueOf() / 1000)
console.log(epoch)

Result:

1601941860

Probably the best way is using the static method now from Date:

const epoch = Math.round(Date.now() / 1000) 
console.log(epoch)

Result:

1601941936

Note that we need to divide by 1000 because the original result is in milliseconds.

 

Dynamically Generate Variable Names in Perl

NOTE: this is not recommended, but it is a powerful feature which can be useful.

We can generate dynamic variable names in Perl using Symbolic References. To use the feature, we have to turn off strict refs.

The code below generates the variable names ‘var1’, ‘var2’, ‘var3’ dynamically in a loop as strings, names which can be used as actual variable names with the help of symbolic references.
Of course, hashes should be used instead whenever possible; this is for demonstration.

use strict;
 
our $var1 = 'a';
our $var2 = 'b';
our $var3 = 'c';

for (my $i = 1; $i < 4; $i++) {
  my $variableName;
  {
    # Symbolic References require 'no strict'.
    no strict 'refs';
    $variableName = ${'var' . $i}; # Dynamic name.
  }
  print $variableName . "\n";
}

Output:

a
b
c

 

Istanbul Ignore Syntax for Jest Code Coverage

Istanbul is the tool Jest uses to calculate test coverage. Sometimes we need to exclude some code from the coverage calculations. This is done with special comments which are parsed by Istanbul. There are a few variations of the syntax.

Ignore a Function

/* istanbul ignore next */
const f = () => {
  return 'abc'
}

This will exclude the entire function from code coverage requirements.

Ignore a Whole File

/* istanbul ignore file */

... file contents ...

Use this as the first line of the file. The entire file will be excluded from code coverage.

Ignore a Method in a Class

class A {
  f() {
    console.log("f called")
  }
  /* istanbul ignore next */ g() {
    console.log("g called")
  }
}

The comment must be on or above the line defining the method so it is not part of the coverage requirement.

Function Inside an Exported Object

Sometimes we have a module which exports some functions inside an object.
The example below shows how to ignore these for coverage. The comment must be right before the function definition.

module.exports = {
  f: () => { 
    console.log('f called')
  },
  g: /* istanbul ignore next */ () => {
    console.log('g called')
  },
  h: /* istanbul ignore next */ async () => {
    console.log('h called')
  }
}

Note that for async functions we must place the comment before the async keyword.

Ignore Else Cases

To ignore just the else case of a block of code for test coverage, use the syntax as below.

function f(x) {
  /* istanbul ignore else */
  if (x >= 0) {
    console.log('positive')
  } else { // Ignore this block for code coverage.
    console.log('negative')
  }
}

NOTE: the ignore-else comment is placed above the if statement to ignore the else case for coverage.

References

https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md

 

Log Values Inside Ramda.js Pipelines to the Console

It can be difficult to debug by examining intermediate values inside Ramda pipelines with console.log.

The Ramda function tap comes in handy here and is like a wiretap: we can use it to tap into a functional pipeline to insert some behaviour in between steps, while letting the pipeline continue to work as usual. We can insert R.tap in between functions inside pipelines built with R.compose or R.pipe.

In diagram form:

A -> B -> C
A -> B -> R.tap(fn) -> C

(pipeline proceeds as usual, passing output from B to the input of C)

The following simple example reverses a list and then takes the average (mean).

const R = require('ramda')
 
const f = R.compose(
  R.mean, // Take average.
  R.reverse // Reverse elements.
)

const result = f([1, 2, 3])

console.log(result)

This prints:

2

Suppose we want to debug this function composition and print out the intermediate reversed list.

We can insert the “wiretap” with R.tap with a function to log the input to see the intermediate value flowing through:

const R = require('ramda')
 
const f = R.compose(
  R.mean, // Take average.
  R.tap(x => console.log(x)), // Print and continue.
  R.reverse // Reverse elements.
)

const result = f([1, 2, 3])

console.log(result)

Result:

[ 3, 2, 1 ]
2

This technique can be very handy to debug more complex code using pipe and compose by inserting R.tap.

References

https://ramdajs.com/docs/#tap

 

Require an Object to Contain at Least One Key in a Joi Schema

When validating JSON objects with Joi in JavaScript we sometimes need an object to have any one of a set of possible keys, i.e. one or more of the specific keys given.

The schema below achieves this with the or() function.

const customerSchema = Joi.object().keys({
  id: Joi.string().optional(),
  accountNumber: Joi.number().optional(),
  emailAddress: Joi.string().optional()
})
.or('id', 'accountNumber', 'emailAddress') // At least one of these keys must be in the object to be valid.
.required()

If we supply a JSON object like below to the Joi schema validate function, we will see a Joi schema validation error similar to the example.

{
  "name": "test"
}

Joi validation error:

"message": "object must contain at least one of [id, accountNumber, emailAddress]"

However, any of the following are valid examples:

{
  "id": "1234"
}
{
  "id": "1234",
  "emailAddress": "test@test.com"
}

Note that we can also use the related functions and(), xor(), nand(), and others to achieve other logical requirements on object keys.

 

Spread Operator Applied to Strings in JavaScript

The spread operator () can be used to get an array of characters easily from a string in ES6 and higher JavaScript.

The following code shows how this works:

const str = "hello"

const chars = [...str]

console.log(chars)

The result is:

[ 'h', 'e', 'l', 'l', 'o' ]

 

Interestingly, we can also use the spread the inside an object instead of an array, in which case the operator will assign the characters of the string at each index from 0 to the length of the string minus one, using the indexes as keys in the object. The example below shows this.

const str = "hello"

const arr = { ...str }

console.log(arr)

Result:

{ 
  '0': 'h', 
  '1': 'e', 
  '2': 'l', 
  '3': 'l', 
  '4': 'o' 
}

 

Organize Test Cases using test.each in Jest

There is a better way to organize repetitive or similar test cases in Jest in a table to avoid writing out very similar tests where only specific input parameters change, and avoid duplicating code.

The simple math functions below are the functions to test in this example.

math-functions.js:

const isPerfectSquare = (x) => {
  return Number.isInteger(Math.sqrt(x))
}
 
const isProductPositive = (a, b) => {
  return a * b > 0
}
 
const realSqrt = (x) => {
  if (x < 0) throw new Error('Not defined for negatives')
  return Math.sqrt(x)
}
 
module.exports = { isPerfectSquare, isProductPositive, realSqrt }

For the first kind of test case layout,  we need to define a templated string in a format similar to the one below, listing the input and output for our test cases.

const { isPerfectSquare } = require('./math-functions.js')

const testCases = test.each`
 input     | expectedOutput
 ${'25'}   | ${true}
 ${'144'}  | ${true}
 ${'1024'} | ${true}
 ${'55'}   | ${false}
 ${'0'}    | ${true}
`

describe('perfect square tests', () => {
  testCases('it should return $expectedOutput when input is: $input',
    ({ input, expectedOutput }) => {
      expect(isPerfectSquare(input)).toBe(expectedOutput)
    })
})

Using test.each with Multiple Arguments per Test Case

The template below is used when we need multiple arguments passed into our function for each test case.

const { isProductPositive } = require('./math-functions.js')

const testCases = test.each`
 a       | b       | expectedOutput
 ${'5'}  | ${'20'} | ${true}
 ${'-1'} | ${'7'}  | ${false}
 ${'-4'} | ${'-4'} | ${true}
 `

describe('positive product tests', () => {
  testCases('it should return $expectedOutput for inputs: $a, $b',
    ({ a, b, expectedOutput }) => {
      expect(isProductPositive(a, b)).toBe(expectedOutput)
    })
})

Using a 2D Array of Inputs for Test Cases with test.each

We can use a two-dimensional array to define the test cases instead of the templated string if the cases are true/false or pass/fail; i.e. if the output should be the same for a varying set of inputs.

const { isProductPositive } = require('./math-functions.js')

const trueCases = [
  [5, 20],
  [-4, -4]
]

const falseCases = [
  [-1, 7]
]

describe('positive product tests: true cases', () => {
  test.each(trueCases)(
    'should return true for inputs: %s, %s',
    ( a, b ) => {
      expect(isProductPositive(a, b)).toBe(true)
    })
})

describe('positive product tests: false cases', () => {
  test.each(falseCases)(
    'should return false for inputs: %s, %s',
    ( a, b ) => {
      expect(isProductPositive(a, b)).toBe(false)
    })
})

Grouping Passing and Failing Test Cases

If we have a set of test cases which should pass and those which should throw an error, we can also use a nice clean layout for them.

const { realSqrt } = require('./math-functions.js')

const passingCases = [ 25, 3.141 ]
const errorCases = [ -1, -4.5 ]

describe('passing test cases', () => {
  test.each(passingCases)(
    'should return successfully for input: %s',
    (x) => {
      try {
        realSqrt(x)
      } catch (error) {
        fail('Should not reach here')
      }
    })
})

describe('expected error test cases', () => {
  test.each(errorCases)(
    'should throw error for input: %s',
    (x) => {
      try {
        realSqrt(x)
        fail('Error was not thrown')
      } catch(error) {
        expect(error.message).toBe('Not defined for negatives')
      }
    })
})

Note the fail() function to explicitly fail the test cases for undesired behaviour.

JavaScript Object Destructuring Assignment in Different Contexts

Destructuring Assignment can be used in different contexts, some of which require subtle variations.

The most common context is assignment to a constant:

const myObject = {
  a: 5,
  b: 10
}

const { b } = myObject

// b is 10

Destructuring With A Value Returned from a Function

const { c } = f()

This requires knowledge of the object returned from f, and will only work if the function returns a field named c.

Destructuring Assignment Statements with the let Keyword

Destructuring assignment can be used with let, but we must use parentheses to give the interpreter some help so the syntax is not ambiguous.

const myObject = {
  a: 5,
  b: 10,
  c: 15
}

let b

({ b } = myObject)

console.log(b); // Required semicolon.

({ c } = myObject)

console.log(c)

This prints:

10
15

Without the parentheses the interpreter gets confused and we get an error:

SyntaxError: Unexpected token =

Parentheses are required to force the interpreter to treat the braces as destructuring assignment instead of assignment to an object literal.

Destructuring in Objects Passed in as Function Arguments

This is a way of requiring a specific object structure only as an argument.
If we define our function as follows:

const f = ( {x, y} ) => {
  console.log("x is: " + x)
  console.log("y is: " + y)
}

Then attempt to use it as below:

const a = 5
const b = 10
f({a, b})

The function prints:

x is: undefined
y is: undefined

The destructuring inside the argument object forces only the specific object structure {a,b} to be a valid input for the function.
Only the code below behaves as desired:

const x = 5
const y = 10

f({x, y})

This prints:

x is: 5
y is: 10

Destructuring Syntax not to be Confused with Returning Literal Objects

Note that a return statement with a literal object is not destructuring:

return { x, y }

This simply returns an object with fields x and y.

Destructuring and Renaming Fields

We can add a colon (:) and a new name after a named field in destructuring assignment in order to rename a field.
Example:

const myObject = {
  r: 10,
  s: 20,
}

const { r: newName } = myObject

console.log(newName) // Will print 10.

Destructure an Object with Remaining Leftover Fields

We can destructure an object taking specific fields we want, and leaving the rest inside the rest variable.

const obj = {
  a: 1,
  b: 2,
  c: 3,
  d: 4
}

const { a, ...rest } = obj

console.log(rest)

This outputs:

{ b: 2, c: 3, d: 4 }

Note that this does not have to be in any specific order.
If we choose to extract c instead as below:

const { c, ...rest } = obj

Our result rest object will be equal to:

{ a: 1, b: 2, d: 4 }

REST API Design for Usability: Human-Readable Date Fields

An API with great Developer Experience (DX) returns responses as immediately understandable as possible, and one common and easily achieved usability feature is to include human-readable or “pretty” date or timestamp fields in JSON representations, in addition to the Unix Epoch timestamps preferred for machine consumption.

In a software project an API can be used by many kinds of technical people, from developers to QA to Product Owners, to test functionality or examine data. In many cases the timestamps on the data are valuable information, and reading them quickly can be very handy and boost productivity, for example to see how recent data is, if it is valid and sorted correctly, and so on.

The following example shows an updated-at field on a response with an added pretty version of the field:

GET /resources/1

{
  "id": 12345,
  "updatedAt": 1580778703,
  "updatedAtPretty": "Tue, 04 Feb 2020 01:11:43 GMT"
}

If using a specific format, we can also include ISO or something similar in the name, referring to the format:

{
  "id": 12345,
  "updatedAt": 1580778877,
  "updatedAtIso": "2020-02-04T01:14:37Z"
}

We could also use updatedAtIso8601 if using this specific readable format. Two other options for general human-readable date fields:

updatedAtReadable
updatedAtHuman

This extra field should not affect the response size much since all API responses should be gzipped.

However, if small size is very important we can always make the feature optional, so that the readable date is only added if a query string parameter is included. This post discusses this pattern in general. Such a request could look like the following:

GET /resources?prettyDates=true

Readable Versions of Other Types of API Fields

Dates and times are not the only kinds of data which could benefit from readable versions. If we have other kinds of human-readable fields, the parameter could control several fields in addition to just the readable date. Suppose we have a field called fileSizeReadable for example, to complement a field expressed in bytes. For a parameter controlling all human-readable fields we could use:

GET /resources?readable=true

GET /resources?human=true

To show a complete example, a default response:

GET /resources/1

{
  "id": 12345,
  "updatedAt": 1580778877,
  "fileSize": 95098036
}

The same call including the pretty fields:

GET /resources/1?human=true

{
  "id": 12345,
  "updatedAt": 1580778877,
  "updatedAtPretty": "2020-02-04T01:14:37Z",
  "fileSize": 95098036,
  "fileSizePretty": "138.3 MB"
}