Skip to main content

Command Palette

Search for a command to run...

I Installed Node.js and Accidentally Became a Backend Developer

Updated
15 min read
I Installed Node.js and Accidentally Became a Backend Developer
S
Trying to transition my career to explore new things, new tech

I had been writing JavaScript in the browser for a while. Console logs, DOM manipulation, making buttons change color. It felt productive until someone asked me to build something that saved data. "Just use Node," they said. I did not know what Node was. I thought JavaScript only ran inside Chrome.

Turns out JavaScript escaped the browser in 2009. A developer named Ryan Dahl took Chrome's V8 engine, the thing that actually reads and runs your JavaScript code, and wrapped it in a program that could run on your operating system directly. No browser needed. That program is Node.js. You write JavaScript in a file, you run that file with the node command, and your operating system executes it the same way it would run a Python script or a C program. The language is the same. The environment is completely different.

The browser gives you document, window, alert(). Node gives you the filesystem, network access, the ability to start an HTTP server. Same syntax, different world underneath.

I want to walk through everything it takes to go from "I have never touched Node" to "I just wrote a working web server." No frameworks, no npm packages, just Node itself and what it ships with.


Installing Node.js

Go to nodejs.org. You will see two versions. One says LTS (Long Term Support), the other says Current. Pick LTS. Current has newer features but LTS is what companies actually use in production, and it gets security patches for longer. You are not going to need bleeding-edge features right now.

The installer works on Windows, macOS, and Linux. Download it, run it, click through the defaults. On Linux you can also install through your package manager, but the version in apt or yum is often outdated. The official installer or a version manager is a better bet.

If you are on macOS or Linux and want to manage multiple Node versions later, look into nvm. It lets you install and switch between Node versions with a single command. On Windows, nvm-windows does the same thing. You do not need this today, but knowing it exists saves future headaches.

# Install via nvm (macOS/Linux) if you want this route
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
nvm install --lts
nvm use --lts

Or just use the installer from the website. Both work.


Checking that it actually installed

Open a terminal. On Windows that is PowerShell or Command Prompt. On macOS it is Terminal. On Linux, whatever terminal emulator you have.

node -v
v22.15.0

If you see a version number, Node is installed. If you see "command not found," the installer either failed or your terminal does not know where Node lives (a PATH issue, which I covered in the Linux post if you want the full story on what PATH actually is).

Check npm too:

npm -v
10.9.2

npm is Node's package manager. It comes bundled with Node automatically. You will use it later to install libraries, but for this post we are not touching it. Everything here uses only what Node ships with out of the box.

Your version numbers will probably be different from mine. That is fine. As long as both commands return a number and not an error, you are good.


The REPL: JavaScript without a file

Before writing any files, there is something worth trying. Type node in your terminal with no arguments:

node
Welcome to Node.js v22.15.0.
Type ".help" for more information.
>

That > prompt is the REPL. REPL stands for Read, Eval, Print, Loop. It reads what you type, evaluates it as JavaScript, prints the result, and loops back waiting for more input. It is an interactive JavaScript sandbox right in your terminal.

> 2 + 2
4
> "hello".toUpperCase()
'HELLO'
> const x = 10
undefined
> x * 3
30
> [1, 2, 3].map(n => n * 2)
[ 2, 4, 6 ]

Every line executes immediately. No file, no save, no refresh. The undefined you see after const x = 10 is just the REPL telling you that the assignment expression itself did not return a value. It is not an error.

The REPL is useful for quick experiments. You want to check how a string method works, or test whether your regex matches something, or try out an array operation before putting it in your code. Faster than opening a browser console, and it runs the exact same V8 engine.

A few REPL commands worth knowing:

.help     shows available commands
.exit     quits the REPL (Ctrl+C twice also works)
.editor   opens multi-line mode so you can paste or type a function

Try .editor mode:

> .editor
// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)
function greet(name) {
  return `hey ${name}, welcome to Node`;
}
// press Ctrl+D
> greet("Saumya")
'hey Saumya, welcome to Node'

The REPL is a scratchpad. Use it when you want to test one thing quickly. For anything longer than a few lines, write a file.

To exit: type .exit or hit Ctrl+C twice.


Your first JavaScript file

Create a folder somewhere. Call it whatever you want. I will use node-basics.

mkdir node-basics
cd node-basics

Create a file called hello.js. You can use any text editor. VS Code, Sublime, Vim, Nano, Notepad, it does not matter. The file just needs to end in .js.

// hello.js
const name = "Saumya";
const hour = new Date().getHours();

