Node.js is the runtime that let JavaScript escape the browser and become a first-class server language. Before writing a single line of code it pays to understand what problem it solves, because Node.js is not "yet another language": it is a very specific way of handling concurrency, and that choice explains almost every one of its strengths and every one of its weaknesses. Once you understand its non-blocking I/O model, the rest of the course stops being a collection of recipes and becomes a logical consequence.
In this lesson we will build that conceptual foundation. As an excuse we will use the project that accompanies us throughout the course: Escena Viva, a small company that sells tickets for cultural events at the Teatro Almendra, the Sala Bóveda and the Auditorio Ribera.
Contents
- What Node.js is and what problem it solves
- JavaScript outside the browser
- The conceptual pieces: V8 and libuv
- The non-blocking I/O model: the waiter analogy
- Comparison with the thread-per-request model
- What Node.js is good at (and what it is not)
- The npm ecosystem and "JavaScript everywhere"
- LTS versions and the release cadence
- Why Escena Viva is a good fit for Node.js
- What Node.js Is and What Problem It Solves
Node.js is a server-side JavaScript runtime. More precisely: it is a program you install on your computer or on a server that knows how to read .js files and execute them, giving them access to the operating system: files, network, processes, environment variables.
It is not a language. It is not a framework. It is the interpreter plus the libraries that surround it.
The historical problem it set out to solve was concurrency in web servers. In the late 2000s, traditional servers handled every request with a dedicated operating system thread. A thread consumes memory (typically anywhere from hundreds of KB to several MB of stack) and switching between threads has a cost. With thousands of simultaneous connections — many of them simply waiting for a database to answer or for the client to finish downloading — the server spent most of its resources managing idle threads.
This problem even has a name: the C10K problem (serving ten thousand concurrent connections on a single machine). Node.js answered it in a radical way: a single thread for your code, and every input/output operation performed asynchronously.
The core idea to hold on to: in Node.js, your code never sits idle waiting for a slow operation to finish. It requests the operation, carries on with something else and, when the result is ready, comes back to deal with it.
- JavaScript Outside the Browser
Until 2009, JavaScript was practically a synonym for "the language of web pages". Ryan Dahl took Google Chrome's V8 engine — already very fast — and packaged it together with an asynchronous input/output library. The result was Node.js.
Taking JavaScript out of the browser changes the tools at your disposal:
| Concept | In the browser | In Node.js |
|---|---|---|
| Global object | window (or globalThis) |
global (or globalThis) |
| User interface | document, DOM, mouse events |
Does not exist: there is no DOM |
| Storage | localStorage, IndexedDB |
File system, databases |
| Network | fetch, XMLHttpRequest (with CORS) |
fetch, the http/https modules, TCP sockets, no CORS |
| File access | Restricted and subject to user permission | Complete (the fs module) |
| Process and environment | Not accessible | process.argv, process.env, process.exit() |
| Security model | The browser sandbox | The permissions of the system user |
That last row matters: in the browser, JavaScript lives locked up for safety. In Node.js, your script can delete files, open ports and spawn processes. With that power comes the responsibility of never running code you do not trust.
We will see these differences in practice in the lesson Your First Node.js Program.
- The Conceptual Pieces: V8 and libuv
Node.js rests on two main components. We introduce them here at a conceptual level; the detailed architecture and the event loop with its phases are covered in Module 2.
V8: the JavaScript engine
V8 is the JavaScript engine developed by Google. Its job is to:
- Parse your JavaScript code.
- Compile it to machine code through just-in-time compilation, optimizing the functions that run most often.
- Manage memory with an automatic garbage collector.
Thanks to V8, JavaScript on Node.js is not "a slow interpreted language": for many tasks it stands shoulder to shoulder with compiled languages running on a virtual machine.
libuv: the asynchronous I/O library
V8 knows how to execute JavaScript, but it does not know how to read files or open sockets. That is libuv's job — a library written in C that provides:
- A uniform interface over each operating system's asynchronous I/O mechanisms (
epollon Linux,kqueueon macOS/BSD, IOCP on Windows). - The event loop, the engine that hands out the work.
- An internal thread pool for operations the operating system cannot perform asynchronously (much of file access, compression, cryptography).
flowchart TB
A["Your JavaScript code<br/>(src/catalog.js)"] --> B["Node.js<br/>APIs: fs, http, path..."]
B --> C["V8<br/>Runs JavaScript"]
B --> D["libuv<br/>Event loop + thread pool"]
D --> E["Operating system<br/>Files, network, timers"]
C --> D
One clarification that heads off a very common misunderstanding: when people say "Node.js is single-threaded" they mean the thread where your JavaScript runs. Underneath, libuv uses several threads. What never happens is two lines of your code running at the same time.
- The Non-Blocking I/O Model: the Waiter Analogy
This analogy is the best way to internalize the model. Picture the restaurant at the Teatro Almendra on an opening night.
Blocking model: one waiter per table
In a traditional restaurant, every table has its own assigned waiter. That waiter:
- Takes the order from table 1.
- Carries it to the kitchen.
- Stands in the kitchen waiting for the dish to be ready.
- Takes it to table 1.
- Only then attends another table.
To serve 50 tables you need 50 waiters. Most of the time they are standing still, waiting for the kitchen. Hiring waiters is expensive and the kitchen corridor fills up with people doing nothing.
Non-blocking model: one extremely well-organized waiter
Now picture a single, exceptionally efficient waiter:
- Takes the order from table 1 and drops it off at the kitchen.
- Does not wait: goes to table 2 and takes its order.
- Moves on to table 3, serves drinks to table 4, takes payment from table 5...
- When the kitchen rings the bell ("order for table 1 is up!"), the waiter picks up the dish and serves it.
A single waiter serves dozens of tables, because the slow work is not done by them: it is done by the kitchen. They only coordinate.
Translated into Node.js:
| Restaurant | Node.js |
|---|---|
| The waiter | The main thread where your JavaScript runs |
| The tables | The clients' requests |
| The kitchen | The operating system, the database, the network, the disk |
| The kitchen bell | The "operation completed" event |
| The notepad of pending orders | The loop's event queue |
| The waiter cooking a three-hour stew themselves | A CPU-intensive computation that blocks everything |
That last row is the key to Node.js's limitations: while the waiter is cooking, nobody is serving the tables. We will come back to it in section 6.
What this looks like in code
Look at the conceptual difference (you do not need to understand every detail yet; this is purely illustrative):
// BLOCKING style (synchronous): the program stops on every line.
const fs = require('node:fs');
const catalog = fs.readFileSync('data/events.json', 'utf8'); // Waits here
console.log('Catalog loaded');
console.log('This line runs after the file has been read');// NON-BLOCKING style (asynchronous): the program carries on and comes back later.
const fs = require('node:fs');
fs.readFile('data/events.json', 'utf8', (error, catalog) => {
// This function runs WHEN the file is ready
console.log('Catalog loaded');
});
console.log('This line runs BEFORE the file has finished being read');In the second case, the console output appears in the "reverse" order to the one reading the file suggests. That is disconcerting at first and is exactly what we will explore in depth in Callbacks and Asynchronous Programming.
- Comparison with the Thread-Per-Request Model
Let's compare Node.js with the classic model used (or once used) by PHP with Apache or Java with traditional servlets.
| Aspect | One thread/process per request (classic PHP, traditional Java) | Node.js (event loop) |
|---|---|---|
| Unit of concurrency | Operating system thread or process | Callback / promise on a single thread |
| Cost per idle connection | High (the thread's stack memory, ~1 MB or more) | Very low (a few KB of data structures) |
| Viable simultaneous connections | Hundreds or a few thousand per machine | Tens of thousands per machine |
| Programmer's mental model | Simple: the code reads top to bottom | Requires thinking asynchronously |
| Shared in-memory state | Requires synchronization (locks, critical sections) | No race conditions inside a callback |
| CPU-intensive computation | Spread across threads and cores | Blocks every user |
| Unhandled failure | Usually affects only one request | Can bring down the whole process if not handled |
| Use of multiple cores | Automatic | Requires cluster or several processes (Module 10) |
There is no "better" model. There is a model suited to a given load profile. Node.js shines when the server spends most of its time waiting on other systems, which is precisely what a modern web API does.
sequenceDiagram
participant C1 as Client A
participant C2 as Client B
participant N as Node.js (1 thread)
participant DB as Database
C1->>N: GET /events
N->>DB: query events
Note over N: Does not wait: stays free
C2->>N: GET /sessions
N->>DB: query sessions
DB-->>N: events result
N-->>C1: response A
DB-->>N: sessions result
N-->>C2: response B
- What Node.js Is Good At (and What It Is Not)
Cases where Node.js is an excellent fit
- REST APIs and microservices: the work consists of receiving a request, querying a database and returning JSON. That is almost pure I/O, and JSON is JavaScript's native format.
- Real-time applications: chats, notifications, live dashboards, capacity tracking. Thousands of open connections that are idle almost all the time: the ideal scenario.
- Command-line tools (CLI): fast start-up, a huge ecosystem, easy to distribute through npm. Much of the modern web development toolchain is written in Node.
- BFF (Backend For Frontend): a thin layer that aggregates calls to several internal services and composes exactly the response a single interface needs. Its job is to wait for several APIs at once: perfect for Node.
- Data streaming: processing large files or continuous flows without loading them into memory in full (Module 3).
- Prototyping and full-stack teams: the same language and the same validations on client and server.
Cases where Node.js is not the best choice
- CPU-intensive computation: video processing, model training, scientific computing, bulk cryptography, generating reports with millions of rows in memory. While your function is calculating, the "waiter" is cooking and nobody is serving the tables.
- Applications with strict typing requirements and complex shared-memory concurrency, where other ecosystems offer more mature tooling.
- Tasks that demand decimal numeric precision, such as accounting with decimals: JavaScript's
numbertype is floating point. (That is why, in Escena Viva, we will store prices in cents as integers; we will formalize this in the lesson The Course Project).
Important: "not the best choice" does not mean "impossible". Module 10 shows how to move heavy computation off the main thread with worker threads, how to spread load across cores with cluster, and how to defer work to queues with Redis. The limitation is real, but it has well-known solutions.
- The npm Ecosystem and "JavaScript Everywhere"
Node.js ships alongside npm (Node Package Manager), the largest package registry in the world, with millions of published libraries. This has two sides.
The good side: almost any common problem has already been solved. Need a web framework? Express. Validate data? Zod or Joi. Talk to MongoDB? Mongoose. Generate a PDF ticket? There are half a dozen options.
The side to watch: installing a package means bringing third-party code into your product. Dependency management, semantic versioning and security audits are part of the craft, and we devote the whole of Module 5 to them.
On top of that, "JavaScript everywhere" means the same language serves the browser, the server, build tooling, desktop applications (Electron) and serverless functions. For a small team like Escena Viva's, this cuts the cost of context switching: the person who validates a form in the browser can reuse that very same validation function in the API.
- LTS Versions and the Release Cadence
Node.js follows a very predictable release calendar. Knowing it avoids nasty surprises in production.
- Every April an even-numbered major version is released (20, 22, 24, 26...).
- Every October an odd-numbered major version is released (21, 23, 25...).
- Only even versions become LTS (Long Term Support), in October of the year they were released.
- Odd versions are short-lived (about six months) and exist to try out new features.
Life cycle of an even version:
| Phase | Approximate duration | What it gets | Use in production? |
|---|---|---|---|
| Current | 6 months (April → October) | Every new feature | Only for experimenting |
| Active LTS | 12 months | Fixes and safe improvements | Yes, recommended |
| Maintenance LTS | 12 months | Only critical and security fixes | Yes, but plan the jump |
| End of Life | — | Nothing | No, no security patches |
The practical rule for a real project is simple: always use the latest Active LTS version and plan the upgrade when yours moves into Maintenance. For Escena Viva we will use an LTS version and pin it in the project so the whole team works the same way.
How to install versions and switch between them is exactly the subject of the next lesson, Installing and Setting Up the Environment.
- Why Escena Viva Is a Good Fit for Node.js
Escena Viva sells tickets for events such as the Concierto de Otoño (Teatro Almendra), the Noche de Monólogos (Sala Bóveda) and the Festival de Jazz de Primavera (Auditorio Ribera). Let's analyze its workload:
| Business need | Work profile | Good fit for Node.js? |
|---|---|---|
| Public catalog of events and sessions | Read from a database and return JSON | Yes: pure I/O |
| Real-time available capacity during a presale | Thousands of open, idle connections | Yes: this is the flagship case |
| Traffic spikes when a festival goes on sale | High concurrency, little CPU per request | Yes |
| Order confirmation through an external payment gateway | Waiting for another API to respond | Yes |
| Emailing the ticket out | Network I/O, can be deferred to a queue | Yes (Module 10) |
| Generating 50,000 ticket PDFs in one go | CPU-intensive | Not on the main thread: worker threads or a queue |
| Monthly sales report in CSV | Large file | Yes, with streams (Module 3) |
Of seven needs, six are I/O. That ratio — very common in business applications — is why Node.js is a sensible choice for this platform. And the exception (the PDFs) has a well-known solution inside the ecosystem itself.
This is what our API's entry point will look like a few modules from now. You do not need to understand it yet; just look at it as a destination:
// Illustrative snippet: do NOT write this yet.
// A minimal server that responds with the Escena Viva catalog.
const http = require('node:http');
const server = http.createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'application/json' });
response.end(JSON.stringify({ message: 'Welcome to Escena Viva' }));
});
server.listen(3000);That is eight lines for a working HTTP server, with nothing else installed. That economy of means is also part of the appeal of Node.js.
Common Mistakes and Tips
Mistake 1: believing that "single-threaded" means "slow" or "does not use the hardware".
A single-threaded Node.js process can serve more concurrent connections than a traditional server with hundreds of threads, because hardly any of them consume CPU. And to take advantage of several cores there is cluster (Module 10): you launch as many Node processes as you have cores.
Mistake 2: confusing "asynchronous" with "parallel". Asynchronous means "I don't wait here, they'll let me know when it's ready". Parallel means "two things happening at the same time". Node.js does a great deal of the former and, for your JavaScript code, none of the latter.
Mistake 3: thinking Node.js is a web framework. Node.js is used to write web servers, but also CLIs, automation scripts, file processors or integration bots. Express (Module 6) is the framework; Node.js is the platform.
Mistake 4: choosing an odd version for a serious project. Odd versions run out of support within a few months. If the major number is odd, do not take it to production.
Tip 1: always ask "who is doing the waiting". For any operation, ask yourself whether the waiting is done by your code (bad) or by the operating system (good). This question will follow you through the entire course.
Tip 2: measure before ruling things out. "Node.js is no good for computation" is true beyond a certain scale. A 2 ms operation blocks nothing noticeable; an 800 ms one does.
Tip 3: do not memorize APIs, understand the model. The functions in fs or http are documented and you can always look them up. The execution model, by contrast, is something you have to carry in your head.
Exercises
Exercise 1: classify the workloads
For each of these hypothetical Escena Viva features, say whether it is I/O-intensive or CPU-intensive, and whether Node.js is a good choice for handling it on the main thread:
- Querying the database for the tickets still available for the Concierto de Otoño.
- Compressing the 40,000 passes for the Festival de Jazz de Primavera into a ZIP file.
- Keeping a connection open with every browser to warn them when fewer than 20 tickets are left.
- Working out the optimal seat allocation for a group of 15 people at the Auditorio Ribera by trying every combination.
- Calling the payment gateway to confirm a charge.
Exercise 2: explain the analogy in your own words
Write a paragraph of 5-8 lines explaining to a colleague who only knows PHP why a single Node.js process can serve 10,000 simultaneous connections while their Apache server saturates at 300. You must use the waiter analogy and explicitly mention where the slow work happens in each case.
Exercise 3: pick a version
You are starting Escena Viva in August 2026. Consulting the Node.js release calendar:
- Which version line would you choose, and why?
- What will happen to that version roughly a year from now?
- What would be the downside of choosing the freshly released Current version?
Solutions
Solution 1
| # | Feature | Type | Node.js on the main thread? |
|---|---|---|---|
| 1 | Querying availability | I/O | Yes. The work is done by the database; Node just waits for the result. |
| 2 | Compressing 40,000 passes | CPU | No. It would block every user for seconds or minutes. Solve it with worker threads or a job queue (Module 10). |
| 3 | Open connections for alerts | I/O | Yes, and this is where Node.js stands out most: thousands of idle connections cost very little. |
| 4 | Brute-force seat combinations | CPU | No, if the number of combinations is large. Better to find a smarter algorithm or move it to a worker. |
| 5 | Call to the payment gateway | I/O | Yes. It is waiting on the network, exactly what the asynchronous model is for. |
Nuance: point 4 depends on the size. If "every combination" amounts to a few dozen, the computation takes microseconds and there is no problem. The right question is never "is it CPU?", but "how long does it block for?".
Solution 2 (model answer)
In Apache with PHP, every visitor gets their own process or thread. That thread asks MySQL for the data and stands still waiting for the answer, occupying memory without doing anything useful; since each thread costs megabytes, by three hundred connections the server has run out of resources. Node.js works like a single, extremely well-organized waiter: it takes the order (the request), drops it off at the kitchen (the database or the operating system) and immediately goes off to serve another table instead of standing there waiting. It is not the one doing the slow work, the kitchen is; the waiter merely coordinates and serves when the bell rings. That is why ten thousand connections that spend nearly all their time waiting cost almost nothing: they are ten thousand orders jotted down on a notepad, not ten thousand waiters. The trade-off is that, if the waiter starts cooking a stew themselves — a heavy computation — every table goes unattended at once.
Solution 3
- The latest even line in Active LTS. In August 2026 that means the 24.x line, which entered LTS in October 2025. It is the one receiving stable fixes and the one supported by hosting providers and by the ecosystem's libraries.
- Around October 2027 it will move to Maintenance LTS (critical and security patches only), which is the moment to plan the jump to the next even line — 26.x, which will have entered LTS in October 2026.
- The freshly released Current version (26.x until October 2026) brings interesting new features, but it may introduce behavioral changes, many native libraries do not support it yet, and it is not designed for production loads. It is excellent for trying things out locally, not for selling tickets.
Conclusion
Node.js is a runtime that brings JavaScript to the server by combining the V8 engine (which executes the code) with libuv (which manages asynchronous input/output and the event loop). Its proposal is to replace the "one thread per request" model with a single thread that never waits: it delegates the slow work and responds to notifications, like a waiter who coordinates instead of cooking.
From that follow its strengths — APIs, real time, CLI tools, BFF layers, streaming — and its well-known weakness: CPU-intensive computation blocks everyone, something Module 10 solves with worker threads, cluster and queues. Around it, the npm ecosystem provides an unmatched library of solutions, and the LTS release calendar lets you choose sensibly which foundation to build on.
With this mental map in place, we have also seen why Escena Viva — a ticketing platform where almost everything is waiting on databases, payment gateways and clients — is a natural use case for Node.js.
In the next lesson, Installing and Setting Up the Environment, we move from theory to practice: we will install Node.js with a version manager, verify that everything works and create the escena-viva/ folder where the project will live throughout the course's twelve modules.
Node.js Course: From Beginner to Advanced
Module 1: Introduction to Node.js
- What Is Node.js?
- Installing and Setting Up the Environment
- Your First Node.js Program
- The Node.js REPL
- Modern JavaScript for Node.js
- The Course Project: the Escena Viva Platform
Module 2: Core Concepts
- Node.js Architecture
- The Event Loop
- Callbacks and Asynchronous Programming
- Promises and async/await
- Events and EventEmitter
- CommonJS Modules and require()
- ES Modules and Interoperability
Module 3: File System and I/O
- Reading and Writing Files
- The fs Module in Depth
- Cross-Platform Paths with the path Module
- Working with Streams
- Transform Streams and pipeline
- Buffers and Binary Data
Module 4: HTTP and Web Servers
- Creating a Simple HTTP Server
- Handling Requests and Responses
- Manual Routing
- Serving Static Files
- Receiving Data: Request Bodies and JSON
- Consuming External APIs from Node.js
Module 5: NPM and Package Management
- Introduction to NPM and package.json
- Installing and Using Packages
- Semantic Versioning and package-lock
- npm Scripts and Project Automation
- Creating and Publishing Packages
- Dependency Security and Maintenance
Module 6: The Express.js Framework
- Introduction to Express.js
- Setting Up an Express Application
- Routing in Express
- Middleware
- Essential Third-Party Middleware
- Input Data Validation
- Error Handling
Module 7: Databases and ORMs
- Introduction to Databases
- Using MongoDB with Mongoose
- CRUD Operations
- Relationships, Population and Advanced Queries
- Using SQL Databases with Sequelize
- Migrations, Transactions and Seed Data
Module 8: Authentication and Authorization
- Introduction to Authentication
- User Registration and Password Hashing
- Sessions and Cookies with Passport.js
- Authentication with JWT
- Role-Based Access Control
- API Security Best Practices
Module 9: Testing and Debugging
- Introduction to Testing
- Unit Testing with Mocha and Chai
- Test Doubles with Sinon
- Integration Testing
- Coverage and Test Automation
- Debugging Node.js Applications
Module 10: Advanced Topics
- The Cluster Module
- Worker Threads
- Caching and Job Queues with Redis
- Performance Optimization
- Building RESTful APIs
- GraphQL with Node.js
Module 11: Deployment and DevOps
- Configuration and Environment Variables
- Logging and Monitoring in Production
- Using PM2 for Process Management
- Packaging with Docker
- Deploying to Heroku and Other PaaS
- Continuous Integration and Deployment
