Node.js Integration
Rift provides official Node.js bindings via the @rift-vs/rift npm package. This package is a drop-in replacement for Mountebank’s Node.js API, making migration seamless.
The Node.js SDK lives in its own repository. It is developed and published from
achird-labs/rift-node, which owns the npm release. This engine repository no longer contains or publishes it — file SDK issues there, and engine issues here.
Installation
npm install @rift-vs/rift
There is no postinstall download. Nothing is fetched at npm install time — the engine binary is resolved lazily, the first time it’s actually needed (rift.spawn() or the Mountebank-compat create()), in this order:
- An explicit override —
binaryPathpassed tospawn(), or theRIFT_BINARY_PATHenv var. - The system
PATH— the first ofrift-http-proxy/rift/mbfound, but only if it--version-probes as Rift. A Mountebankmbshadowing the name onPATHis skipped rather than run in Rift’s place. - A previously-downloaded copy in the local version cache.
- A checksummed download from GitHub Releases (or
RIFT_DOWNLOAD_URL/RIFT_MIRROR_URL), cached for next time.
If RIFT_OFFLINE or RIFT_SKIP_BINARY_DOWNLOAD is set, step 4 never runs — resolution throws with manual-install instructions instead of touching the network. Run npx rift-fetch ahead of time (e.g. in CI, or to prep an air-gapped install) to warm the cache so first use doesn’t pay the download cost.
The in-process embedded transport (rift.embedded()) resolves a separate artifact — the librift_ffi cdylib — via the companion @rift-vs/rift-embedded package, following the same override → cache → download order (see below).
Supported Platforms
| Platform | Architecture | libc |
|---|---|---|
| macOS | x64, arm64 | - |
| Linux | x64, arm64 | glibc and musl (musl auto-selected on Alpine) |
| Windows | x64 | - |
Basic Usage
The modern surface is a typed, fluent DSL over three transports that all hand back the same engine client — pick whichever fits how the binary should run:
rift.embedded()— in-process, no child process, needs@rift-vs/rift-embeddedinstalled.rift.spawn()— manages theriftengine binary as a child process for you.rift.connect(url)— attaches to an engine already running elsewhere.
import { rift, imposter, onGet, okJson } from '@rift-vs/rift';
await using engine = await rift.spawn(); // or rift.embedded() / rift.connect(url)
const users = await engine.create(
imposter('users').stub(onGet('/api/users/1').willReturn(okJson({ id: 1, name: 'Alice' })))
);
await fetch(`${users.url}/api/users/1`);
await using closes the engine automatically at the end of scope (Symbol.asyncDispose); call await engine.close() yourself if you’re not in a context that supports it.
Mountebank-compat create()
Existing Mountebank-style code — raw REST calls against the admin API, the mb CLI, or code already written against the pre-0.15 @rift-vs/rift — keeps working unchanged via the default export’s create():
import rift from '@rift-vs/rift';
const server = await rift.create({ port: 2525, loglevel: 'info' });
// existing Mountebank-style REST calls / mb client code work unchanged against server.port
await fetch('http://localhost:2525/imposters', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
port: 4545,
protocol: 'http',
stubs: [{
predicates: [{ equals: { path: '/api/users' } }],
responses: [{ is: { statusCode: 200, body: JSON.stringify([{ id: 1, name: 'Alice' }]) } }]
}]
})
});
const response = await fetch('http://localhost:4545/api/users');
console.log(await response.json()); // [{ id: 1, name: 'Alice' }]
await server.close();
create() is a permanent, first-class drop-in — not deprecated — so adopting the typed DSL above is incremental, not a forced rewrite.
Migration from Mountebank
Migrating from Mountebank is straightforward - just change the import:
Before (Mountebank):
import mb from 'mountebank';
const server = await mb.create({
port: 2525,
loglevel: 'debug',
allowInjection: true,
});
After (Rift):
import rift from '@rift-vs/rift';
const server = await rift.create({
port: 2525,
loglevel: 'debug',
allowInjection: true,
});
All your existing imposter configurations and test code work without changes.
TypeScript Support
Full TypeScript definitions are included:
import rift, { CreateOptions, RiftServer } from '@rift-vs/rift';
const options: CreateOptions = {
port: 2525,
loglevel: 'debug',
};
const server: RiftServer = await rift.create(options);
// Type-safe properties
console.log(server.port); // number
console.log(server.host); // string
await server.close();
API Reference
create(options?: CreateOptions): Promise<RiftServer>
Creates and starts a new Rift server instance.
CreateOptions
| Option | Type | Default | Description |
|---|---|---|---|
port | number | 2525 | Admin API port |
host | string | 'localhost' | Bind address |
loglevel | string | 'info' | Log level: debug, info, warn, error |
logfile | string | - | Path to log file |
datadir | string | - | Directory for imposter persistence (Mountebank --datadir parity) |
ipWhitelist | string[] | - | Allowed IP addresses |
allowInjection | boolean | false | Enable script injection |
impostersRepository and redis (custom Mountebank repository config) are accepted by the type for compatibility but rejected at runtime — Rift’s native binary can’t load a Node repository module. Use datadir for persistence instead.
RiftServer
| Property/Method | Type | Description |
|---|---|---|
port | number | Port the server is listening on |
host | string | Host the server is bound to |
close() | Promise<void> | Gracefully shutdown the server |
Events
The server emits the following events:
exit- Emitted when the server process exitserror- Emitted on server errorsstdout- Emitted with stdout datastderr- Emitted with stderr data
Testing Example with Jest
import rift from '@rift-vs/rift';
describe('API Tests', () => {
let server;
beforeAll(async () => {
server = await rift.create({ port: 2525 });
// Create test imposters
await fetch('http://localhost:2525/imposters', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
port: 4545,
protocol: 'http',
stubs: [{
predicates: [{ equals: { method: 'GET', path: '/health' } }],
responses: [{ is: { statusCode: 200, body: { status: 'ok' } } }]
}]
})
});
});
afterAll(async () => {
await server.close();
});
test('health check returns ok', async () => {
const response = await fetch('http://localhost:4545/health');
const data = await response.json();
expect(data.status).toBe('ok');
});
});
(@rift-vs/rift/testkit/jest also ships a setupRift() helper with automatic per-test imposter teardown — see the SDK README’s “Testkit” section.)
Environment Variables
| Variable | Applies to | Description |
|---|---|---|
RIFT_BINARY_PATH | engine binary | Explicit path override; skips PATH/cache/download |
RIFT_FFI_LIB | embedded cdylib | Explicit path override; skips cache/download (no checksum — you own the file) |
RIFT_CACHE_DIR | embedded cdylib | Overrides the cache root (defaults to XDG_CACHE_HOME, then %LOCALAPPDATA% on Windows, else ~/.cache) |
RIFT_DOWNLOAD_URL | both | Alternate release mirror base |
RIFT_MIRROR_URL | engine binary | Alternate mirror base for the binary only (RIFT_DOWNLOAD_URL wins if both are set) |
RIFT_OFFLINE / RIFT_SKIP_BINARY_DOWNLOAD | both | Air-gapped mode: never reach the network; resolution throws with manual-install instructions if nothing local is found |
RIFT_SKIP_CHECKSUM | engine binary only | Opt out of a missing (not mismatched) checksum sidecar — not available for the cdylib |
Note: these govern binary/library resolution, not installation — there’s nothing to skip during npm install since it never downloads anything (see “Installation” above).
Manual Binary Installation
If resolution can’t reach the network (e.g. behind a firewall) and nothing is cached or on PATH, install the binary manually:
- Download from GitHub Releases
- Point the SDK at it:
export RIFT_BINARY_PATH=/path/to/rift
Or run npx rift-fetch ahead of time on a machine with network access, then copy its resolved cache directory to the air-gapped one.
SDK Development
This engine repository does not contain the Node.js SDK source — it only documents how to use the published package. To build the SDK from source, run its tests, or contribute a change, see achird-labs/rift-node and its README/CONTRIBUTING guide.
Utility Functions
resolveBinary(options?): Promise<string>
The current binary resolver. Resolution order:
options.binaryPathorRIFT_BINARY_PATH, if it exists on disk.- The first of
rift-http-proxy/rift/mbfound onPATHthat--version-probes as Rift (a Mountebankmbshadowing the name is skipped). - A previously-downloaded copy in the local version cache.
- Otherwise, download and checksum-verify the release archive for the resolved version (skipped entirely, throwing instead, when
RIFT_OFFLINE/RIFT_SKIP_BINARY_DOWNLOADis set).
findBinary(): Promise<string> (deprecated)
Legacy wrapper over resolveBinary() limited to steps 1-3 above (no download); kept for backward compatibility. Prefer resolveBinary.
downloadBinary(version?: string): Promise<string> (deprecated)
Legacy wrapper that force-downloads a specific version, bypassing PATH/cache. Prefer resolveBinary({ version }).
getBinaryVersion(): Promise<string | null> (deprecated)
Returns the resolved binary’s version string, or null if not found.
Requirements
- Node.js 20.0.0 or later. The SDK uses the global
fetch,worker_threads, andawait using. - ESM only. The package ships as ES modules with no CommonJS build —
import, notrequire(). - One of the supported platforms (see above)