Chapter 2
Node.js Runtime, Tooling and Project Setup
Diploma in Full Stack Programming
Backend Development with Node.js/Express
•
Diploma
Node.js Runtime, Tooling and Project Setup
Introduction
Node.js is a runtime environment that allows JavaScript to execute outside a web browser. It is widely used for HTTP APIs, real-time services, command-line tools, automation, and backend applications. For full-stack programmers, understanding Node.js involves more than learning syntax: it requires understanding how the runtime executes code, how asynchronous operations are coordinated, how packages are managed, and how a project is structured for reliable teamwork and deployment.
This chapter establishes the foundation for later work with Express. Express does not replace Node.js; it operates on top of Node.js and uses facilities such as the HTTP module, streams, networking, file systems, and process management.
Learning Objectives
By the end of this chapter, you should be able to:
explain the roles of the V8 engine, Node.js runtime, event loop, and operating system;
use the Node.js REPL and command-line tooling;
create and manage a project with npm;
interpret semantic versioning and dependency ranges;
distinguish CommonJS and ECMAScript modules;
configure applications safely with environment variables;
define useful npm scripts;
apply linting and formatting practices; and
design a professional Node.js project structure.
1. The Node.js Execution Model
1.1 Runtime, engine, and platform
JavaScript is a programming language. V8 is a JavaScript engine, originally developed for Google Chrome, that parses JavaScript, generates machine code, and executes it. Node.js embeds V8 and adds APIs for server-side work, including file access, networking, cryptography, streams, processes, and timers.
A useful conceptual model is:
JavaScript source is parsed by V8.
V8 executes synchronous JavaScript and manages memory.
Node.js provides JavaScript-accessible system APIs.
The operating system performs many I/O operations.
Completion events are placed into queues and later handled by JavaScript callbacks or promise continuations.
Node.js is therefore not a language and not merely a library. It is a runtime platform consisting of an engine, system APIs, an event-driven coordination mechanism, and command-line tooling.
1.2 V8 and memory
V8 stores objects, arrays, functions, and other dynamically allocated values in a managed memory area commonly called the heap. Function execution uses a call stack. When objects are no longer reachable, garbage collection can reclaim their memory. This automatic memory management reduces manual errors, but it does not eliminate memory leaks: a program can accidentally retain references to objects that it no longer needs.
The event-driven model does not mean that JavaScript executes multiple instructions simultaneously on one main thread. Ordinary JavaScript callbacks run to completion on the main thread. A long calculation or an accidental infinite loop can prevent other callbacks from running. This is why CPU-intensive work may require worker threads, separate processes, or an external job system.
1.3 The event loop and non-blocking I/O
Node.js commonly handles many concurrent I/O operations by avoiding a dedicated JavaScript thread for each request. When code starts an operation such as reading a file or contacting a database, the runtime delegates appropriate work to the operating system or an internal worker pool. When the operation completes, its callback or promise continuation becomes eligible for execution.
Figure: A simplified view of Node.js event-driven execution.
The figure is conceptual rather than a complete specification of event-loop phases. Timers, pending callbacks, poll activity, check callbacks, and close callbacks have more precise ordering rules. Promise reactions and process.nextTick() also have special scheduling behavior. Production code should avoid assuming that unrelated asynchronous operations complete in a particular order.
1.4 Concurrency and parallelism
Concurrency means that multiple operations are in progress during an overlapping period. Parallelism means that operations execute simultaneously on different CPU cores or execution threads. A Node.js application can achieve high I/O concurrency while most application JavaScript runs on one main thread. Worker threads and child processes provide mechanisms for parallel or isolated computation, but they add communication and operational complexity.
Node.js is especially effective for I/O-bound services, such as APIs that spend much of their time waiting for databases, files, or network services. It is not automatically the best choice for every CPU-bound workload.
2. Using the Node.js REPL and Command Line
REPL means Read–Eval–Print Loop. Start it by entering node in a terminal. The REPL reads an expression, evaluates it, prints the result, and waits for another expression.
The REPL is useful for exploring APIs, testing expressions, inspecting values, and checking language behavior. It is not a substitute for automated tests or source-controlled programs.
Common command-line uses include:
Command
Purpose
node app.js
Run a JavaScript file
node --version
Display the Node.js version
node --help
Display command-line help
node --watch app.js
Restart a program after changes in supported Node.js versions
node -e "console.log(2 + 2)"
Execute a short expression
node --env-file=.env app.js
Load environment variables in supported versions
Node.js version support matters. Teams should select a maintained LTS release and record the expected version through documentation and, where appropriate, files such as .nvmrc, Volta configuration, or a package.jsonengines field. Developers should verify the exact features supported by their selected version.
3. npm and Package Management
npm is both a package registry and a command-line tool. It downloads packages, records dependencies, runs project scripts, and supports publishing. A project normally begins with:
mkdir task-api
cd task-api
npm init -y
This creates package.json, the project manifest. A typical manifest may contain:
Install a runtime dependency with npm install express. Install a development-only tool with npm install --save-dev eslint. The first command updates dependencies; the second updates devDependencies.
node_modules contains installed packages and should normally not be committed to version control. package-lock.json records the resolved dependency tree and should normally be committed so that installations are reproducible. In automated environments, npm ci installs from the lock file and is designed for clean, repeatable installation.
3.1 Direct and transitive dependencies
A direct dependency is explicitly required by the project. A transitive dependency is required by another package. A package may be safe and well maintained while one of its transitive dependencies later develops a vulnerability or becomes unsupported. Teams should therefore review dependency trees, update regularly, and use security scanning as one part of—not a replacement for—technical review.
3.2 Semantic versioning
Semantic Versioning, commonly written as MAJOR.MINOR.PATCH, communicates compatibility expectations:
MAJOR: incompatible public API changes;
MINOR: backward-compatible functionality; and
PATCH: backward-compatible bug fixes.
A version such as 2.4.1 means major version 2, minor version 4, patch version 1. Semantic versioning is a communication convention, not a guarantee. A package can incorrectly introduce a breaking change in a minor release, or a consumer can misuse a supposedly compatible API.
Common npm ranges include:
Range
General meaning
1.2.3
Exactly that version
^1.2.3
Compatible minor and patch updates below version 2.0.0
~1.2.3
Patch-level updates below version 1.3.0
>=1.2.3 <2.0.0
Explicit bounded range
For versions with major zero, compatibility rules are more conservative and package authors may treat minor changes as breaking. Lock files still matter because a range can resolve to different versions over time.
4. Modules: CommonJS and ECMAScript Modules
Modules divide a program into files with explicit boundaries. They improve organization, reuse, testing, and control over dependencies.
4.1 CommonJS
CommonJS is the traditional Node.js module system:
// math.cjs
function add(a, b) {
return a + b;
}
module.exports = { add };
require() loads a module, and module.exports defines its exported value.
4.2 ECMAScript modules
ECMAScript modules are the standardized JavaScript module system:
// math.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from './math.js';
console.log(add(2, 3));
A project can opt into ESM by placing "type": "module" in package.json, or by using the .mjs extension. CommonJS can be selected with .cjs or an appropriate package configuration.
ESM uses static import and export declarations, supports tooling such as tree analysis, and aligns with browser JavaScript. CommonJS remains common in existing Node.js ecosystems. Mixing systems is possible but can create interoperability issues, particularly around default exports, asynchronous loading, and file extensions. A professional project should choose a primary system and document any intentional exceptions.
Avoid confusing a module’s public interface with its internal implementation. Export only what other parts of the application need. This reduces coupling and makes later changes safer.
5. Configuration and Environment Variables
Configuration includes values that vary between environments: port numbers, database URLs, logging levels, feature flags, and external service credentials. Environment variables are a common mechanism for supplying configuration without hard-coding it into source files.
const port = Number.parseInt(process.env.PORT ?? '3000', 10);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error('PORT must be a valid TCP port');
}
The conversion is important because environment variables arrive as strings. Configuration should be parsed and validated at startup, not repeatedly and inconsistently throughout the application.
A .env file may be useful for local development:
PORT=3000
LOG_LEVEL=info
It must not be committed when it contains secrets. Commit a .env.example containing names and safe sample values. Production secrets should normally be supplied through a managed secret store, deployment platform, or protected environment configuration.
Configuration principles include:
fail fast when required values are missing;
validate types, ranges, and allowed values;
never log passwords, tokens, or private keys;
use least privilege for database and service credentials;
separate configuration from application logic; and
document required variables and their purpose.
Environment variables are not a security boundary by themselves. Any process user or diagnostic tool with suitable access may be able to read them.
6. Scripts, Linting, and Quality Controls
npm scripts provide a consistent interface for common tasks:
Scripts reduce dependence on each developer remembering long commands. They also give continuous-integration systems predictable entry points.
Linting uses static analysis to detect suspicious patterns, style violations, and some correctness problems without running the complete application. ESLint is widely used in JavaScript projects. A linter may identify unused variables, unreachable code, inconsistent syntax, or unsafe practices. It cannot prove that business rules are correct and does not replace tests, code review, type checking, or runtime monitoring.
Formatting tools such as Prettier can standardize layout. Teams should avoid endless arguments about personal style by committing configuration files and applying checks automatically. A practical quality pipeline often includes installation from the lock file, linting, tests, security checks, and a build or packaging step where applicable.
7. Professional Project Structure
There is no single universally correct directory structure. The appropriate design depends on system size, team conventions, deployment model, and domain boundaries. A small service might begin as:
server.js: starts the process and binds the network port;
app.js: constructs the application, middleware, and routes;
routes: maps HTTP methods and paths to handlers;
controllers: translates transport requests into application actions;
services: contains business rules and use cases;
repositories: isolates persistence operations; and
config: loads and validates configuration.
This arrangement is not a rigid law. Over-separating a tiny program creates unnecessary indirection, while placing database queries, validation, and HTTP formatting in one large file makes change difficult. Organize around clear responsibilities and stable boundaries.
8. Illustrative Case: Preparing a Task API
Background
A student team is building a task-management API for a college project. Their first prototype consists of one server.js file, hard-coded port settings, an uncommitted .env file, and dependencies installed without committing a lock file.
Problem
The application works on one laptop but fails in the test environment. A database connection string was accidentally exposed in a repository. Team members also use different Node.js versions, and a package update changes behavior unexpectedly.
Analysis
The failure is not primarily an Express problem. It reflects weak runtime and project governance: configuration is not validated, secrets are not controlled, dependency resolution is not reproducible, and the project has no standard quality commands.
Approach
The team takes these steps:
Selects a maintained Node.js LTS release and records it in project documentation.
Runs npm init and commits package.json and package-lock.json.
Adds express as a runtime dependency and ESLint as a development dependency.
Creates src/server.js, src/app.js, and responsibility-based directories.
Moves the port and database URL to environment configuration.
Adds startup validation and a safe .env.example.
Defines start, dev, test, lint, and check scripts.
Adds .gitignore entries for node_modules, local environment files, logs, and coverage output.
Uses npm ci in continuous integration and reviews dependency updates before merging.
Outcome
Every developer can install the same dependency tree, run the same checks, and configure the service without exposing credentials. Failures occur early with informative messages. The separation between application construction and server startup also makes automated testing easier.
Learning points
Professional setup is part of software engineering, not administrative overhead. Reproducibility, secret management, version discipline, and automated checks reduce defects before users encounter them.
9. Challenges, Limitations, and Governance
Node.js applications face several recurring challenges. Callback or promise misuse can produce race conditions and unhandled failures. Blocking CPU work can increase response latency for every request sharing the event loop. Excessive dependencies enlarge the attack surface and maintenance burden. Automatic package updates can introduce unexpected behavior. Configuration mistakes can expose secrets or connect a test program to production data.
Governance should include dependency ownership, review policies, vulnerability response, license awareness, and secure release procedures. Open-source packages have licenses with obligations; legal or organizational policy may require review before distribution. Logs should support diagnosis without collecting unnecessary personal data. Data protection principles such as purpose limitation, minimization, retention control, and access restriction apply to backend logs and configuration as well as to databases.
Artificial intelligence tools can assist with scaffolding, documentation, and lint-rule explanations, but generated code must be reviewed. Developers remain responsible for dependency choices, security, licensing, correctness, and protection of confidential source code or credentials. Never paste production secrets into an AI tool or commit AI-generated code without testing and inspection.
10. Contemporary Practice and Best Practices
Current Node.js practice emphasizes maintained LTS releases, explicit module choices, lock-file-based installation, automated checks, structured logging, and deployment-specific configuration. Containers and cloud platforms often make the process environment explicit, but they do not remove the need for validation or secret management. Observability commonly combines logs, metrics, and traces; even a small educational API benefits from meaningful startup errors and request correlation identifiers.
A practical setup checklist is:
use a maintained Node.js release;
initialize the project with a clear package name and license policy;
commit the lock file but not node_modules or secrets;
select ESM or CommonJS deliberately;
validate environment configuration at startup;
define repeatable npm scripts;
lint and test before merging;
review direct and transitive dependencies; and
document installation, configuration, and operational commands in README.md.
Chapter Summary
Node.js combines the V8 JavaScript engine with server-side APIs and an event-driven runtime. Its non-blocking I/O model supports high concurrency for many I/O-bound services, but synchronous or CPU-heavy work can block the main JavaScript thread. The REPL is useful for experimentation, while npm supplies project initialization, package installation, scripts, and dependency management. package.json describes a project; the lock file records a reproducible dependency tree. Semantic versioning communicates intended compatibility but is not an absolute guarantee.
CommonJS and ECMAScript modules provide different module systems, and a project should adopt a deliberate, documented strategy. Environment configuration must be parsed, validated, and kept separate from source code and secrets. npm scripts, linting, testing, and continuous integration create repeatable quality controls. Finally, a professional project structure separates startup, application composition, transport concerns, business logic, persistence, and configuration according to the system’s actual complexity.
Key Terms
V8: The JavaScript engine embedded by Node.js.
Runtime: Software that provides the environment and services needed to execute a program.
Event loop: A coordination mechanism that schedules callbacks and asynchronous completions.
Non-blocking I/O: Input/output design that allows JavaScript to continue while an operation is pending.
Concurrency: Multiple operations progressing during overlapping periods.
REPL: Read–Eval–Print Loop for interactive code execution.
npm: Node.js package manager and package registry client.
package.json: Project manifest containing metadata, scripts, and dependency declarations.
package-lock.json: File recording resolved package versions and dependency relationships.
Dependency: External software required by a project.
Semantic versioning: MAJOR.MINOR.PATCH convention for communicating compatibility.
CommonJS: Node.js module system using require and module.exports.
ECMAScript module: Standard JavaScript module system using import and export.
Environment variable: Process-supplied configuration value represented as text.
Linting: Static analysis for potential errors and rule violations.
Transitive dependency: Package required indirectly through another dependency.
LTS: Long-Term Support release intended for extended maintenance.
Configuration validation: Checking configuration values for required presence, type, range, and allowed form.
Review Questions
Short Answer Questions
What is the relationship between V8 and Node.js?
Why can a CPU-intensive function harm the responsiveness of a Node.js server?
What is the purpose of package-lock.json?
Distinguish dependencies from devDependencies.
Why should environment variables be validated at application startup?
Long Answer Questions
Explain the event-driven execution model of Node.js and discuss its suitability for I/O-bound services.
Compare CommonJS and ECMAScript modules, including syntax, configuration, and interoperability considerations.
Explain semantic versioning and discuss why version ranges and lock files are both important.
Design a professional directory structure for a medium-sized Express API and justify each major directory.
Discuss how npm scripts, linting, testing, and continuous integration contribute to software quality.
Analytical / Application Questions
A team’s API works locally but fails in continuous integration because it uses a newer package version and a missing database variable. Diagnose the causes and propose a reproducible setup.
A server performs a large synchronous data transformation whenever a request arrives. Predict the effect on concurrent requests and recommend two architectural alternatives.
Design a configuration strategy for development, testing, and production that protects secrets, validates required values, and allows each environment to use different services.
Multiple Choice Questions with Answers
Question 1
Which component executes JavaScript instructions in Node.js?
A. The V8 engine
B. The npm registry
C. The package-lock file
D. The Express router
Correct Answer: A. The V8 engine
Question 2
Why is a long synchronous computation particularly harmful in a typical Node.js application?
A. It deletes package metadata
B. It blocks the main JavaScript thread and delays other callbacks
C. It automatically changes semantic versions
D. It prevents environment variables from existing
Correct Answer: B. It blocks the main JavaScript thread and delays other callbacks
Question 3
Which command creates a package.json file using default answers?
A. node create package
B. npm install package.json
C. npm init -y
D. npm start -y
Correct Answer: C. npm init -y
Question 4
What does a package-lock.json file primarily provide?
A. A list of source-code comments
B. A record of resolved dependency versions and relationships
C. A replacement for environment variables
D. A JavaScript execution engine
Correct Answer: B. A record of resolved dependency versions and relationships
Question 5
In semantic versioning, what does the PATCH component normally indicate?
A. An incompatible API redesign
B. A new backward-compatible feature
C. A backward-compatible bug fix
D. A change from CommonJS to ESM
Correct Answer: C. A backward-compatible bug fix
Question 6
Which syntax is associated with ECMAScript modules?
A. require() and module.exports
B. import and export
C. npm and npx
D. process.env and console.log
Correct Answer: B. import and export
Question 7
Why should an environment variable such as PORT be parsed before use?
A. Environment variables are supplied as strings
B. Parsing installs a package
C. Parsing creates a lock file
D. Environment variables are always encrypted
Correct Answer: A. Environment variables are supplied as strings
Question 8
Which item should normally not be committed to a source-control repository?
A. package.json
B. package-lock.json
C. README.md
D. A local .env file containing secrets
Correct Answer: D. A local .env file containing secrets
Question 9
What is the principal purpose of linting?
A. To statically identify suspicious patterns and rule violations
B. To replace all runtime testing
C. To publish packages to npm
D. To encrypt database credentials
Correct Answer: A. To statically identify suspicious patterns and rule violations
Question 10
Which practice best supports reproducible dependency installation in continuous integration?
A. Delete the lock file before every build
B. Use npm ci with a committed lock file
C. Install packages manually on each server
D. Use only unbounded dependency ranges
Correct Answer: B. Use npm ci with a committed lock file
Backend Development with Node.js/Express
Chapter 2
of 15