npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

Iโ€™ve always been into building performant and accessible sites, but lately Iโ€™ve been taking it extremely seriously. So much so that Iโ€™ve been building a tool to help me optimize and monitor the sites that I build to make sure that Iโ€™m making an attempt to offer the best experience to those who visit them. If youโ€™re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, ๐Ÿ‘‹, Iโ€™m Ryan Hefnerย  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If youโ€™re interested in other things Iโ€™m working on, follow me on Twitter or check out the open source projects Iโ€™ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soonโ€“ish.

Open Software & Tools

This site wouldnโ€™t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you ๐Ÿ™

ยฉ 2024 โ€“ย Pkg Stats / Ryan Hefner

staying-alive

v1.0.0

Published

> ๐ŸŽถ Ah, ha, ha, ha, stayin' aliiiiiiiiiiiiiive! [๐ŸŽค](https://www.youtube.com/watch?v=I_izvAbhExY)

Downloads

6,917

Readme

Staying Alive ๐Ÿ•บ

๐ŸŽถ Ah, ha, ha, ha, stayin' aliiiiiiiiiiiiiive! ๐ŸŽค

staying-alive is a funky package that helps creating health endpoints for NodeJS. Whether you need the simplest health check or mutliple probes with different config, you're stayin' alive.

Installation

$ yarn add staying-alive

Usage

Info
If you're using Express, please use the express middleware.
If you're using Koa, please use the koa router.

import { createHealthCheck, optional } from 'staying-alive';
import { databaseCheck } from 'staying-alive/integrations/database';

// create a health check function
const healthCheck = createHealthCheck({
  integrations: {
    'my-database': databaseCheck({
      host: 'localhost',
      port: 5432,
      dbName: 'my-db',
      user: 'postgres',
      password: 'password',
      dialect: 'postgres',
    }),
    'my-redis': optional(
      redisCheck({
        host: 'localhost',
        port: 6379,
      }),
    ),
  },
});

// check health
const health = await healthCheck();

If the database is reachable but the redis is not, the example above will return:

{
  status: 'degraded',
  duration: 0.045,
  integrations: {
    'my-db': {
      alive: true,
      duration: 0.033,
      required: true
    },
    'my-redis': {
      alive: false,
      duration: 0.012,
      required: false
    }
  }
}

All integrations are required by default.

  • If any required integration is not "alive", the overall status will be "error".
  • If only optional integrations are not "alive", the overall status will be "degraded"

To mark an integration as optional, wrap it in optional().

Express middleware

If you're using Express, you can install the middleware like this:

import { healthCheckMiddleware } from 'staying-alive/express';
import express from 'express';

const app = express();

app.use(
  '/health',
  healthCheckMiddleware({
    integrations: {
      'my-database': databaseCheck({
        host: 'localhost',
        port: 5432,
        dbName: 'my-db',
        user: 'postgres',
        password: 'password',
        dialect: 'postgres',
      }),
    },
  }),
);

The Express middleware can also define additional "probes", see healthCheckMiddleware API.

Koa router

If you're using Koa, you can install the health router like this:

import Koa from 'koa';
import { healthCheckRouter } from 'staying-alive/koa';

const app = new Koa();

const healthRouter = healthCheckRouter({
  prefix: '/health',
  integrations: {
    'my-database': databaseCheck({
      host: 'localhost',
      port: 5432,
      dbName: 'my-db',
      user: 'postgres',
      password: 'password',
      dialect: 'postgres',
    }),
  },
});
app.use(healthRouter.routes()).use(healthRouter.allowedMethods());

๐Ÿ“ฆ Requires @koa/router and @types/koa__router for TypeScript.

The Koa router can also define additional "probes", just like the Express middleware. See healthCheckMiddleware API.

Integrations

Integrations are direct dependencies of an app, like its database or cache. They can be checked using staying-alive's built-in integrations, or by building your own.

Integrations will require you to install additional packages, like sequelize for databases or redis for redis.

See the Integrations API:

Writing your own integration

There are 2 ways to use a custom integration: use the customCheck or create a custom integration:

import type { CheckFunction } from 'staying-alive';

function myCustomIntegration(options): CheckFunction {
  return async () => {
    if (somethingFails(options)) {
      throw new Error('it failed!'); // throw to mark failure
    }
    // nothing needs to be returned
  };
}

API

createHealthCheck()

