Set Up JavaScript Development: Browser DevTools, Node.js, and VS Code

A working JavaScript environment has three parts: a runtime (Node.js), an editor (VS Code), and a browser inspector (Chrome DevTools). Install them in that order, wire them together with one launch.json file, and you can set breakpoints in server and client code from a single window.

Quick Takeaways

  • Install Node.js through a version manager (nvm, fnm, or Volta), not the system package manager. You get per-project versions and no sudo npm.
  • Use an LTS release for anything you ship. Check the status column at nodejs.org/en/about/previous-releases, because the schedule changes every October.
  • VS Code ships with the JavaScript Debugger built in. The old “Debugger for Chrome” extension is obsolete.
  • Add ESLint and Prettier on day one. A formatter that runs on save removes style arguments for good.
Tool Role Install method Verify with
Node.js Runtime and npm nvm / fnm / Volta node -v
VS Code Editor and debugger Official installer code --version
Chrome DevTools Browser inspection Built into Chrome F12 or Ctrl+Shift+I

Step 1: Install Node.js with a Version Manager

Node.js runs JavaScript outside the browser. Installing it through a version manager lets you switch versions per project, which matters when one repo needs Node 22 and another needs 24.

Node follows a predictable cycle. Each new major version enters Current for six months, then is promoted to LTS (Long-Term Support) with about 30 months of security fixes. Node.js 24 (“Krypton”) and 22 (“Jod”) are LTS lines, and the project’s own schedule slates Node.js 26 for LTS promotion in October 2026. Starting with Node 27, the project moves to one major release per year, and every release becomes LTS. Production apps should run only Active or Maintenance LTS.

Install on macOS or Linux with nvm

# Install nvm using the command from github.com/nvm-sh/nvm (check the current version tag).
# Then restart your shell and install the latest LTS release:
nvm install --lts

# Make it the default for new terminals
nvm alias default 'lts/*'

# Confirm the install
node -v
npm -v

node -v prints the runtime version (for example v24.x.x). npm -v prints the bundled package manager version. If either command fails, the shell has not loaded nvm. Open a new terminal and retry.

Install on Windows

Use nvm-windows, fnm, or Volta. Alternatively, run the official installer from nodejs.org and accept the defaults, which add Node to your PATH.

# With winget (built into current Windows releases)
winget install Schniz.fnm
fnm install --lts
fnm default lts-latest
node -v

Pin the version per project

Create a .nvmrc file so every teammate (and your CI) uses the same runtime.

# Write the current version into .nvmrc
node -v > .nvmrc

# Later, in the project folder:
nvm use

nvm use reads .nvmrc and switches the active version for that shell. fnm and Volta read the same file or a volta key in package.json.

Version manager comparison

Manager Platforms Switch speed Auto-switch per folder Best for
nvm macOS, Linux Slower (shell script) Manual or hook Most tutorials, widest docs
nvm-windows Windows Moderate No Windows-only teams
fnm All Fast (Rust) Yes, with a flag Daily use across platforms
Volta All Fast Yes, via package.json Teams that want pinned toolchains

Step 2: Install and Configure VS Code

Download VS Code from code.visualstudio.com. On first launch, enable Settings Sync so extensions and preferences follow your account.

Run this once so you can open projects from a terminal:

# macOS/Linux/Windows: open the folder in VS Code
code .

On macOS, run Shell Command: Install ‘code’ command in PATH from the Command Palette (Cmd+Shift+P) if code is not found.

Extensions worth installing

Extension Purpose Required?
ESLint Catches bugs and enforces rules Yes
Prettier – Code formatter Formats on save Yes
Error Lens Shows diagnostics inline Optional
Path Intellisense Autocompletes file paths in imports Optional
Live Server Hot-reloads static HTML Optional

Skip extensions that duplicate built-in features. VS Code already provides IntelliSense, Emmet, Git integration, and a debugger for JavaScript.

Workspace settings

Save team-wide settings in .vscode/settings.json so they travel with the repo.

{
  // Format with Prettier whenever you save
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",

  // Run ESLint auto-fixes on save
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },

  // Use the workspace's TypeScript version for JS type checking
  "js/ts.implicitProjectConfig.checkJs": true,

  // Keep node_modules out of search results
  "search.exclude": {
    "**/node_modules": true,
    "**/dist": true
  }
}

