
# Running Code

> Learn how to run files, evaluate code, preload modules, and handle program output.

This page shows the ways to hand `nstd` a program: a file, inline code, or a
module loaded ahead of time. It also covers what `require()` and `import()`
can reach, how the filesystem shows up, and what a program's exit code and
uncaught errors look like.

## Running a script

Pass a file. It runs as the program's entry point, and anything after it
becomes the program's own arguments:

```sh
nstd script.js one two
```

`nstd run script.js` does the same thing, spelled out. Node decides whether an
entry is CommonJS or an ES module by its extension and its nearest
`package.json`, and `nstd` makes the same call, so a `.mjs` file runs as a
module and a `.js` or `.cjs` file runs as CommonJS (unless `package.json` says
`"type": "module"`).

Inside the program, `process.argv` looks like Node's: the path to the `nstd`
binary, the resolved path to the script, then `one` and `two`.

## Evaluating code directly

`-e` (or `--eval`) runs a string instead of a file:

```sh
nstd -e 'console.log(require("os").hostname())'
```

There is no entry file in this form, so no script path is inserted into
`process.argv`: `process.argv[1]` is whatever argument came right after the
eval string, matching `node -e`.

## Loading a module first

`-r`, or `--require`, loads a CommonJS module before your program runs. Give
it more than once to load several, in order:

```sh
nstd -r ./setup.cjs script.js
nstd -r ./a.cjs -r ./b.cjs script.js
```

A preload shares the realm with the rest of the program - if it patches a
global or changes a module's state, your entry point sees the change. If a
preload throws and nothing catches it, the program stops before the entry
point ever runs.

`NODE_OPTIONS` carries the same option, which matters for a child process:

```sh
NODE_OPTIONS='--require ./setup.cjs' nstd script.js
```

A wrapper script only arranges the one process it starts, but a child spawned
with `child_process.fork()` or as `process.execPath` starts fresh and carries
nothing across except its environment - so `NODE_OPTIONS` is the only way a
preload reaches it too. See [CLI Options](/guide/cli) for the rest of what
`NODE_OPTIONS` can carry.

## Seeing what this binary can serve

`--print-realm` starts up the JavaScript environment - the "realm" - and
reports on it instead of running a program:

```sh
nstd --print-realm
```

```
nstd 0.0.1 (quickjs-ng ..., zig ...)
node v26.7.0 on linux/x64

67 builtin(s) registered, 0 unavailable
  + assert
  + buffer
  + fs
  ...
```

Every `node:` module this build could evaluate is listed with a `+`; anything
that failed is listed with a `-` and the reason. This is the fastest way to
check whether a given build of `nstd` has what your program needs before you
run it.

## What `require()` and `import()` do here

`require()` reaches three kinds of things: the `node:` builtins compiled into
the binary, your own files on disk, and packages in `node_modules`, resolved
the same way Node resolves them - `"exports"`, `"imports"`, and the plain
walk up the directory tree when a package has neither.

`import()` and `.mjs` files work too, including relative specifiers, packages
with no `"exports"` field, `node:` builtins, CommonJS and JSON dependencies,
import attributes, cycles and top-level await. `require()` of an ES module
works as well, and a CommonJS file can `import()` another module dynamically
and have the specifier resolve relative to itself, the way Node resolves it.
Named imports out of a `.cjs` file (`import { readFile } from './some.cjs'`)
work too, using the same CommonJS parser Node's own binding uses.

See [Node.js Compatibility](/guide/compatibility) for what module loading
does not cover yet.

## The filesystem

`fs`, `require()`, and everything that resolves a path talk to the real
filesystem on the machine `nstd` runs on - there is no sandbox and no virtual
root. A relative path resolves against the process's real working directory,
and `fs.watch()`/`fs.watchFile()` both work. Treat a script run with `nstd`
exactly as you would treat one run with `node`: it can read, write and delete
anything the user running it can.

## Exit codes and uncaught errors

A program that runs to completion without calling `process.exit()` leaves
with `process.exitCode`, which defaults to `0` and which your code can set
directly, exactly as in Node.

An exception nothing catches is fatal: the program prints it and exits `1`,
unless a `process.on('uncaughtException', ...)` listener (or
`process.setUncaughtExceptionCaptureCallback()`) claims it, in which case the
program keeps running. An unhandled promise rejection follows the same path -
`process.on('unhandledRejection', ...)` sees it first, and if nothing claims
it there either, it is fatal the same way an uncaught throw is.

A few situations exit with a different code, the same way they do under
`node`:

| exit code | when                                                               |
| --------- | ------------------------------------------------------------------ |
| `0`       | the program finished, or `--help`/`--version`/`--print-realm` ran  |
| `1`       | an uncaught exception or unhandled rejection, or `process.exit(1)` |
| `9`       | an option on the command line or in `NODE_OPTIONS` was refused     |
| `70`      | the realm itself failed to start (a bug, not a program error)      |

A script file that does not exist is reported the same way: a message on
stderr and exit `1`, before any of your code runs.
