> For the complete documentation index, see [llms.txt](https://docs.sealights.io/knowledgebase/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sealights.io/knowledgebase/setup-and-configuration/sealights-agents-and-plugins/node.js-agent/capturing-runtime-coverage/backend-instrumentation-dynamic.md).

# Backend Instrumentation (dynamic)

> **Applies to:** backend Node.js services — Express/Nest/Fastify APIs, workers, and similar.

Backend services use **dynamic instrumentation**. SeaLights attaches to the Node.js process at startup and instruments code at runtime.

This is different from [Scanning Your Application](/knowledgebase/setup-and-configuration/sealights-agents-and-plugins/node.js-agent/scanning-your-application.md). `scan` uploads the build map. `run` and `preload.js` attach runtime coverage to the live process.

Nothing is rewritten before deployment. You deploy the original service and start it through SeaLights.

{% stepper %}
{% step %}

### Scan the backend service

Start by scanning the service code. This creates the SeaLights build map.

You need a valid token and Build Session ID first. See [Scanning Your Application → Prerequisites](/knowledgebase/setup-and-configuration/sealights-agents-and-plugins/node.js-agent/scanning-your-application.md#prerequisites).

Use the same app folder in `--scanDir` that you used during the scan.
{% endstep %}

{% step %}

### Start the service with SeaLights

Choose the attach method that matches how you start the service.

* Use **`preload.js`** when you want to keep the original start command unchanged.
* Use **`slnodejs run`** when you control the full launch command.

{% hint style="warning" %}
If you run TypeScript with `ts-node`, prefer **`preload.js`**. It works without extra runner flags.
{% endhint %}

#### Option A — `preload.js`

Inject the agent with `NODE_OPTIONS` and keep your normal startup command unchanged.

{% hint style="info" %}
This also works behind `npm start`, `pm2`, containers, and other startup wrappers. Set `NODE_OPTIONS` and keep the original startup command unchanged.
{% endhint %}

{% tabs %}
{% tab title="Bash" %}

```sh
export SL_tokenfile=./sltoken.txt
export SL_buildsessionidfile=buildSessionId
export SL_labid=my_lab_id
export NODE_OPTIONS="--require ./node_modules/slnodejs/lib/preload.js"

node ./server/app.js
```

{% endtab %}

{% tab title="GitHub Actions" %}

```yaml
- name: Start backend with SeaLights (preload)
  env:
    SL_tokenfile: ./sltoken.txt
    SL_buildsessionidfile: buildSessionId
    SL_labid: my_lab_id
    NODE_OPTIONS: "--require ./node_modules/slnodejs/lib/preload.js"
  run: node ./server/app.js &
```

{% endtab %}

{% tab title="PowerShell" %}

```powershell
$env:SL_tokenfile = ".\sltoken.txt"
$env:SL_buildsessionidfile = "buildSessionId"
$env:SL_labid = "my_lab_id"
$env:NODE_OPTIONS = "--require ./node_modules/slnodejs/lib/preload.js"

node .\server\app.js
```

{% endtab %}

{% tab title="Azure DevOps" %}

```yaml
- script: node ./server/app.js &
  displayName: Start backend with SeaLights (preload)
  env:
    SL_tokenfile: ./sltoken.txt
    SL_buildsessionidfile: buildSessionId
    SL_labid: my_lab_id
    NODE_OPTIONS: "--require ./node_modules/slnodejs/lib/preload.js"
```

{% endtab %}
{% endtabs %}

#### Option B — `slnodejs run`

Wrap the startup command with `run`.

Everything after `--` is passed to your application startup command.

{% tabs %}
{% tab title="Bash" %}

```sh
npx slnodejs run \
  --tokenFile ./sltoken.txt \
  --buildSessionIdFile buildSessionId \
  --scanDir ./server \
  --labId my_lab_id \
  --useInitialColor \
  -- ./server/app.js
```

{% endtab %}

{% tab title="GitHub Actions" %}

```yaml
- name: Start backend with SeaLights (run)
  env:
    SL_TOKEN: ${{ secrets.SL_TOKEN }}
  run: |
    npx slnodejs run \
      --token "$SL_TOKEN" \
      --buildSessionIdFile buildSessionId \
      --scanDir ./server \
      --labId my_lab_id \
      --useInitialColor \
      -- ./server/app.js &
```

{% endtab %}

{% tab title="PowerShell" %}

```powershell
npx slnodejs run `
  --tokenFile .\sltoken.txt `
  --buildSessionIdFile buildSessionId `
  --scanDir .\server `
  --labId my_lab_id `
  --useInitialColor `
  -- .\server\app.js
```

{% endtab %}

{% tab title="Azure DevOps" %}

```yaml
- script: |
    npx slnodejs run \
      --token "$(SL_TOKEN)" \
      --buildSessionIdFile buildSessionId \
      --scanDir ./server \
      --labId my_lab_id \
      --useInitialColor \
      -- ./server/app.js &
  displayName: Start backend with SeaLights (run)
```

{% endtab %}
{% endtabs %}

#### Validate

After startup, confirm that SeaLights is attached and receiving activity.

1. Start the service with one of the methods above.
2. Trigger at least one request, job, or worker execution.
3. Check [Agent Monitor](/knowledgebase/setup-and-configuration/monitoring/sealights-cockpit/agent-monitor.md) for the running agent.
4. Confirm coverage appears after your tests hit the service.
   {% endstep %}

{% step %}

### Run your tests

Once the backend service is running with SeaLights attached, continue to [Capturing Tests](/knowledgebase/setup-and-configuration/sealights-agents-and-plugins/node.js-agent/capturing-tests.md).
{% endstep %}
{% endstepper %}

### Reference

#### Multiple environments and replicas

Use Lab IDs consistently across environments.

* Use the **same `labId`** for multiple replicas of the same environment.
* Use a **different `labId`** for each separate environment, such as `qa-api` and `staging-api`.
* Use **`testStage`** when you want the agent grouped under a specific test stage.

{% hint style="info" %}
If you omit `--labId` or `SL_labid`, SeaLights uses the Build Session ID as the fallback Lab ID.
{% endhint %}

#### Troubleshooting

If the service starts but coverage does not appear, validate the runtime attachment first.

{% tabs %}
{% tab title="Bash" %}

```sh
ps -ef | grep slnodejs
```

{% endtab %}

{% tab title="PowerShell" %}

```powershell
Get-WmiObject Win32_Process | Where-Object { $_.CommandLine -like "*slnodejs*" }
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
If the startup succeeds but the agent does not appear, check whether `NODE_OPTIONS` was replaced by another startup layer.
{% endhint %}

{% hint style="warning" %}
Common issues:

* The `--scanDir` path does not match the folder used during `scan`.
* The `--` separator is missing in `slnodejs run`.
* The process starts correctly, but no traffic reaches instrumented code.
  {% endhint %}

See also:

* [dryRun Command: Troubleshooting and Validation](/knowledgebase/setup-and-configuration/sealights-agents-and-plugins/node.js-agent/advanced-features/dryrun-command-troubleshooting-and-validation.md)
* [Understanding the `--` (Double Dash) Separator in `slnodejs run` Commands](/knowledgebase/setup-and-configuration/troubleshooting-faq/node-javascript/understanding-the-double-dash-separator-in-slnodejs-run-commands.md)

#### Parameters

| Parameter                                   | Purpose                                                                        |
| ------------------------------------------- | ------------------------------------------------------------------------------ |
| `--tokenFile` / `--token`                   | SeaLights authentication. Use a file locally or a secret in CI.                |
| `--buildSessionIdFile` / `--buildSessionId` | Build session identifier. This must match the build you scanned.               |
| `--scanDir`                                 | The service source directory used during `scan`.                               |
| `--labId`                                   | Connects the running service to a specific lab or environment.                 |
| `--testStage`                               | Names the stage this running agent belongs to.                                 |
| `--useInitialColor`                         | Starts collecting coverage from process startup, not only from later requests. |

{% hint style="info" %}
Use `--useInitialColor` when you need coverage from bootstrap code, startup hooks, or worker initialization.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.sealights.io/knowledgebase/setup-and-configuration/sealights-agents-and-plugins/node.js-agent/capturing-runtime-coverage/backend-instrumentation-dynamic.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
