NodeJSRunning Your First Script

Running Your First Script

The REPL is for quick experiments; real programs live in files. This page shows how to write a .js file, run it, pass arguments, choose the module system, and use the modern watch mode so you do not restart manually after every edit.

Create and run a file

Create app.js with any text editor:

app.js

JS
const now = new Date().toLocaleTimeString()
console.log('Hello! It is', now)

Run it from the same folder:

Bash
node app.js
Hello! It is 2:14:08 PM
The .js extension is optional (sometimes)
`node app` works too if `app.js` exists, but being explicit (`node app.js`) is clearer and avoids ambiguity when both `app.js` and a folder `app/` exist.
What Node does when you run a file

Understanding the startup sequence demystifies a lot of "why did it do that?" moments:

From command to output

Text
node app.js
   1. Initialize V8 and the libuv event loop
   2. Decide module system: .mjs → ESM, .cjs → CommonJS,
      .js → look at "type" in the nearest package.json
   3. Resolve, load, and execute app.js top to bottom
   4. Keep the process alive while the event loop has work
   5. Exit with code 0 when the loop drains (or the code you set)
ESM vs CommonJS — which one runs?

File / setting

Module system

Syntax

app.mjs

ES Modules

import / export

app.cjs

CommonJS

require / module.exports

app.js + "type":"module"

ES Modules

import / export

app.js + no/"commonjs"

CommonJS

require / module.exports

Mismatch errors
Using `import` in a file Node treats as CommonJS throws `SyntaxError: Cannot use import statement outside a module`. The fix is to set `"type": "module"` in `package.json` or rename to `.mjs`. Full detail in [Modules: CommonJS vs ESM](/nodejs/commonjs).
Reading command-line arguments

Arguments after the filename live in process.argv (an array of strings). The first two entries are the Node binary and the script path; your arguments start at index 2:

greet.js

JS
const name = process.argv[2] || 'stranger'
console.log(`Hello, ${name}!`)

Bash
node greet.js Alice
Hello, Alice!

We cover parsing, flags, and util.parseArgs in depth in Command-Line Arguments (process.argv).

Watch mode — auto-restart on save

Node 18.11+ has a built-in --watch flag that reruns your script whenever a dependent file changes — no nodemon needed. --watch-path limits what it watches:

Bash
node --watch app.js                 # rerun on any imported file change
node --watch-path=./src app.js      # only watch ./src
Watch mode is for development only
`--watch` restarts the whole process on change — great locally, never in production. For older Node, install `nodemon` as a dev dependency. Pair it with `--env-file` to reload config too.
npm scripts — the conventional entry point

Rather than memorizing flags, teams put run commands in package.json so everyone uses the same invocation:

package.json

JSON
{
  "scripts": {
    "start": "node app.js",
    "dev": "node --watch --env-file=.env app.js"
  }
}

Bash
npm run dev   # runs the "dev" script above
Exit codes

When a script finishes successfully it exits with code 0; an uncaught error exits non-zero. CI pipelines and shell scripts branch on this, so set it deliberately:

JS
if (somethingWentWrong) {
  console.error('Failed!')
  process.exitCode = 1   // preferred: let pending I/O drain, then exit 1
  return
}
Prefer exitCode over exit() mid-async
Calling `process.exit(1)` terminates **immediately** and can truncate a half-written log or an in-flight response. Setting `process.exitCode` and returning lets the event loop drain naturally, then exits with your code. Reserve `process.exit()` for genuine "stop right now" cases.
  • Exit code 0 = success.

  • Non-zero = failure (CI tools treat this as a failed step).

  • You can chain in a shell: node build.js && node deploy.js runs deploy only on success.

Next
Set up a comfortable workspace in [Editor & Dev Environment Setup](/nodejs/editor-setup), then write the canonical [Hello World](/nodejs/hello-world) server.