Skip to content

Node.js SDK Guide

This guide covers adding Liteguard to a Node.js application. For browser applications see the JavaScript guide. For React applications see the React guide.


Prerequisites

  • A Project Client Token from your Liteguard project. Follow the Getting Started guide through the Copy a Project Client Token step if you do not have one yet.
  • Node.js 18 or later.

Install the SDK

bash
npm install @liteguard/liteguard

The package resolves to the Node.js runtime automatically when imported in a Node.js process. It uses AsyncLocalStorage for per-request scope propagation and process.hrtime / process.memoryUsage for measurement. If you want to be explicit, you can install @liteguard/liteguard-node directly — the API is identical.


Initialize the client

Create a single LiteguardClient instance and call start() during application startup, before you begin accepting requests.

ts
import { LiteguardClient } from '@liteguard/liteguard';

const client = new LiteguardClient(process.env.LITEGUARD_TOKEN!);
await client.start();

start() fetches the initial guard bundle and starts background refresh and flush timers. It does not need to be called again during the process lifetime.

Do not commit your token to source control. Read it from an environment variable or secrets manager.

Environment

ts
const client = new LiteguardClient(process.env.LITEGUARD_TOKEN!, {
  environment: process.env.LITEGUARD_ENV ?? 'default',
});

The slug must match one of the environments defined in your workspace. Slugs are visible in Config > Workspace > [your workspace] under Environments.


Evaluate a guard

client.isOpen(guardName) is synchronous and reads from the locally cached bundle. No network call occurs per check.

When you use TypeScript, string literals passed to isOpen, peekIsOpen, evaluate, and executeIfOpen are checked at compile time against the guard-name contract. Dynamically constructed strings are not validated in the client.

ts
if (client.isOpen('payments.checkout')) {
  // guarded code path
}

Passing properties per call

Pass a properties map to match against rules configured in the Liteguard UI:

ts
if (client.isOpen('payments.checkout', {
  properties: {
    userId: req.user.id,
    plan: req.user.plan,
  },
})) {
  // guarded code path
}

For request handlers that evaluate multiple guards, create a scope once with the caller's properties and pass it to each check:

ts
const scope = client.createScope({
  properties: {
    userId: req.user.id,
    plan: req.user.plan,
  },
});

if (scope.isOpen('payments.checkout')) { /* ... */ }
if (scope.isOpen('billing.invoice_download')) { /* ... */ }

Scopes are immutable and cheap to create. Derive variants using scope.withProperties(...).

Async context propagation

The Node.js runtime uses AsyncLocalStorage to propagate a scope through await chains automatically. Set the scope at the top of a request handler and all downstream client.isOpen() calls pick it up without needing to pass the scope explicitly:

ts
app.use(async (req, res, next) => {
  await client.runWithScope(
    client.createScope({ properties: { userId: req.user.id } }),
    () => next(),
  );
});

// Elsewhere in the same request:
client.isOpen('feature.x'); // picks up userId automatically

Shut down cleanly

Flush buffered telemetry before your process exits:

ts
process.on('SIGTERM', async () => {
  await client.stop();
  process.exit(0);
});

stop() sends any pending signals and stops the background timers.


Verify in Liteguard

After calling isOpen at least once, open the Guards tab and confirm your guard appears. See Your First Guard to configure its behavior.


Next steps