Senzing Node.js SDK
    Preparing search index...

    Deployment Guide

    Production deployment guide for @senzing/sdk (sz-napi).


    The @senzing/sdk package contains only the NAPI bridge binary (a few MB). The Senzing runtime libraries and support data must be present in the container. Use an Ubuntu or Debian base image and install senzingsdk-runtime via apt.

    FROM ubuntu:24.04

    # Install Senzing runtime
    RUN apt-get update && apt-get install -y curl gnupg && \
    curl -fsSL https://senzing-production-apt.s3.amazonaws.com/senzingstaging.conf \
    | tee /etc/apt/sources.list.d/senzing.list && \
    apt-get update && apt-get install -y senzingsdk-runtime && \
    rm -rf /var/lib/apt/lists/*

    # Make Senzing libraries visible to the dynamic linker
    ENV LD_LIBRARY_PATH=/opt/senzing/er/lib

    WORKDIR /app

    # Copy package files and install dependencies
    COPY package*.json ./
    RUN npm ci --omit=dev

    # Copy application source
    COPY . .

    CMD ["node", "dist/server.js"]

    Senzing support data (the SUPPORTPATH) can be large. Mount it from the host or a dedicated data volume rather than baking it into the image:

    # docker-compose.yml excerpt
    services:
    app:
    image: my-senzing-app
    environment:
    - LD_LIBRARY_PATH=/opt/senzing/er/lib
    volumes:
    - senzing-data:/opt/senzing/data:ro

    volumes:
    senzing-data:
    external: true

    SQLite is convenient for development but not suitable for production. Use PostgreSQL for concurrent access, durability, and scale.

    Pass this as the settings argument to SzEnvironment:

    {
    "PIPELINE": {
    "CONFIGPATH": "/opt/senzing/er/resources/templates",
    "RESOURCEPATH": "/opt/senzing/er/resources",
    "SUPPORTPATH": "/opt/senzing/data"
    },
    "SQL": {
    "CONNECTION": "postgresql://user:password@host:5432/senzing"
    }
    }
    import { SzEnvironment } from "@senzing/sdk";

    const settings = JSON.stringify({
    PIPELINE: {
    CONFIGPATH: "/opt/senzing/er/resources/templates",
    RESOURCEPATH: "/opt/senzing/er/resources",
    SUPPORTPATH: "/opt/senzing/data",
    },
    SQL: {
    CONNECTION: process.env.SENZING_DB_URL,
    },
    });

    const env = new SzEnvironment("my-service", settings);

    Keep credentials out of source code. Read SENZING_DB_URL (or equivalent) from an environment variable, a secrets manager, or a mounted secret file at startup.


    The Senzing shared libraries must be on the dynamic linker search path before Node.js starts.

    Platform Variable Typical value
    Linux LD_LIBRARY_PATH /opt/senzing/er/lib

    On macOS, the prebuilt .node binary embeds an rpath so no DYLD_LIBRARY_PATH is needed. On Linux production deployments (Docker, systemd), set LD_LIBRARY_PATH in your shell profile, systemd unit file, Docker ENV directive, or Kubernetes env block — not inside your Node.js process, because the dynamic linker reads them before the process starts.

    # systemd unit excerpt
    [Service]
    Environment=LD_LIBRARY_PATH=/opt/senzing/er/lib
    Environment=SENZING_DB_URL=postgresql://user:password@db:5432/senzing
    ExecStart=/usr/bin/node /app/dist/server.js

    Construct the settings object at process startup from environment variables so no credentials are hardcoded:

    function buildSettings(): string {
    return JSON.stringify({
    PIPELINE: {
    CONFIGPATH:
    process.env.SENZING_CONFIGPATH ?? "/opt/senzing/er/resources/templates",
    RESOURCEPATH:
    process.env.SENZING_RESOURCEPATH ?? "/opt/senzing/er/resources",
    SUPPORTPATH: process.env.SENZING_SUPPORTPATH ?? "/opt/senzing/data",
    },
    SQL: {
    CONNECTION: process.env.SENZING_DB_URL,
    },
    });
    }

    const env = new SzEnvironment("my-service", buildSettings());

    NAPI-RS schedules each synchronous Rust function on the libuv thread pool, so engine calls never block the Node.js event loop. Multiple calls can be in-flight simultaneously; the Senzing engine handles internal locking.

    By default libuv uses 4 worker threads. For heavy ingestion workloads, increase this before the process starts:

    UV_THREADPOOL_SIZE=16 node dist/server.js
    

    Set UV_THREADPOOL_SIZE to match the expected concurrency of simultaneous engine calls. Values larger than the number of CPU cores rarely help and can increase contention.

    For Electron apps or services where you want hard isolation between workloads, use worker_threads. Each worker creates its own SzEnvironment:

    import { Worker } from "node:worker_threads";
    import { fileURLToPath } from "node:url";

    const settings = buildSettings();

    // Spawn a worker that owns its own SzEnvironment
    const worker = new Worker(
    fileURLToPath(new URL("./worker.js", import.meta.url)),
    { workerData: { settings } },
    );

    worker.on("message", (result) => {
    console.log("Worker result:", result);
    });

    worker.postMessage({
    type: "add-record",
    dataSourceCode: "DS",
    recordId: "1",
    recordDefinition: "{}",
    });

    The worker file initializes its own environment and processes messages:

    // worker.ts
    import { isMainThread, parentPort, workerData } from "node:worker_threads";
    import { SzEnvironment, SzFlags } from "@senzing/sdk";

    if (!isMainThread && parentPort) {
    const env = new SzEnvironment("worker", workerData.settings);
    const engine = env.getEngine();

    parentPort.postMessage({ type: "ready" });

    parentPort.on("message", (msg) => {
    if (msg.type === "shutdown") {
    env.destroy();
    process.exit(0);
    }
    // handle other message types ...
    });
    }

    See examples/worker-threads/ for the complete bidirectional pattern.


    engine.getStats() returns a JSON string with internal performance counters: throughput rates, cache hit ratios, thread workload distribution, and timing histograms. Log these periodically or expose them via a health endpoint.

    import { SzEnvironment } from "@senzing/sdk";

    function logStats(env: SzEnvironment): void {
    const raw = env.getEngine().getStats();
    const stats = JSON.parse(raw) as Record<string, unknown>;

    // Log the full stats object at debug level
    console.debug("senzing_stats", JSON.stringify(stats));

    // Extract key counters for a metrics system
    const workload = stats["workload"] as Record<string, number> | undefined;
    if (workload) {
    console.info("senzing_workload", {
    addRecordCount: workload["addedRecords"] ?? 0,
    reresolutionCount: workload["reresolutionTriggered"] ?? 0,
    reresolutionTime: workload["reresolutionTime"] ?? 0,
    });
    }
    }

    // Emit stats every 60 seconds
    setInterval(() => logStats(env), 60_000);

    For production, emit these counters to your observability platform (Datadog, Prometheus, CloudWatch, etc.) rather than stdout.


    npm installs the prebuilt .node binary for your platform automatically via optional dependencies. No Rust toolchain or build step is needed in production.

    The prebuilt artifacts are published as platform-specific packages:

    Package Platform
    @senzing/sdk-darwin-arm64 macOS arm64
    @senzing/sdk-linux-x64-gnu Linux x64
    @senzing/sdk-linux-arm64-gnu Linux arm64
    @senzing/sdk-win32-x64-msvc Windows x64

    When npm runs npm install @senzing/sdk, it resolves and downloads only the binary matching the current platform.

    If the target host cannot reach the npm registry, download the .node artifact directly from the GitHub Releases page for the sz-napi repository. Place the file alongside sdk.js in the @senzing/sdk package directory. The JS entry point loads the .node file by platform name — no other configuration is required.

    # Example: manually place the Linux x64 binary
    cp senzing-sdk.linux-x64-gnu.node node_modules/@senzing/sdk/