With the environment installed and the escena-viva/ folder created, it is time to write code. In this lesson we will build the project's first real program: src/catalog.js, a script that defines Escena Viva's event catalog and prints it to the console, with the option of filtering it by venue from the command line.

Along the way you will discover the practical differences between running JavaScript in the browser and in Node.js, meet the process object, learn to tell standard output apart from error output, and find out what a program's exit code means. These concepts look minor and turn out to be fundamental the moment your code stops being an exercise and starts living inside a server or a deployment pipeline.

Contents

  1. Writing and running your first script
  2. The browser and Node.js: what is there and what is not
  3. The process object: your window onto the system
  4. The Escena Viva catalog: src/catalog.js
  5. Displaying data: console.log, console.table and template literals
  6. Reading arguments with process.argv
  7. console.error, stdout and stderr
  8. Exit codes and process.exit
  9. Debugging the classic syntax error
  10. What this program does not do yet

  1. Writing and Running Your First Script

Move into the project folder and create a test file inside src/:

cd escena-viva

Create src/hello.js with this content:

// src/hello.js
// The first program of the course: checking that Node runs our code.

console.log('Escena Viva: environment up and running');
console.log('Node version:', process.version);
console.log('Working directory:', process.cwd());

Run it:

node src/hello.js
Escena Viva: environment up and running
Node version: v24.5.0
Working directory: /home/user/projects/escena-viva

Three important details about this first run:

  • The path is relative to the directory you run node from, not to the file. If you were inside src/, the command would be node hello.js.
  • The .js extension is optional: node src/hello works too. Write it anyway; it is clearer.
  • There is no compilation and no prior configuration. You do not need a package.json, you do not need to install anything, and you do not need to declare a main. Node reads the file and runs it.

  1. The Browser and Node.js: What Is There and What Is Not

If you come from front-end development, your instinct will be to reach for things that do not exist here. Let's check:

// src/differences.js
// Which global objects exist in Node and which do not.

console.log('typeof window   :', typeof window);     // undefined
console.log('typeof document :', typeof document);   // undefined
console.log('typeof alert    :', typeof alert);      // undefined
console.log('typeof global   :', typeof global);     // object
console.log('typeof process  :', typeof process);    // object
console.log('typeof console  :', typeof console);    // object
console.log('typeof fetch    :', typeof fetch);      // function
node src/differences.js
typeof window   : undefined
typeof document : undefined
typeof alert    : undefined
typeof global   : object
typeof process  : object
typeof console  : object
typeof fetch    : function

A summary of the equivalences:

Task Browser Node.js
Global object window global
Global object (standard in both) globalThis globalThis
Displaying information console.log, alert console.log (there is no alert)
Manipulating the interface document.querySelector(...) Does not exist: there is no interface
HTTP request fetch (with CORS restrictions) fetch (no CORS) or the http/https modules
Storing data locally localStorage Files (fs) or a database
Reading input parameters location.search process.argv
Configuration variables Baked into the bundle process.env
Ending execution Closing the tab process.exit()
Timers setTimeout, setInterval The same ones, plus setImmediate

console.log works in both environments, but it does not do the same thing: in the browser it writes to the developer tools console; in Node it writes to the process's standard output, which is what you see in the terminal and what can be redirected to a file or to another program. We will come back to this in section 7.

  1. The process Object: Your Window onto the System

process is a global object representing the operating system process your program runs in. It is the gateway to everything surrounding your code.

Property or method What it gives you
process.argv An array with the command-line arguments
process.env An object with the environment variables
process.version The Node version (v24.5.0)
process.platform 'linux', 'darwin', 'win32'
process.cwd() The current working directory
process.uptime() How many seconds the process has been alive
process.memoryUsage() Memory usage in bytes
process.exit(code) Ends the process with an exit code
process.exitCode Sets the exit code without cutting execution short
process.stdout / process.stderr The standard output and error streams

A handy example for diagnosing environments:

// src/environment.js
// A quick diagnosis of Escena Viva's runtime environment.

const memory = process.memoryUsage();

