Node.js Get Started
Node.js is a runtime that lets you execute JavaScript outside a web browser, on your own machine or on a server. It is built on Google’s V8 engine (the same engine that powers Chrome) plus a C++ library called libuv that adds an event loop, a thread pool, and access to the file system, network, and other operating-system features. That combination is what lets a single-threaded JavaScript program handle thousands of concurrent connections without spawning a thread per request. In this lesson you will install Node.js, run your first scripts, understand what actually happens when you type node app.js, and build a tiny HTTP server.
Overview: What Node.js Actually Is
A normal JavaScript engine like V8 only understands the language itself — numbers, objects, functions, promises. It has no idea what a file or a network socket is, because browsers don’t let JavaScript touch those directly for security reasons. Node.js wraps V8 with a set of C/C++ bindings (via libuv) that expose the operating system: reading files, opening TCP sockets, spawning processes, timers, and more. On top of those bindings, Node ships a standard library written in JavaScript — modules like fs, http, path, and events — that you require or import into your own code.
The other core piece is the event loop. Node.js runs your JavaScript on a single main thread. When you call an asynchronous operation — reading a file, querying a database, waiting on a timer — Node hands that work off to libuv (which may use its own background thread pool for things like file I/O) and immediately continues running the rest of your script. When the operation finishes, libuv queues a callback, and the event loop picks it up and runs it once the main thread is free. This is why Node.js can serve many clients at once on one thread: it never sits idle waiting for slow I/O, it just processes whatever is ready next. You’ll go deeper into the event loop’s exact phases in a later lesson — for now, the important takeaway is that Node.js is asynchronous and single-threaded by default, and the code you write should avoid blocking that one thread.
Node.js also comes with npm (Node Package Manager), installed automatically alongside Node. npm lets you install reusable packages from the public registry and manages your project’s dependencies through a package.json file.
Installing Node.js
Download the current LTS (Long-Term Support) release from the official Node.js website, or install it through a version manager such as nvm (Node Version Manager), which makes it easy to switch between Node versions per project. LTS releases (even-numbered major versions like 20 and 22) are the ones you want for real projects — they receive security and bug fixes for years, unlike the short-lived odd-numbered “current” releases. This lesson targets Node.js 20 or 22 LTS.
Once installed, confirm it from a terminal:
node -v
# v22.11.0
npm -v
# 10.9.0
If both commands print a version number, you’re ready to go. If node is not found, your installer did not add Node to your system PATH — reinstall using the official installer for your OS, or use nvm, which sets this up for you automatically.
Syntax: Running Node.js
There are two main ways to run JavaScript with Node.js:
- Run a file:
node path/to/file.jsexecutes the file top to bottom and exits when there’s no more work queued (no open server, no pending timer, etc). - The REPL: running
nodewith no arguments drops you into a Read-Eval-Print Loop — an interactive prompt where you can type expressions and see results immediately. It’s useful for quickly testing a snippet, but not for real programs.
$ node
> 1 + 2
3
> console.log("hi")
hi
undefined
> .exit
The table below summarizes the commands you’ll use constantly while getting started:
| Command | What it does |
|---|---|
node file.js |
Runs a script file with Node.js |
node |
Opens the interactive REPL |
node -v / node --version |
Prints the installed Node.js version |
npm -v |
Prints the installed npm version |
npm init -y |
Creates a default package.json in the current folder |
npm install <package> |
Installs a package and adds it to package.json |
npx <command> |
Runs a package’s CLI without installing it globally |
Examples
Example 1: Your First Script
Create a file named hello.js and run it with node hello.js.
console.log("Hello from Node.js!");
console.log("Node version:", process.version);
console.log("Running on:", process.platform);
Output:
Hello from Node.js!
Node version: v22.11.0
Running on: linux
This introduces the global process object, which Node.js injects into every script (it’s not a module you import). process.version reports the exact Node build you’re running, and process.platform reports the operating system — useful when your code needs to behave differently across environments.
Example 2: Reading Command-Line Arguments
Node.js exposes everything typed after node on the command line through process.argv, an array. The first two entries are always the path to the Node executable and the path to your script, so any arguments you pass start at index 2.
// greet.js
const name = process.argv[2] ?? "stranger";
console.log(`Hello, ${name}!`);
console.log("All argv entries:", process.argv);
Run it with an argument:
node greet.js Maya
Output:
Hello, Maya!
All argv entries: [
'/usr/bin/node',
'/home/user/greet.js',
'Maya'
]
The nullish coalescing operator (??) supplies a fallback only when process.argv[2] is null or undefined (i.e. no argument was passed), so running node greet.js with no name still prints a sensible greeting instead of “Hello, undefined!”.
Example 3: A Minimal HTTP Server
This is where Node.js starts to feel like a server platform rather than a scripting tool. The built-in node:http module lets you accept HTTP requests without installing anything.
const http = require("node:http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello from your first Node.js server!\n");
});
const PORT = process.env.PORT ?? 3000;
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
Output:
Server running at http://localhost:3000/
Save this as server.js and run node server.js. Unlike the previous two examples, this script does not exit — server.listen() keeps the event loop alive waiting for incoming connections, so Node.js stays running until you stop it with Ctrl+C or call server.close(). Visit http://localhost:3000/ in a browser (or run curl http://localhost:3000/) and you’ll see the response text. Notice the module is imported with the node: prefix (require("node:http")) — this is the current recommended style for built-in modules because it makes it unambiguous, at a glance, that http is a core module and not a package named http from npm.
How It Works Step by Step
When you run node server.js, here is the order of events:
- Node.js starts, initializes the V8 engine and the libuv event loop, and loads your file as a CommonJS module.
- Your script runs synchronously from top to bottom: the
require("node:http")call loads the module,http.createServer()creates a server object (but does not yet listen for connections), andserver.listen(PORT, callback)asks the OS to start listening on that port. - Once the synchronous script finishes, Node.js checks whether there’s anything left to do. Because the server is listening, there’s an active “handle” keeping the process alive, so Node.js does not exit — it enters the event loop and waits.
- When a client connects, the OS notifies libuv, libuv notifies the event loop, and the event loop invokes your request handler callback — the function you passed to
createServer()— withreqandresobjects for that specific request. - Inside the callback,
res.writeHead()queues the status line and headers, andres.end()writes the body and tells Node the response is complete, flushing it back to the client. - The process repeats for every new connection, all on the same thread, until you terminate it.
Compare this to the two earlier examples: hello.js and greet.js have no pending timers, servers, or callbacks left after their last line runs, so the event loop finds nothing to wait for and Node.js exits immediately on its own.
Common Mistakes
Mistake 1: Mixing require and import, or getting the module type wrong
Node.js supports two module systems: CommonJS (require/module.exports) and ES Modules (import/export). Which one a file uses is determined by its extension and by the "type" field in the nearest package.json. If you set "type": "module" but write CommonJS-style code, Node.js throws at startup:
{
"name": "my-app",
"version": "1.0.0",
"type": "module"
}
// app.js — fails because package.json says "type": "module"
const http = require("node:http");
console.log("starting...");
Running this prints ReferenceError: require is not defined in ES module scope, you can use import instead. Until you reach the lesson on ES Modules, keep new projects on the CommonJS default by leaving the "type" field out of package.json entirely (or setting it to "commonjs"), and stick to require/module.exports consistently:
{
"name": "my-app",
"version": "1.0.0"
}
// app.js — works: plain CommonJS, no "type" field set
const http = require("node:http");
console.log("starting...");
Mistake 2: Running code that requires an uninstalled package
Beginners often copy an example that does require("express"), run it, and hit Error: Cannot find module 'express'. Listing a dependency in package.json is not the same as having it installed — npm only writes files into node_modules when you actually run the install command:
npm install express
This downloads the package into a local node_modules folder and records it under "dependencies" in package.json. If you clone a project someone else wrote, always run npm install (no package name) first — it reads package.json and installs everything listed there before you try to run the code.
Best Practices
- Install Node.js via
nvm(or your OS’s official installer) and always use an even-numbered LTS release for real projects, not the latest “current” release. - Run
npm init -yat the start of every new project to create apackage.jsonbefore installing any packages. - Commit
package.jsonandpackage-lock.jsonto version control, but addnode_modulesto.gitignore— it’s regenerated bynpm install. - Prefer the
node:prefix for built-in modules (require("node:fs")) so it’s immediately clear a module is part of Node.js itself, not a third-party package. - Use the REPL for quick one-off checks, but write real programs in files so they’re saved, testable, and repeatable.
- Check
node -vwhenever something behaves unexpectedly — a surprising number of “bugs” are really just an outdated or unexpected Node version.
Practice Exercises
- Install Node.js if you haven’t already, then run
node -vandnpm -vto confirm both are on yourPATH. - Write a script
info.jsthat prints your Node.js version, your OS platform, and the current working directory (hint: look upprocess.cwd()). Run it withnode info.js. - Extend the HTTP server from Example 3 so that visiting
/timeresponds with the current date and time as plain text, while every other path still returns the original greeting (hint: checkreq.urlinside the request handler).
Summary
- Node.js runs JavaScript outside the browser by pairing the V8 engine with libuv, which adds an event loop, a thread pool, and OS-level access to files, networking, and processes.
- Node.js is single-threaded and asynchronous by default: slow I/O is handed off to libuv, and callbacks run on the main thread once results are ready.
- Install an LTS version, then verify it with
node -vandnpm -v. - Run scripts with
node file.js, or explore interactively with the REPL by runningnodealone. processis a global object giving youprocess.argv,process.env,process.version, and more, without anyrequirecall.- A script exits automatically once nothing is left pending; an active server or timer keeps the event loop — and the process — alive.
- Use
npm init -yto scaffoldpackage.json, and always runnpm installbefore running code that depends on packages you haven’t installed yet.