Saving a file now triggers Prettier, then ESLint fixes. Setting checkJs gives you type-error squiggles in plain .js files without adding TypeScript to the build.

Step 3: Initialize a Project

Create a project folder and generate a package.json.

mkdir js-dev-setup && cd js-dev-setup
npm init -y
npm install --save-dev eslint @eslint/js prettier

Then edit package.json to enable ES modules and add scripts:

{
  "name": "js-dev-setup",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "node --watch src/server.js",
    "start": "node src/server.js",
    "lint": "eslint .",
    "format": "prettier --write .",
    "test": "node --test"
  },
  "engines": {
    "node": ">=22"
  }
}

Setting "type": "module" makes Node treat .js files as ES modules, so import and export work without a build step. The –watch flag restarts the process on file changes, which replaces nodemon for most projects. node –test runs the built-in test runner.

A minimal server to debug

// src/server.js
import { createServer } from 'node:http';

const PORT = process.env.PORT ?? 3000;

// Pure function: easy to unit test and to step through in a debugger
export function greet(name = 'world') {
  return `Hello, ${name}!`;
}

const server = createServer((req, res) => {
  const url = new URL(req.url, `http://${req.headers.host}`);
  const message = greet(url.searchParams.get('name') ?? undefined);

  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message }));
});

server.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});

Run npm run dev and visit http://localhost:3000/?name=Ada. The server returns {"message":"Hello, Ada!"}. Edit the file and save, and the process restarts on its own.

ESLint flat config

ESLint 9 and later use eslint.config.js. Create it in the project root:

// eslint.config.js
import js from '@eslint/js';

export default [
  js.configs.recommended,
  {
    languageOptions: {
      ecmaVersion: 'latest',
      sourceType: 'module',
      // Declare the Node.js globals your code uses
      globals: {
        process: 'readonly',
        console: 'readonly',
        URL: 'readonly'
      }
    },
    rules: {
      'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      eqeqeq: 'error',
      'prefer-const': 'error'
    }
  }
];

Run npm run lint. Without the globals block, ESLint reports process as undefined.

Step 4: Debug Node.js in VS Code

The built-in JavaScript Debugger (js-debug) handles Node.js, Chrome, and Edge. You configure it in .vscode/launch.json.

{
  "version": "0.2.0",
  "configurations": [
    {
      // Run the current file under the debugger
      "name": "Node: Current File",
      "type": "node",
      "request": "launch",
      "program": "${file}",
      "skipFiles": ["<node_internals>/**"]
    },
    {
      // Launch the dev server with breakpoints enabled
      "name": "Node: Server",
      "type": "node",
      "request": "launch",
      "program": "${workspaceFolder}/src/server.js",
      "envFile": "${workspaceFolder}/.env",
      "console": "integratedTerminal",
      "skipFiles": ["<node_internals>/**"]
    },
    {
      // Attach to a process started with --inspect
      "name": "Node: Attach",
      "type": "node",
      "request": "attach",
      "port": 9229,
      "restart": true
    }
  ]
}

Click the gutter beside a line in src/server.js to set a breakpoint, select Node: Server, and press F5. Execution pauses on the breakpoint. The Variables pane shows local state, and the Debug Console evaluates expressions in the paused scope.

skipFiles keeps the debugger out of Node’s internal modules, so F11 (step into) stays inside your code. The Attach configuration helps when a script runs elsewhere, such as in Docker:

# Start any script with the inspector open
node --inspect=9229 src/server.js

For quick one-offs, run Debug: Toggle Auto Attach from the Command Palette. Any Node process you start in the integrated terminal then connects to the debugger automatically.

Step 5: Debug in the Browser with Chrome DevTools

Open DevTools with F12, Ctrl+Shift+I (Windows/Linux), or Cmd+Option+I (macOS). Four panels cover most daily work.

Panel Use it to Shortcut or tip
Elements Inspect and edit the live DOM and CSS Right-click, then Inspect
Console Run JavaScript, read logs and errors Use $0 for the selected element
Sources Set breakpoints, step through code Ctrl+P opens any file
Network Inspect requests, timing, payloads Filter by Fetch/XHR
Performance Record and profile runtime behavior Record, reload, stop