let greeting;

if (hour < 12) {
  greeting = "Good morning";
} else if (hour < 17) {
  greeting = "Good afternoon";
} else {
  greeting = "Good evening";
}

console.log(`\({greeting}, \){name}.`);
console.log(`It is currently ${hour}:00 hours.`);
console.log("This is running outside the browser.");

Nothing fancy. Variables, a conditional, template literals, console.log. The same JavaScript you already know. The only difference is where it runs.


Running it

node hello.js
Good afternoon, Saumya.
It is currently 14:00 hours.
This is running outside the browser.

That is it. Node read your file, executed the JavaScript, printed the output to your terminal. No browser opened. No HTML page. Your operating system ran JavaScript the same way it runs any other program.

What just happened under the hood is worth understanding, even briefly.

When you type node hello.js, the Node.js runtime starts up. It hands your code to V8, which compiles the JavaScript into machine code that your CPU can actually execute. V8 does not interpret JavaScript line by line like some languages do. It compiles it. That is why Node is fast enough to run web servers handling thousands of requests.

The output goes to stdout, which is what your terminal is reading. console.log in Node writes to stdout. In the browser, console.log writes to the browser's developer console. Same function name, different destination.

Try adding an error to the file on purpose:

// error-test.js
console.log("this will print");
undefinedVariable.doSomething();
console.log("this will NOT print");
node error-test.js
this will print
/home/you/node-basics/error-test.js:2
undefinedVariable.doSomething();
^

ReferenceError: undefinedVariable is not defined
    at Object.<anonymous> (/home/you/node-basics/error-test.js:2:1)

Node gives you the file name, the line number, a caret pointing at the exact spot, and the error type. Read these. They tell you what went wrong and where. The first line printed because Node executes sequentially until it hits an unhandled error, then it stops. The third line never ran.


A few things Node has that the browser does not

Before we build the server, I want to show a couple of things that make Node different from browser JavaScript. You cannot access these in Chrome's console.

// node-extras.js

// __dirname: the absolute path to the folder this file is in
console.log("This file lives in:", __dirname);

// __filename: the absolute path to this file itself
console.log("This file is:", __filename);

// process: information about the running Node process
console.log("Node version:", process.version);
console.log("Operating system:", process.platform);
console.log("Current directory:", process.cwd());

// process.argv: command line arguments passed to the script
console.log("Arguments:", process.argv);
node node-extras.js hello world
This file lives in: /home/you/node-basics
This file is: /home/you/node-basics/node-extras.js
Node version: v22.15.0
Operating system: linux
Current directory: /home/you/node-basics
Arguments: [ '/usr/bin/node', '/home/you/node-basics/node-extras.js', 'hello', 'world' ]

process.argv is an array. The first element is always the path to the Node binary. The second is the path to your script. Everything after that is whatever you typed after the filename. This is how CLI tools read your input.

A quick practical use:

// greet-cli.js
const name = process.argv[2] || "stranger";
console.log(`Hello, ${name}.`);
node greet-cli.js Ravi
Hello, Ravi.
node greet-cli.js
Hello, stranger.

You just built a tiny CLI tool. Nothing installed, no libraries, just process.argv and a fallback.


Writing a Hello World server

This is the part where Node stops feeling like "JavaScript that runs in the terminal" and starts feeling like something genuinely different. We are going to write an HTTP server from scratch. No Express, no framework, just the http module that ships with Node.

// server.js
const http = require("http");

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello from Node.js\n");
});

server.listen(3000, () => {
  console.log("Server running at http://localhost:3000");
});
node server.js
Server running at http://localhost:3000

Open your browser and go to http://localhost:3000. You will see "Hello from Node.js" on the page. Your terminal is still running. The server is sitting there, waiting for requests.

Let me break down what each part does.

require("http") loads Node's built-in HTTP module. This module knows how to speak HTTP, the protocol your browser uses to talk to websites. You did not install this. It comes with Node.

http.createServer() creates a server object. The function you pass it runs every single time someone makes a request to your server. That function gets two arguments: req (the incoming request) and res (the response you send back).

res.writeHead(200, { "Content-Type": "text/plain" }) sets the HTTP status code to 200 (which means "OK, everything is fine") and tells the browser the response body is plain text.

res.end("Hello from Node.js\n") sends the actual response body and closes the connection. If you forget res.end(), the browser will hang forever waiting for the response to finish.