createHealthCheck() is the core function to create a health check function:

import { createHealthCheck } from 'staying-alive';

const healthCheck = createHealthCheck({
  integrations: {
    'name-of-integration': webCheck('https://example.org'),
    // other integrations
  },
});

It will return an async function that will check each integrations:

const health = await healthCheck();

The returned health object contains the following properties:

type HealthObject = {
  status: 'ok' | 'degraded' | 'error';
  duration: number; // the overall time it took to run all checks, in seconds
  integrations?: {
    'name-of-integration': {
      alive: boolean;
      duration: number;
      required: boolean;
    };
  };
};

optional()

All integrations are required by default. To mark an integration as optional, wrap it in optional():

import { createHealthCheck, optional } from 'staying-alive';
import { databaseCheck } from 'staying-alive/integrations/database';

const healthCheck = createHealthCheck({
  integrations: {
    'my-optional-database': optional(databaseCheck(options)),
  },
});

healthCheckMiddleware()

healthCheckMiddleware() returns an Express middleware.

import { healthCheckMiddleware } from 'staying-alive/express';
import express from 'express';

const app = express();

app.use(
  '/health', // the path where all endpoints are mounted
  healthCheckMiddleware({
    integrations: {
      'my-database': databaseCheck(options),
      'my-cache': redisCheck(options),
    },
    probes: {
      liveness: {
        'my-database': 'required',
        'my-cache': 'optional', // here my-cache is marked as optional
      },
      readiness: {
        'my-database': 'required',
        'my-cache': 'required',
      },
    },
  }),
);

The middleware will create multiple endpoints:

  • an overall health endpoint
  • an endpoint for each integration
  • an endpoint for each "probe", where you can redefine if integrations are required or not.

The above example will create the following endpoints:

  • GET /health the overall health endpoint
    • Returns 200 when status is ok or degraded
    • Returns 500 when status is error
  • GET /health/my-database health endpoint for the my-database integration
    • Returns 200 when alive=true
    • Returns 500 when alive=false
  • GET /health/my-web-check health endpoint for the my-database integration
    • Returns 200 when alive=true
    • Returns 500 when alive=false
  • GET /health/liveness
    • Returns the same as the overall /health endpoint, but my-cache is optional
  • GET /health/readiness
    • Returns the same as the overall /health endpoint, all integrations are required

Integrations

Database

import { databaseCheck } from 'staying-alive/integrations/database';

createHealthCheck({
  integrations: {
    'my-db': databaseCheck({
      user: 'postgres',
      password: 'password',
      host: 'localhost',
      port: 5432,
      dbName: 'postgres',
      dialect: 'postgres',
    }),
  },
});

๐Ÿ“ฆ Requires sequelize and an accompanying database client library. See sequelize docs

Options

| Property | Type | Description | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | dbName | string | The name of the database to be checked. | | dialect | Dialect | The dialect of the database to be checked. | | dialectOptions | object | (Optional) The dialect specific options of the database to be checked. | | host | string | The host where the database to be checked is located. | | password | string | The password to connect to the database. | | port | number | The port to connect to the database. | | user | string | The username to connect to the database. |

Redis

import { redisCheck } from 'staying-alive/integrations/redis';

createHealthCheck({
  integrations: {
    'my-redis': redisCheck({
      host: 'localhost',
      port: 6379,
    }),
  },
});

๐Ÿ“ฆ Requires the redis package

Options

| Property | Type | Description | | ---------------- | ------ | ------------------------------------------- | | connectTimeout | number | Optional connection timeout in milliseconds | | db | number | Optional database name | | host | string | Hostname of the Redis server | | password | string | Optional password for authentication | | port | number | Port of the Redis server |

DynamoDB

import { dynamodbCheck } from 'staying-alive/integrations/dynamodb';

createHealthCheck({
  integrations: {
    'my-dynamo': dynamodbCheck({
      region: 'us-east-1',
      credentials: {
        accessKeyId: '***',
        secretAccessKey: '***',
      },
      endpoint: 'http://localhost:8000',
    }),
  },
});

๐Ÿ“ฆ Requires the @aws-sdk/client-dynamodb package

Options

Options for dynamodbCheck is the same object as new DynamoDBClient(options); from @aws-sdk/client-dynamodb: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/interfaces/dynamodbclientconfig.html

Web

import { webCheck } from 'staying-alive/integrations/web';