Console methods beyond console.log()

const users = [
  { id: 1, name: 'Ada', role: 'admin' },
  { id: 2, name: 'Linus', role: 'user' }
];

console.table(users);                 // Renders an array of objects as a table
console.group('Request');             // Collapsible group
console.time('fetch');                // Start a timer
await fetch('/api/users');
console.timeEnd('fetch');             // Prints elapsed milliseconds
console.groupEnd();
console.assert(users.length === 3, 'Expected 3 users'); // Logs only when false

console.table() prints a sortable grid. console.assert() stays silent unless the condition is false, so you can leave it in during development without noise.

Breakpoint types in the Sources panel

Type How to add Use case
Line Click the line number Pause at a known spot
Conditional Right-click, then Add conditional breakpoint Pause only when user.id === 42
Logpoint Right-click, then Add logpoint Log a value without editing code
DOM change Elements panel, right-click node, Break on Find what mutates an element
XHR/Fetch Sources, XHR/fetch Breakpoints Pause on a matching URL

Logpoints replace most temporary console.log() calls. You never commit them by accident, because they live in the browser, not in the file.

Debug client code from VS Code

Add a Chrome configuration to launch.json to set breakpoints in VS Code and run the page in a debugging Chrome instance:

{
  "name": "Chrome: Localhost",
  "type": "chrome",
  "request": "launch",
  "url": "http://localhost:5173",
  "webRoot": "${workspaceFolder}/src"
}

Start your dev server first, then press F5. The webRoot setting maps browser URLs to local files, which is how source maps connect bundled output to your original source. For a full-stack project, use a compound configuration to start the server and the browser together.

Bad Code vs. Good Code: Setup Anti-Patterns

Anti-pattern 1: Debugging with scattered logs

// Bad: logs left in, no structure, impossible to filter
function applyDiscount(cart, code) {
  console.log('cart', cart);
  console.log('code', code);
  const total = cart.items.reduce((s, i) => s + i.price, 0);
  console.log('total', total);
  return total * (code == 'SAVE10' ? 0.9 : 1);
}
// Good: strict equality, named constants, no debug output.
// Inspect values with a breakpoint or a logpoint instead.
const DISCOUNTS = { SAVE10: 0.1 };

export function applyDiscount(cart, code) {
  const subtotal = cart.items.reduce((sum, item) => sum + item.price, 0);
  const rate = DISCOUNTS[code] ?? 0;
  return subtotal * (1 - rate);
}

The refactor removes the loose == comparison, drops the magic string from the logic, and keeps the function pure. ESLint’s eqeqeq rule flags the first version on save.

Anti-pattern 2: Global installs and sudo npm

# Bad: global tool versions drift between machines, and sudo breaks file ownership
sudo npm install -g eslint prettier
# Good: project-local tools pinned in package.json, run through npm scripts
npm install --save-dev eslint prettier
npx eslint .

Local installs make npm ci reproduce the exact toolchain on any machine or CI runner.

Anti-pattern 3: Hardcoded secrets

// Bad: the key is now in Git history
const apiKey = 'sk_live_abc123';
// Good: read from the environment; load .env during development
const apiKey = process.env.API_KEY;
if (!apiKey) throw new Error('API_KEY is not set');
# Node 20.6+ loads a .env file natively
node --env-file=.env src/server.js

Add .env to .gitignore. The thrown error makes a missing variable fail fast at startup instead of deep inside a request.

Troubleshooting Common Setup Problems

Symptom Likely cause Fix
command not found: node Shell has not loaded nvm/fnm Open a new terminal; check your shell profile
EACCES during npm install -g Global dir owned by root Use a version manager; never sudo npm
Breakpoints show as gray circles Source maps or webRoot wrong Fix webRoot; confirm sourceMap: true in the bundler
Cannot use import statement outside a module Missing module type Add "type": "module" or rename to .mjs
ESLint says process is not defined Missing globals in config Add Node globals to languageOptions.globals
Debugger exits immediately Wrong program path Use ${workspaceFolder} and check the file exists