console.log(`Node        : ${process.version}`);
console.log(`Platform    : ${process.platform}`);
console.log(`Directory   : ${process.cwd()}`);
console.log(`Heap memory : ${Math.round(memory.heapUsed / 1024 / 1024)} MB`);
Node        : v24.5.0
Platform    : linux
Directory   : /home/user/projects/escena-viva
Heap memory : 5 MB

  1. The Escena Viva Catalog: src/catalog.js

Now for the real program. Escena Viva runs events at three venues: the Teatro Almendra, the Sala Bóveda and the Auditorio Ribera. Every event has one or more sessions, and every session has a date, a capacity, a number of tickets sold and a price.

One design decision we adopt from the very first minute: prices are stored in cents, as whole numbers. Never 25.50, always 2550. The reason is that JavaScript's number type is floating point and produces surprises like 0.1 + 0.2 === 0.30000000000000004. With money, that is unacceptable. We will formalize this and other conventions in The Course Project.

Create src/catalog.js:

// src/catalog.js
// Escena Viva event catalog.
// For now the data is written in the file itself; in module 3
// we will read it from data/events.json.

const catalog = [
  {
    id: 'evt-001',
    title: 'Concierto de Otono',
    venue: 'Teatro Almendra',
    category: 'concert',
    durationMinutes: 95,
    sessions: [
      { id: 'ses-001-1', dateTime: '2026-10-03T20:00:00', capacity: 420, sold: 180, priceCents: 2500 },
      { id: 'ses-001-2', dateTime: '2026-10-04T19:00:00', capacity: 420, sold: 96,  priceCents: 2200 }
    ]
  },
  {
    id: 'evt-002',
    title: 'Noche de Monologos',
    venue: 'Sala Boveda',
    category: 'comedy',
    durationMinutes: 80,
    sessions: [
      { id: 'ses-002-1', dateTime: '2026-10-10T21:30:00', capacity: 120, sold: 118, priceCents: 1800 },
      { id: 'ses-002-2', dateTime: '2026-10-11T21:30:00', capacity: 120, sold: 45,  priceCents: 1800 },
      { id: 'ses-002-3', dateTime: '2026-10-17T21:30:00', capacity: 120, sold: 12,  priceCents: 1500 }
    ]
  },
  {
    id: 'evt-003',
    title: 'Festival de Jazz de Primavera',
    venue: 'Auditorio Ribera',
    category: 'festival',
    durationMinutes: 240,
    sessions: [
      { id: 'ses-003-1', dateTime: '2027-04-17T19:00:00', capacity: 900, sold: 640, priceCents: 3800 },
      { id: 'ses-003-2', dateTime: '2027-04-18T19:00:00', capacity: 900, sold: 720, priceCents: 4200 }
    ]
  }
];

Notice one deliberate detail: the text values that will end up inside identifiers or file names avoid accents and ñ (Concierto de Otono, Sala Boveda). In the data shown to the end user we will eventually use the correct spelling, but while we are working at the console and with technical names, keeping things simple saves us encoding headaches. It is a project convention, not a limitation of Node.

  1. Displaying Data: console.log, console.table and Template Literals

Next, in the same file, add one function that formats the price and another that prints the catalog.

// --- Presentation helpers ---

// Turns 2500 (cents) into the string "25.00 EUR".
function formatPrice(cents) {
  const euros = Math.floor(cents / 100);
  const remainder = String(cents % 100).padStart(2, '0');
  return `${euros}.${remainder} EUR`;
}

// Turns "2026-10-03T20:00:00" into "03/10/2026 20:00".
function formatDate(isoDate) {
  const date = new Date(isoDate);
  const day = String(date.getDate()).padStart(2, '0');
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const year = date.getFullYear();
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  return `${day}/${month}/${year} ${hours}:${minutes}`;
}

An explanation of the new pieces, for anyone arriving with only basic JavaScript:

  • Template literals (backticks): `${euros}.${remainder} EUR` lets you embed expressions inside a string without concatenating with +. It is more readable and it accepts real line breaks.
  • padStart(2, '0'): pads on the left until the string reaches 2 characters. It turns 5 into "05", which is what we want for dates and cents.
  • Math.floor(2500 / 100) gives the whole euros; 2500 % 100 gives the leftover cents. By always working with integers there are no rounding errors.

Now the listing itself:

// --- Catalog presentation ---

