What Node.js actually is
JavaScript was born inside browsers. For years it could only run there, wired to web pages.
Node.js (2009) took the JavaScript engine out of Chrome (called V8) and wrapped it in a standalone program. Now JavaScript runs anywhere: your laptop, a server in a data center, a Raspberry Pi.
What changes outside the browser?
| In the browser | In Node.js |
|---|---|
document, window, DOM | not available, there is no page |
| cannot touch your files | full file system access (fs) |
| cannot open a port | can be a server (http) |
| runs when a page loads | runs when you type node app.js |
Two of those rows lean on a new word. A port is a number (0 to 65535) that your operating system uses to deliver incoming network traffic to the right program: when a program claims port 3000, every connection addressed to that number on your machine is handed to that program. A server must claim a port to receive requests at all, which is why “cannot open a port” is the biggest thing browser JavaScript cannot do and Node can.
The language is the same one you learned in Advanced JavaScript: functions, objects, callbacks, promises. Only the surroundings change.
How the OS routes traffic to the right program
When a laptop runs a Node API and a database at once, the operating system knows which program receives which data because each program claimed a different port, and every incoming connection is addressed to a port number.
Ports exist for exactly this routing job. The API might claim port 3000 and the database 5432, and each incoming connection carries the port number it is addressed to, so the OS has an unambiguous delivery address.
That is also why two servers cannot claim the same port on one machine, since the OS would have no way to decide who gets the traffic. The error you see in that case is EADDRINUSE, and it is one of the first Node errors most people meet.
| Port | Usually |
|---|---|
| 80 | plain HTTP |
| 443 | HTTPS |
| 3000 | a local dev server, by convention |
| 5432 | PostgreSQL |
Nothing enforces those conventions, and any program may claim any free port. The low numbers below 1024 are the exception, since most systems require administrator rights to claim them, which is part of why local development happens on 3000 rather than 80.
You will see a server claim a port with listen(3000) in unit 3.
Callbacks still rule here
In Advanced JavaScript you learned callbacks: functions passed to other functions to run later. Node is built on them, because a server spends most of its life waiting: for a database, a file, another API.
Node never sits idle during a wait. It starts the slow thing, hands over a callback, and moves on to the next request. When the slow thing finishes, the callback runs. This is called being non-blocking.
The example below fakes a slow operation with setTimeout. Watch the order of the printed lines: the program does not stop at step 2 to wait.
Reading the order of non-blocking output
Line 3 is scheduled first and prints last, because Node keeps going instead of waiting.
console.log("1. ask the database for a user"); setTimeout(() => { console.log("3. the user arrived: Ada"); }, 20); console.log("2. keep serving other requests meanwhile");
Output
1. ask the database for a user 2. keep serving other requests meanwhile 3. the user arrived: Ada
setTimeout schedules the callback to run later, and then the program continues immediately to the next line. The 20 milliseconds is a minimum delay rather than an appointment, since the callback runs once the current work is finished and at least that much time has passed.
The numbering in the strings is the point of the example. The source order is 1, 3, 2, and the output order is 1, 2, 3, so reading top to bottom is not enough to predict what happens.
Changing 20 to 0 does not change the output. A zero-delay callback still waits for the current synchronous code to finish, which is the detail lesson 2-2 explains with the event loop.
For a backend this ordering is the whole business model. The setTimeout stands in for a database query, and the line that prints second stands in for another user's request being served during the wait.
Why non-blocking matters for a server
Because while one request waits on a slow database, the server can handle other requests.
A server may have thousands of users at once, and most of the time spent on each request is waiting on I/O, meaning databases, files, and other services. Non-blocking means one wait never freezes everyone else.
The blocking alternative makes the cost concrete. If a 50 millisecond query stopped the whole program, one process could serve at most 20 requests per second no matter how fast the machine was, because it would spend nearly all its time doing nothing.
Node does this with a single thread plus callbacks, which is the event loop model you will map out in lesson 2-2. Single-threaded and non-blocking sound contradictory and are not, since the thread is never the thing doing the waiting.
The flip side is worth knowing now. Non-blocking helps with waiting and not with computing, so a genuinely slow calculation on that one thread does freeze everyone, which is why heavy CPU work belongs somewhere other than a request handler.
A callback-based config loader
loadConfig(callback) waits 10 milliseconds and then hands a config object to the callback, and the line after the call proves the wait does not block.
function loadConfig(callback) { setTimeout(() => { callback({ port: 3000 }); }, 10); } loadConfig((config) => { console.log("Server will use port " + config.port); }); console.log("loading config...");
Output
loading config...
Server will use port 3000The message about loading prints first because the callback only runs after the delay. loadConfig returns immediately, having scheduled work rather than performed it.
This is the shape of every callback-based Node API, including the file and network functions from later units. The function takes something to call when the result is ready, and the result arrives as an argument to that callback rather than as a return value.
The reason it cannot be a return value is timing. loadConfig finishes long before the config exists, so anything it returned would be a promise of a result at best and undefined at worst, which is why the callback style came first historically.
Note that config is a parameter name chosen by the caller, not by loadConfig. The loader decides what to pass and the caller decides what to call it, which is the same separation as any other function argument.
Callbacks like this are workable for one step and get ugly when three of them nest, and lesson 2-4 replaces the pattern with promises and await.