server.listen(3000) tells the server to start listening on port 3000. A port is just a number that identifies which program should receive incoming network traffic. Port 80 is the default for HTTP, port 443 for HTTPS. We use 3000 because it is unlikely to conflict with anything else running on your machine.

The callback in listen() runs once the server is ready. That is where the console.log fires.

Here is what happens when your browser visits localhost:3000:

To stop the server, go back to your terminal and press Ctrl+C. The process exits and the port is freed.


Making the server do more than one thing

A server that returns the same text for every URL is not very useful. Let's handle different routes:

// server-routes.js
const http = require("http");

const server = http.createServer((req, res) => {
  // req.url contains the path the browser requested
  // req.method contains GET, POST, etc.

  if (req.url === "/" && req.method === "GET") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end("<h1>Home page</h1><p>Try visiting /about or /time</p>");

  } else if (req.url === "/about" && req.method === "GET") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end("<h1>About</h1><p>This is a Node.js server with no framework.</p>");

  } else if (req.url === "/time" && req.method === "GET") {
    const now = new Date().toLocaleTimeString();
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ time: now }));

  } else {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("404 - not found\n");
  }
});

server.listen(3000, () => {
  console.log("Server running at http://localhost:3000");
});
node server-routes.js

Now try these in your browser:

  • http://localhost:3000/ returns an HTML page

  • http://localhost:3000/about returns a different HTML page

  • http://localhost:3000/time returns JSON with the current time

  • http://localhost:3000/anything-else returns a 404

Notice the /time route sets Content-Type to application/json and uses JSON.stringify to turn a JavaScript object into a JSON string. This is how APIs work. The browser (or any HTTP client) gets back structured data it can parse and use.

The req.url and req.method check is the most primitive form of routing. In a real app you would use Express or Fastify to handle this, because writing if/else blocks for every URL gets old fast. But seeing it done manually first is how you understand what those frameworks actually do underneath. They are reading req.url and req.method and matching patterns, just with nicer syntax.


A habit worth building right now

Every time you modify your server code, you have to stop the server (Ctrl+C) and restart it (node server.js). This gets annoying within about ten minutes.

Node 22 has a built-in watch mode:

node --watch server.js

Now when you save changes to server.js, Node automatically restarts the server. No extra tools needed. If you are on an older Node version, nodemon is the classic npm package that does the same thing, but if your Node supports --watch, use that first.


What is actually different from browser JavaScript

By this point you have written and run JavaScript outside the browser. The syntax is identical, but the environment is not. Here is what changed:

The browser gives you window, document, localStorage, fetch (in modern browsers), alert(), and the entire DOM. None of these exist in Node. If you try document.getElementById in a Node script, you get a ReferenceError. There is no document. There is no HTML page. There is no DOM.

Node gives you process, require() (or import with ES modules), __dirname, __filename, access to the filesystem through the fs module, the ability to create servers with http, read and write files, run child processes, interact with your operating system. None of these exist in the browser. If you try require("fs") in Chrome's console, it will not work.

The language is one thing. The environment is another. People mix these up constantly when starting out. If a tutorial shows document.querySelector it is browser JavaScript. If it shows require("http") it is Node JavaScript. The JavaScript itself is identical. What changes is the stuff around it.


Common mistakes when you are starting out

I am putting these here because I made all of them.

Running node with no filename and wondering why your file did not execute. Typing node alone opens the REPL. Typing node server.js runs the file. They do different things.

Editing the file and expecting the running server to pick up changes. Node reads your file once when it starts. If you change the file, the running process does not know. Restart it, or use --watch.

Forgetting res.end() in your server. The browser will just keep spinning. The connection stays open, waiting for more data that never comes. Every response needs to be closed with res.end().

Using require for a file and forgetting the path prefix. require("http") loads a built-in module. require("myfile") tries to find a package called "myfile" in node_modules. require("./myfile") loads a file relative to the current file. The ./ matters.

Port already in use. If you see EADDRINUSE when starting your server, another process is already using that port. Either that process is still running from last time (check with lsof -i :3000 on macOS/Linux or netstat -ano | findstr :3000 on Windows), or pick a different port number.


Where to go from here

At this point you can install Node, run JavaScript files, use the REPL for quick tests, and write a basic HTTP server. That is enough to start building things.

The next post in this series covers npm and packages: what package.json is, how to install and use libraries, and the difference between dependencies and devDependencies. After that we will get into Express, which takes the manual routing we did here and makes it not terrible.

For now, the best thing you can do is experiment. Write scripts that read command-line arguments and do something with them. Add more routes to your server. Try returning different content types. Break things and read the error messages.


References