function showCatalog(events) {
  console.log('');
  console.log('==========================================');
  console.log('      ESCENA VIVA - EVENT CATALOG');
  console.log('==========================================');

  for (const event of events) {
    // Total tickets available, adding up every session.
    let totalAvailable = 0;
    for (const session of event.sessions) {
      totalAvailable += session.capacity - session.sold;
    }

    console.log('');
    console.log(`${event.title}  [${event.id}]`);
    console.log(`  Venue     : ${event.venue}`);
    console.log(`  Category  : ${event.category}`);
    console.log(`  Duration  : ${event.durationMinutes} min`);
    console.log(`  Sessions  : ${event.sessions.length}`);
    console.log(`  Available : ${totalAvailable} tickets`);

    for (const session of event.sessions) {
      const available = session.capacity - session.sold;
      const occupancy = Math.round((session.sold / session.capacity) * 100);
      console.log(
        `    - ${formatDate(session.dateTime)}  ` +
        `${formatPrice(session.priceCents)}  ` +
        `${available}/${session.capacity} available  (${occupancy}% full)`
      );
    }
  }

  console.log('');
}

console.table: an effortless table

Node includes console.table, which draws an array of objects as an ASCII table. It is perfect for inspecting data quickly.

// A compact table view.
function showSummaryTable(events) {
  // We build a "flat" array: console.table expects simple objects.
  const rows = events.map((event) => {
    let totalCapacity = 0;
    let totalSold = 0;
    for (const session of event.sessions) {
      totalCapacity += session.capacity;
      totalSold += session.sold;
    }

    return {
      Event: event.title,
      Venue: event.venue,
      Sessions: event.sessions.length,
      Capacity: totalCapacity,
      Sold: totalSold,
      Occupancy: `${Math.round((totalSold / totalCapacity) * 100)}%`
    };
  });

  console.table(rows);
}

Output:

┌─────────┬─────────────────────────────────┬────────────────────┬──────────┬──────────┬──────┬───────────┐
│ (index) │ Event                           │ Venue              │ Sessions │ Capacity │ Sold │ Occupancy │
├─────────┼─────────────────────────────────┼────────────────────┼──────────┼──────────┼──────┼───────────┤
│ 0       │ 'Concierto de Otono'            │ 'Teatro Almendra'  │ 2        │ 840      │ 276  │ '33%'     │
│ 1       │ 'Noche de Monologos'            │ 'Sala Boveda'      │ 3        │ 360      │ 175  │ '49%'     │
│ 2       │ 'Festival de Jazz de Primavera' │ 'Auditorio Ribera' │ 2        │ 1800     │ 1360 │ '76%'     │
└─────────┴─────────────────────────────────┴────────────────────┴──────────┴──────────┴──────┴───────────┘

Other console methods worth knowing:

Method Use
console.log(...) Normal output (stdout)
console.error(...) Error output (stderr)
console.warn(...) Same as error: it goes to stderr
console.table(array) ASCII table
console.dir(obj, { depth: null }) Inspects a nested object without truncating it
console.time('t') / console.timeEnd('t') Measures how long a block takes
console.count('x') Counts how many times a point is reached

  1. Reading Arguments with process.argv

We want to be able to run:

node src/catalog.js "Sala Boveda"

and see only the events at that venue. Arguments arrive in process.argv, an array of strings.

// Let's first look at exactly what process.argv contains
console.log(process.argv);
node src/catalog.js "Sala Boveda" --table
[
  '/home/user/.nvm/versions/node/v24.5.0/bin/node',
  '/home/user/projects/escena-viva/src/catalog.js',
  'Sala Boveda',
  '--table'
]

The structure is always the same:

Index Content
0 The absolute path of the Node executable
1 The absolute path of the script being run
2 onwards The arguments the user typed

Hence the usual idiom: process.argv.slice(2).

// --- Reading the arguments ---

const args = process.argv.slice(2);

// The first argument that does not start with "--" is read as a venue.
const venueFilter = args.find((arg) => !arg.startsWith('--'));
const useTable = args.includes('--table');

// We filter the catalog if a venue was given.
let displayedEvents = catalog;

if (venueFilter) {
  displayedEvents = catalog.filter(
    (event) => event.venue.toLowerCase() === venueFilter.toLowerCase()
  );
}