Recommended Project Structure

js-dev-setup/
├── .vscode/
│   ├── launch.json
│   └── settings.json
├── src/
│   └── server.js
├── test/
│   └── server.test.js
├── .gitignore
├── .nvmrc
├── eslint.config.js
├── package.json
└── README.md

Commit .vscode/launch.json and settings.json so new contributors press F5 and get the same experience on day one. Keep personal preferences, such as themes and font size, in your user settings.

FAQ

Which Node.js version should I install for development?

Install the latest LTS release. LTS lines get about 30 months of security fixes and are the only releases the Node.js project recommends for production. Check the status column on nodejs.org before you install, since a Current release moves to LTS each October.

Do I need to install npm separately from Node.js?

No. npm ships with Node.js. Running node -v and npm -v after installation confirms both. To use another package manager such as pnpm or Yarn, enable it with corepack enable, which ships with Node.

Is the Debugger for Chrome extension still needed in VS Code?

No. VS Code bundles the JavaScript Debugger, which supports Node.js, Chrome, and Edge. Use "type": "node" for server code and "type": "chrome" for browser code in launch.json.

Should I use VS Code’s debugger or Chrome DevTools?

Use both. Chrome DevTools gives the best view of the DOM, network, and rendering performance. VS Code is better for server code and for stepping through source files without leaving your editor. A compound launch configuration lets you debug both sides in one session.

Hot this week

Humanoids in Factories and Warehouses: What They Actually Do Today

Which humanoid robots do real work in factories and warehouses today? See deployment data, ROI math and ROS 2 code. Read the 2026 guide.

Debugging Robot Code: Logs, Serial Output, rosbag, and Foxglove

Debug robot code faster with ROS 2 logging, serial capture, rosbag2 recording, and Foxglove. Copy the configs and scripts into your stack today.

Build a Mapping Robot With SLAM From Scratch

Build a SLAM mapping robot from scratch: wiring, odometry, LiDAR, ROS 2 slam_toolbox config and tuning. Follow the full guide and map your first room.

Depth Cameras: Stereo, Time-of-Flight, and Structured Light Compared

Compare stereo, time-of-flight, and structured light depth cameras: accuracy, range, sunlight, ROS2 setup. Pick the right sensor for your robot.

Python vs C++ for Robotics: Which Should You Use?

Python vs C++ for robotics compared: latency, ROS2 performance, control loops, and code examples. Learn which language fits your robot. Read the guide.

Topics

Humanoids in Factories and Warehouses: What They Actually Do Today

Which humanoid robots do real work in factories and warehouses today? See deployment data, ROI math and ROS 2 code. Read the 2026 guide.

Debugging Robot Code: Logs, Serial Output, rosbag, and Foxglove

Debug robot code faster with ROS 2 logging, serial capture, rosbag2 recording, and Foxglove. Copy the configs and scripts into your stack today.

Build a Mapping Robot With SLAM From Scratch

Build a SLAM mapping robot from scratch: wiring, odometry, LiDAR, ROS 2 slam_toolbox config and tuning. Follow the full guide and map your first room.

Depth Cameras: Stereo, Time-of-Flight, and Structured Light Compared

Compare stereo, time-of-flight, and structured light depth cameras: accuracy, range, sunlight, ROS2 setup. Pick the right sensor for your robot.

Python vs C++ for Robotics: Which Should You Use?

Python vs C++ for robotics compared: latency, ROS2 performance, control loops, and code examples. Learn which language fits your robot. Read the guide.

Build a ROS 2 Rover: Raspberry Pi 5, Motors, and LiDAR

Build a ROS 2 rover with Raspberry Pi 5, RPLIDAR C1, and a Pico motor controller. Copy the code, wire it, and map your room. Start building now.

Imitation Learning for Robots: Teach by Demonstration

Learn imitation learning for robots: behavior cloning, DAgger, ACT, and diffusion policies with ROS2 code. Build your first policy today.

LiDAR for Robots: 2D, 3D, and Solid-State Compared

Compare 2D, 3D, and solid-state LiDAR for robots. See specs, ROS2 configs, code, and a selection workflow. Pick the right sensor now.

Related Articles

Popular Categories