createHealthCheck({
  integrations: {
    'my-web-check': webCheck('http://example.org', options),
  },
});

๐Ÿ“ฆ Requires the node-fetch package (v2)

Options

The webCheck function accepts the same parameters as the fetch function from node-fetch: https://github.com/node-fetch/node-fetch/tree/2.x#fetchurl-options

Custom

You can write your own custom integration by using the customCheck function:

import { customCheck } from 'staying-alive/integrations/custom';

createHealthCheck({
  integrations: {
    'my-custom-check': customCheck({
      func: async () => {
        await checkSomething();
      },
    }),
  },
});

Options

| Property | Type | Description | | -------- | --------------------- | -------------------------------------------------------------------------- | | func | () => Promise<void> | Async check function. The function is expected to throw if the check fails |

Recipes

Health endpoints with custom route handlers

You can get the same functionalities as the Express middleware (integrations and probe endpoints):

import { createHealthCheck, optional } from 'staying-alive';
import { databaseCheck } from 'staying-alive/integrations/database';

// define integrations
const myDatabase = databaseCheck(options);
const myRedis = redisCheck(options);

// create multiple health check functions
const healthCheckAllRequired = createHealthCheck({
  integrations: {
    'my-database': myDatabase,
    'my-redis': myRedis,
  },
});

const healthCheckRedisOptional = createHealthCheck({
  integrations: {
    'my-database': myDatabase,
    'my-redis': optional(myRedis), // here redis is marked as optional
  },
});

Then simply call the healthCheck functions in your route handlers and return the health object.

Contributing

Running locally

This package must be imported into another to be used. In the test directory are 3 sample packages to do this:

  • package-cjs - a traditional CommonJS package, pure javascript. This package is mostly used to make sure the library can be imported with require() in a JS project.
  • package-esm - an ESM-only package (type: "module"), pure javascript. This package is mostly used to make sure the library can be imported with ESM import in a JS project.
  • package-ts - A package setup with TypeScript. This package holds most of the integration tests that run in CI in src/main.ts.
  • package-ts-koa - A package setup with TypeScript & koa. This package is a demo of using a basic health check with Koa.

Yalc is used to link the packages:

  1. from the repo root, build the library: yarn build
  2. publish the package (locally) with Yalc: yalc publish dist
  3. cd into test/package-ts and run yalc add staying-alive

CI is also using Yalc this way, checkout the CircleCI config.

Testing locally

To run unit tests, yarn test.

To run integration tests:

  • Run docker compose up -d in the root to set up the necessary Docker containers
  • cd into test/package-ts and run yarn && yarn build to install dependencies and build the app.
  • Execute the tests by running node dist/main.js

Adding an integration

To add an integration, create a file in src/integrations. It should export a single function ending with Check, ie. fooCheck(). This function should return an async function () => Promise<void>. To mark a failure, throw an Error.

You'll most likely need an NPM package to implement the integration:

  1. Add the package as a devDependency: yarn add -D some-package
  2. In package.json, copy the devDependency to peerDependencies
  3. In package.json, mark the dependency as optional in peerDependenciesMeta

Don't forget to add an export entry in package.json under the exports key (see package.json for similar entries).

Info
If the integration you want to add requires a package that conflicts with one already installed (ie. if you need redis@2 while this package already depends on redis@3), you'll have to implement this integration in a separate package. There are plans to support this, but not at present. Please talk with Frontend Platform in case you need this.

You'll also need to include an integration test:

  1. Add the service docker image in .circleci/config.yml under jobs.integration-tests.docker
  2. Add 2 checks in test/package-ts/src/main.ts - one should be required and succeed, the other should be optional and fail.
  3. Add an expect call in test/package-ts/src/main.ts to make sure the optional check does fail.

Changesets

This repo uses changesets to manage changelogs and releases.

How it works:

  1. Make your changes in code in a feature branch
  2. Once happy with the changes, run yarn changeset. This will generate a temporary file in .changeset. Commit this file. If your PR includes multiple changes, you can produce multiple changesets.
  3. When the PR is approved and ready to merge, create a release for any changesets by running yarn release. This will remove all the temporary files in .changeset and generate required changes in each package's CHANGELOG.md and package.json.
  4. Commit these changes and push to the feature branch
  5. Merge the feature branch

Please be thoughtful and correct on patch, minor, and breaking releases based on the changes you are introducing.