Comparing in lower case with toLowerCase() makes the filter forgiving: "sala boveda" and "Sala Boveda" both work. It is a small detail with a big effect on the usability of a command-line tool.

For more complex arguments (options with values, combined flags), Node includes util.parseArgs, and the ecosystem offers packages such as commander or yargs. We will look at them when we reach npm in Module 5.

  1. console.error, stdout and stderr

Every operating system process has two separate output channels:

  • stdout (standard output): the program's useful result.
  • stderr (error output): error messages, warnings and diagnostics.

They are separate for a practical reason: it lets you redirect the result to a file without mixing errors into it.

# Only the catalog goes to the file; errors still show up on screen
node src/catalog.js > reports/catalog.txt

# Redirect the errors to another file as well
node src/catalog.js > reports/catalog.txt 2> reports/errors.txt
flowchart LR
    P["Node process<br/>src/catalog.js"] -->|"stdout (1)<br/>console.log"| A["Terminal or<br/>reports/catalog.txt"]
    P -->|"stderr (2)<br/>console.error"| B["Terminal or<br/>reports/errors.txt"]

If you mix errors into console.log, anyone redirecting your output to a file will find error messages embedded among the data. Rule: data through console.log, everything else through console.error.

Let's apply it to the venue filter:

// --- Validating the filter ---

if (venueFilter && displayedEvents.length === 0) {
  // This is a diagnostic, not data: it goes to stderr.
  console.error(`Error: there are no events at venue "${venueFilter}".`);
  console.error('Available venues:');
  for (const venue of new Set(catalog.map((event) => event.venue))) {
    console.error(`  - ${venue}`);
  }
  process.exit(1);
}

new Set(...) removes duplicates: if two events share a venue, it only appears once. We will look at this more calmly in Modern JavaScript for Node.js.

  1. Exit Codes and process.exit

When a program ends, it returns an integer to the operating system: the exit code.

Code Conventional meaning
0 Success
1 Generic error
2 Incorrect usage (badly written arguments)
> 2 Specific codes defined by your application

You can check it after running a command:

node src/catalog.js "Nonexistent Venue"
echo $?          # Linux/macOS -> prints 1
node src\catalog.js "Nonexistent Venue"
$LASTEXITCODE    # Windows PowerShell -> prints 1

This is what lets you chain commands safely in a deployment:

# The report is only generated if the catalog validated correctly
node src/catalog.js --validate && node src/report.js

Using process.exit() responsibly

process.exit(1) kills the process immediately. It is a blunt instrument: if there are pending file writes or network responses, they may be left half done.

The recommended alternative in most cases is to assign process.exitCode and let the program finish naturally:

// Preferable: sets the exit code but lets things wind down cleanly
if (venueFilter && displayedEvents.length === 0) {
  console.error(`Error: there are no events at venue "${venueFilter}".`);
  process.exitCode = 1;
  return;   // If we are inside a function
}
Form When to use it
process.exitCode = 1 Almost always: safe, lets pending tasks complete
process.exit(1) Fatal errors in simple scripts, or when you have no choice but to stop

We close the file with the execution itself:

// --- Execution ---

if (useTable) {
  showSummaryTable(displayedEvents);
} else {
  showCatalog(displayedEvents);
}

console.log(`Total: ${displayedEvents.length} event(s) shown.`);

Try it out:

node src/catalog.js                        # The whole catalog, detailed format
node src/catalog.js --table                # The whole catalog, table format
node src/catalog.js "Teatro Almendra"      # A single venue
node src/catalog.js "Nonexistent Venue"    # Handled error, exit code 1

  1. Debugging the Classic Syntax Error

Introduce a fault deliberately: delete a closing brace in the catalog array and run the script.

/home/user/projects/escena-viva/src/catalog.js:34
];
 ^

SyntaxError: Unexpected token ']'
    at wrapSafe (node:internal/modules/cjs/loader:1281:20)
    at Module._compile (node:internal/modules/cjs/loader:1321:27)
    ...

Node.js v24.5.0

How to read this message, from top to bottom:

  1. .../src/catalog.js:34: the file and line where Node noticed the problem.
  2. The code fragment and the ^: the exact token that did not fit.
  3. SyntaxError: Unexpected token ']': the type of error.
  4. The call stack (at wrapSafe...): in syntax errors these are Node's internal functions; ignore it, it tells you nothing.

