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
npm install @liteguard/liteguardThe 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.
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
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.
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:
if (client.isOpen('payments.checkout', {
properties: {
userId: req.user.id,
plan: req.user.plan,
},
})) {
// guarded code path
}Per-request scopes (recommended)
For request handlers that evaluate multiple guards, create a scope once with the caller's properties and pass it to each check:
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:
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 automaticallyShut down cleanly
Flush buffered telemetry before your process exits:
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
- React guide — if you are using React on the server or in a full-stack framework
- Your First Guard
- Concepts: guards
- Guarding patterns: new feature