A key point that confuses a lot of people: the line Node reports is where the parser choked, not necessarily where the mistake is. If you forget a brace on line 20, the error may point at line 34. Search upwards from the reported line.

Common errors and their messages:

Message Usual cause
SyntaxError: Unexpected token '}' An extra or badly closed brace, parenthesis or bracket
SyntaxError: Unexpected end of input A closing symbol is missing at the end of the file
ReferenceError: catalog is not defined A misspelled name, or a variable used before it was declared
TypeError: Cannot read properties of undefined (reading 'sessions') You accessed a property of something whose value is undefined
Error: Cannot find module '/path/src/catalog.js' The wrong file name or path

That last one is very typical at the beginning: Cannot find module almost always means you ran node catalog.js from the project root instead of node src/catalog.js.

An editor with ESLint would have flagged the error before you even saved. That is why the previous lesson insisted on setting it up.

  1. What This Program Does Not Do Yet

Knowing what you have built matters as much as knowing what you are still missing:

Current limitation Where it gets solved
The data is written inside the .js; changing the catalog means touching code Module 3: we will read data/events.json with fs
It only shows up in the terminal; there is no way to query it from a browser Module 4: we will serve the catalog over HTTP
All the code lives in a single file Module 2: we will split it into modules with require/module.exports
There is no persistence for orders or users Module 7: the escena_viva database
Argument parsing is hand-rolled Module 5: specialized packages

That is exactly the path this course takes: that very same src/catalog.js will grow and transform module by module until it becomes a real API.

Common Mistakes and Tips

Mistake 1: using window, document or alert. They do not exist in Node. If a tutorial uses them, it is talking about the browser.

Mistake 2: believing that process.argv[0] is the user's first argument. It is the path of the Node executable. The user's arguments start at index 2; always use process.argv.slice(2).

Mistake 3: sending errors through console.log. It pollutes the data output. Errors and warnings go through console.error.

Mistake 4: calling process.exit() in the middle of an operation. It kills the process without waiting for pending writes to finish. Prefer process.exitCode.

Mistake 5: running the script from the wrong directory. Cannot find module usually means exactly that. Check process.cwd().

Mistake 6: looking for the syntax error precisely on the line Node reports. The parser points at where it failed, not at where the slip is. Look at the preceding lines.

Tip 1: use console.table to inspect data. It is far more readable than a console.log of an array of objects.

Tip 2: measure with console.time. Wrapping a block between console.time('load') and console.timeEnd('load') is the cheapest way to find out what is slow.

Tip 3: think of your scripts as commands. Clear arguments, data on stdout, errors on stderr and a correct exit code. That way you can chain them with other tools.

Exercises

Exercise 1: environment information

Create src/environment.js that prints a table (with console.table) containing these rows: Node version, platform, architecture (process.arch), working directory, number of arguments received, and heap memory used in MB. Run it with and without extra arguments and check that the count changes.

Exercise 2: maximum price filter

Extend src/catalog.js to accept a second filter: --max=2000 should show only the events that have at least one session whose priceCents is less than or equal to that value. Requirements:

  • It must combine with the venue filter: node src/catalog.js "Sala Boveda" --max=1600.
  • If the value of --max is not a valid number, write a message through console.error and finish with exit code 2.

Exercise 3: occupancy report with an exit code

Create src/capacity-alert.js that walks the catalog and lists through console.log every session with fewer than 20% of its tickets sold (the ones that need promoting). Rules:

  • Format of each line: evt-002 | ses-002-3 | 17/10/2026 21:30 | 10% sold.
  • If no session is in that situation, write No sessions at risk and finish with code 0.
  • If there is at least one, finish with exit code 1, so that an alerting system can detect it.

Solutions

Solution 1

// src/environment.js
// Runtime environment diagnosis, presented as a table.

const memory = process.memoryUsage();
const args = process.argv.slice(2);

const data = [
  { Item: 'Node version',  Value: process.version },
  { Item: 'Platform',      Value: process.platform },
  { Item: 'Architecture',  Value: process.arch },
  { Item: 'Directory',     Value: process.cwd() },
  { Item: 'Arguments',     Value: args.length },
  { Item: 'Heap used (MB)', Value: Math.round(memory.heapUsed / 1024 / 1024) }
];

console.table(data);
node src/environment.js
node src/environment.js one two three    # The "Arguments" row now reads 3

Solution 2

// --- Maximum price filter ---

// We look for an argument shaped like --max=NNNN
const maxArg = args.find((arg) => arg.startsWith('--max='));

if (maxArg) {
  // Everything after the "=" sign is the value.
  const rawValue = maxArg.split('=')[1];
  const maxPrice = Number(rawValue);

  // Number('abc') returns NaN; Number('') returns 0, which is no good here either.
  if (!Number.isInteger(maxPrice) || maxPrice <= 0) {
    console.error(`Error: invalid value in --max ("${rawValue}").`);
    console.error('Usage: node src/catalog.js [venue] [--max=CENTS] [--table]');
    process.exit(2);   // 2 = incorrect usage
  }

  displayedEvents = displayedEvents.filter((event) =>
    // some() returns true if AT LEAST one session meets the condition.
    event.sessions.some((session) => session.priceCents <= maxPrice)
  );
}

This block must be placed after the venue filter, so that the two combine: each filter narrows down the result of the previous one.

node src/catalog.js --max=2000           # Noche de Monologos (1800 and 1500)
node src/catalog.js "Sala Boveda" --max=1600   # Only because of the 1500 session
node src/catalog.js --max=abc            # Error, exit code 2

Solution 3

// src/capacity-alert.js
// Lists the sessions with low sales so promotional action can be taken.

const catalog = require('./catalog-data.js'); // (see the note at the end)

const PERCENT_THRESHOLD = 20;
const atRisk = [];

for (const event of catalog) {
  for (const session of event.sessions) {
    const percentage = Math.round((session.sold / session.capacity) * 100);

    if (percentage < PERCENT_THRESHOLD) {
      atRisk.push({ event, session, percentage });
    }
  }
}

if (atRisk.length === 0) {
  console.log('No sessions at risk');
  process.exitCode = 0;
} else {
  for (const item of atRisk) {
    console.log(
      `${item.event.id} | ${item.session.id} | ` +
      `${formatDate(item.session.dateTime)} | ${item.percentage}% sold`
    );
  }
  // A non-zero code so that an alerting system picks it up.
  process.exitCode = 1;
}

Expected output with our data:

evt-002 | ses-002-3 | 17/10/2026 21:30 | 10% sold

A note on the require in the first line: for this second script to use the same data, we would have to export it from a shared file. We have not seen how to do that yet — it is the subject of CommonJS Modules and require() — so for now copy the catalog array and the formatDate function into src/capacity-alert.js. The fact that this duplication feels uncomfortable is precisely why modules exist.

Conclusion

You have written and run your first real Node.js program. Along the way you have seen that JavaScript on the server has no window and no document, but it does have process, the object that connects you to the operating system: arguments, environment, working directory, memory and exit code.

You have built src/catalog.js with Escena Viva's three founding events — the Concierto de Otoño at the Teatro Almendra, the Noche de Monólogos at the Sala Bóveda and the Festival de Jazz de Primavera at the Auditorio Ribera — formatted it with template literals and console.table, filtered it with process.argv and learned to keep data (stdout) apart from diagnostics (stderr), closing with a coherent exit code. You also know how to read a SyntaxError without panicking: file, line, token, and then look upwards.

You have done all of this by editing a file, saving it and running it again. There is a far quicker way to try out isolated ideas — an expression, a transformation of the catalog, a doubt about how a method behaves — without that save-and-run cycle. In the next lesson, The Node.js REPL, you will learn to use the interactive interpreter to explore Escena Viva's data live and try transformations out before writing them into the script.

Node.js Course: From Beginner to Advanced

Module 1: Introduction to Node.js

Module 2: Core Concepts

Module 3: File System and I/O

Module 4: HTTP and Web Servers

Module 5: NPM and Package Management

Module 6: The Express.js Framework

Module 7: Databases and ORMs

Module 8: Authentication and Authorization

Module 9: Testing and Debugging

Module 10: Advanced Topics

Module 11: Deployment and DevOps

Module 12: Real-World Projects

© Copyright 2026. All rights reserved