Technical courses usually fail for the same reason: each lesson teaches an isolated API with a toy example, and by the end you know how to write twenty fragments you have never seen fit together. This course works the other way round. Everything you learn from here on will build a single real application, module by module, until it reaches production as an API.

That application is Escena Viva. You have already seen its data in the previous lessons; now we are going to formalize it: who it is, who it serves, what its domain model is, what conventions it adopts and — most importantly — which part of it you will build in each of the twelve modules. By the end of this lesson you will have the project skeleton created, the seed file data/events.json written, and a clear map of the destination.

Contents

  1. The business context
  2. The actors and what each of them needs
  3. The domain model
  4. The seed file data/events.json
  5. The repository folder structure
  6. Road map: what each module builds
  7. Project conventions
  8. A note on fictional data

  1. The Business Context

Escena Viva is a small company that handles ticket sales for three cultural spaces in a medium-sized city:

Venue Capacity Programming profile
Teatro Almendra 420 seats Concerts, studio theater, a stable program
Sala Bóveda 120 seats Small formats: stand-up, singer-songwriters, micro-theater
Auditorio Ribera 900 seats Large events and festivals

Until now, each venue sold its own tickets: one with a spreadsheet, another over the phone, and the third with a legacy system nobody knows how to maintain. Escena Viva exists to unify all of it in a single platform.

The three events we start with — the ones you already know — are:

  • Concierto de Otoño (Teatro Almendra), 2 sessions in October 2026.
  • Noche de Monólogos (Sala Bóveda), 3 sessions in October 2026.
  • Festival de Jazz de Primavera (Auditorio Ribera), 2 sessions in April 2027.

The requirements that drive the project's technical decisions are these:

  1. Brutal, very short concurrency spikes. When a festival goes on sale, thousands of people arrive at once for ten minutes. For the rest of the month the traffic is modest.
  2. Capacity cannot be oversold. That is the hard requirement: two people cannot buy the same seat.
  3. Prices are real money. They tolerate no rounding errors.
  4. We have to integrate with third parties: a payment gateway, an email service, and in the future a code scanner at the door.
  5. The team is small. Two people develop and maintain everything, so simplicity and reusing knowledge between client and server have real value.

Points 1, 4 and 5 are exactly why, in the first lesson, we concluded that Node.js is a good fit for this business.

  1. The Actors and What Each of Them Needs

flowchart LR
    A["Attendee<br/>(buys tickets)"] --> P["Escena Viva<br/>platform"]
    O["Organizer<br/>(programs events)"] --> P
    D["Administrator<br/>(operates the system)"] --> P
    P --> PG["Payment gateway"]
    P --> CO["Email service"]
    P --> BD[("Database<br/>escena_viva")]
Actor Who they are What they need to do
Attendee The public who buy Browse the catalog, see availability, buy tickets, retrieve their order, download their tickets
Organizer The person responsible for a venue Create and edit their events and sessions, set capacity and price, review sales for their events
Administrator The Escena Viva team See every sale, generate reports, manage users and organizers, cancel orders

This separation is not decorative: it is what will justify the role-based access control in Module 8. An organizer at the Teatro Almendra must not be able to see the Sala Bóveda's revenue.

  1. The Domain Model

These are the system's entities. The names in the "identifier" column are the ones we will use literally in the code, in English and in plain ASCII.

Entity Identifier What it represents Main attributes
Event event A scheduled show id, title, venue, organizer, category, durationMinutes, description
Session session One specific showing of an event at a date and time id, eventId, dateTime, capacity, sold, priceCents
Ticket ticket An individual right of admission code, sessionId, orderId, status
Order order One purchase: several tickets from one or more sessions id, userId, date, lines, totalCents, status
User user An account on the platform id, email, name, role, passwordHash
Organizer organizer Whoever programs events at a venue id, name, venues, contact
Venue venue The physical space id, name, maxCapacity, address
Capacity capacity A session's capacity (not a table of its own) An integer inside session
Price priceCents An amount in cents (not a table of its own) An integer inside session
Catalog catalog The collection of published events An array of event

And this is how they relate:

erDiagram
    VENUE ||--o{ EVENT : "hosts"
    ORGANIZER ||--o{ EVENT : "programs"
    EVENT ||--|{ SESSION : "has"
    SESSION ||--o{ TICKET : "generates"
    ORDER ||--|{ TICKET : "groups"
    USER ||--o{ ORDER : "places"
    USER }o--|| ORGANIZER : "may be"

    VENUE {
        string id
        string name
        int maxCapacity
    }
    EVENT {
        string id
        string title
        string venue
        string category
        int durationMinutes
    }
    SESSION {
        string id
        string dateTime
        int capacity
        int sold
        int priceCents
    }
    TICKET {
        string code
        string sessionId
        string status
    }
    ORDER {
        string id
        string date
        int totalCents
        string status
    }
    USER {
        string id
        string email
        string role
    }

Three modeling decisions worth understanding right away:

  • Capacity lives on the session, not on the event. The Teatro Almendra has 420 seats, but a particular session may only put 380 on sale if there is a press area held back. The venue supplies the maximum; the session, the reality.
  • A ticket is individual and has its own code. An order of 4 tickets generates 4 ticket records, each with its own unique code, because each one is validated separately at the door.
  • sold is a denormalized counter on the session. Strictly it could be worked out by counting tickets, but showing availability in the catalog has to be instantaneous. In Module 7 we will see how to keep that counter consistent using transactions.

Statuses

Entity Possible statuses Notes
order pending → paid → issued, or cancelled pending holds the capacity for 10 minutes
ticket valid → used, or cancelled used is set by the scanner at the door
event draft → published → finished Only published ones appear in the public catalog

  1. The Seed File data/events.json

Until Module 7, when the escena_viva database arrives, this file is the data source for the whole application. Create data/events.json with exactly this content:

[
  {
    "id": "evt-001",
    "title": "Concierto de Otono",
    "venue": "Teatro Almendra",
    "organizer": "org-almendra",
    "category": "concert",
    "durationMinutes": 95,
    "status": "published",
    "description": "Chamber symphonic repertoire to open the season.",
    "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",
    "organizer": "org-boveda",
    "category": "comedy",
    "durationMinutes": 80,
    "status": "published",
    "description": "Four comedians, a short format and the audience right up close.",
    "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",
    "organizer": "org-ribera",
    "category": "festival",
    "durationMinutes": 240,
    "status": "published",
    "description": "Three stages and twelve line-ups across two days.",
    "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
      }
    ]
  }
]

A field-by-field commentary

JSON does not allow comments, so here they are instead:

Field Type Why it is like this
id (event) "evt-NNN" A readable prefix: reading a record tells you what it is without checking the context
title text What the public sees
venue text For now the name; in Module 7 it becomes a reference to the venue table
organizer "org-xxx" A reference to the organizer; it underpins the role control of Module 8
category text Used to filter and group the catalog
durationMinutes integer In minutes, never as text like "1h 35min": data gets calculated, not read
status text draft, published or finished
description text Short marketing copy
sessions array Nested inside the event: it is the natural shape of a JSON document and the one we will use with MongoDB
sessions[].id "ses-NNN-M" It includes the event number: readable and traceable at a glance
dateTime ISO 8601 A YYYY-MM-DDTHH:mm:ss string: sortable as text and stable through JSON
capacity integer Seats on sale for this session
sold integer A counter of tickets already sold
priceCents integer 2500 = 25.00 EUR. Never decimals

Check that the file is valid JSON before moving on:

node -p "require('./data/events.json').length"
# 3

node -e "require('./data/events.json').forEach(e => console.log(e.id, '-', e.title))"
# evt-001 - Concierto de Otono
# evt-002 - Noche de Monologos
# evt-003 - Festival de Jazz de Primavera

If the command fails with SyntaxError, there is an extra comma, an unclosed quote, or you have used single quotes. In JSON every key and every string goes in double quotes, and a trailing comma after the last element is not allowed.

We still do not know how to read this file from code with fs — that is Module 3 — but it is already in place and already the reference. From there on, src/catalog-data.js will stop carrying the data hard-coded inside it.

  1. The Repository Folder Structure

This is the target structure, the one the project will have at the end of the course. Do not create all of it now: it will grow with each module.

escena-viva/
├── .env                     # Local secrets (NEVER in the repository) - Module 11
├── .gitignore
├── .nvmrc                   # The project's Node version
├── package.json             # Metadata and dependencies - Module 5
├── data/
│   ├── events.json          # The catalog seed
│   └── sales.csv            # Sales history for reports - Module 3
├── reports/                 # Generated output (ignored by git)
├── src/
│   ├── catalog.js           # Entry point for the console catalog
│   ├── catalog-data.js      # Access to the catalog data
│   ├── domain/              # Event, Session, Order classes - Module 2
│   ├── server/              # HTTP server and routes - Modules 4 and 6
│   ├── middleware/          # Express middleware - Module 6
│   ├── models/              # Database models - Module 7
│   ├── auth/                # Authentication and roles - Module 8
│   └── utils/               # Formatting, validation, helpers
└── tests/                   # Automated tests - Module 9

Organizing principles we will follow:

  • One responsibility per folder. If you do not know where to put a file, it probably does two things.
  • src/ holds only code; data/ only data; reports/ only generated results. Generated results are never version-controlled.
  • File names in lower case with hyphens (catalog-data.js), never with spaces or capitals. Linux distinguishes upper from lower case even when your laptop does not, and that has broken more deployments than anyone can count.

  1. Road Map: What Each Module Builds

This table is the map of the course. Keep it: every time you start a module you will know which piece of Escena Viva you are adding.

Module Topic Delivery in Escena Viva
1 Introduction The environment installed, the project skeleton, the console catalog, the data/events.json seed
2 Core concepts Domain classes (Event, Session) split into modules; asynchronous loading of the catalog; an EventEmitter that raises the alarm when a session sells out
3 File system and I/O Really reading data/events.json with fs; generating reports in reports/; processing data/sales.csv with streams without loading it into memory
4 HTTP and web servers Our first server: GET /events, GET /events/:id, creating orders with a JSON POST, and consuming an external exchange-rate API
5 NPM The project's package.json, dependencies, the npm start and npm run report scripts, and publishing our own formatting utility package
6 Express Migrating the server to Express: catalog and order routes, logging middleware, purchase validation and centralized error handling
7 Databases Replacing the JSON with the escena_viva database: models, CRUD for events and sessions, availability queries, and transactions so capacity is never oversold
8 Authentication Attendee registration and sign-in, password hashing, JWT sessions, and roles: attendee, organizer and administrator
9 Testing and debugging Unit tests for the capacity and price calculations, integration tests for the purchase API, coverage, and debugging a real failure
10 Advanced topics Surviving the on-sale spike: cluster, PDF generation in worker threads, catalog caching and an email queue with Redis, and a well-designed REST API
11 Deployment and DevOps Per-environment configuration, structured logging, PM2, a Docker image of the platform, deployment and a continuous integration pipeline
12 Real-world projects Extensions on what we have learned, and the wrap-up: from project to production
flowchart LR
    M1["M1-2<br/>In-memory data<br/>and console"] --> M3["M3<br/>JSON and CSV<br/>files"]
    M3 --> M4["M4-6<br/>HTTP API<br/>and Express"]
    M4 --> M7["M7-8<br/>Database<br/>and users"]
    M7 --> M9["M9-10<br/>Testing and<br/>performance"]
    M9 --> M11["M11-12<br/>Deployment<br/>and production"]

  1. Project Conventions

These rules apply to all the code in the course. Written down once here, we will not debate them again.

7.1 Identifiers in English

Variables, functions, classes, properties and file names are written in English, with no accents or non-ASCII characters: priceCents, availableTickets, calculateOccupancy, catalog-data.js.

The exceptions are the ones the environment imposes: language keywords (const, class, return), Node and browser APIs (readFile, createServer, map), and package names (express, mongoose).

Why no accented characters? Because an identifier such as sesión is valid JavaScript but turns into a source of bugs the moment it travels through a URL, a file name or an HTTP header. The same applies to our data: the venue and event names keep their Spanish spelling but drop the accents (Sala Boveda, Concierto de Otono).

Style: camelCase for variables and functions, PascalCase for classes, UPPERCASE_WITH_UNDERSCORES for global constants.

7.2 Dates in ISO 8601 format

Every date is stored as a YYYY-MM-DDTHH:mm:ss string.

const dateTime = '2026-10-03T20:00:00';   // Good
const date = '03/10/2026 20:00';          // Bad: ambiguous and not sortable

Three reasons:

  1. It sorts correctly as text, with no conversion to Date.
  2. It is unambiguous: 03/10/2026 is 3 October in Spain and 10 March in the United States.
  3. It survives the trip through JSON. A Date object becomes text when serialized and does not come back as a Date when deserialized; if you store text already, there are no surprises.

Formatting it as 03/10/2026 20:00 is the presentation layer's job, never storage's.

7.3 Prices as whole cents

Every monetary amount is stored as a whole number of cents. 2500 means 25.00 EUR.

console.log(0.1 + 0.2);              // 0.30000000000000004
console.log(19.99 * 3);              // 59.97000000000001

console.log(1999 * 3);               // 5997  cents, exact
console.log((5997 / 100).toFixed(2)); // '59.97'

JavaScript's number type is a double-precision float (IEEE 754) and cannot represent 0.1 exactly. With money, that error accumulates and ends up as an accounting discrepancy. Working with integers the problem disappears: you only divide by 100 at the very last moment, in order to display.

Naming convention: every monetary field carries the suffix Cents (priceCents, totalCents, discountCents). That way confusion is impossible.

7.4 Ticket codes

Every ticket has a code in this format:

EV-2026-000123
│  │    └── 6-digit sequence, zero-padded on the left
│  └─────── Year of issue
└────────── Escena Viva prefix
function generateTicketCode(year, sequence) {
  // padStart pads with zeros up to 6 characters: 123 -> '000123'
  return `EV-${year}-${String(sequence).padStart(6, '0')}`;
}

console.log(generateTicketCode(2026, 123));   // EV-2026-000123

Why this format rather than a long random identifier?

  • It can be read out over the phone. Someone can dictate it to customer service without mistakes.
  • It is sortable and its year is visible at a glance.
  • It is short enough to print under a barcode.

The trade-off is that it is predictable: if you know one code you can guess the next. That is why the code is never proof of ownership; validation at the door also checks the ticket's status in the database. It is a conscious decision, of the kind you take constantly on a real project.

7.5 Other conventions

Convention Rule
File encoding UTF-8 without BOM
Line endings \n (LF), on Windows too
Indentation 2 spaces
Quotes Single in JavaScript, double mandatory in JSON
Semicolons Yes, always
Language of the comments English
Format of JSON files JSON.stringify(data, null, 2)
Data on stdout, diagnostics on stderr Always

  1. A Note on Fictional Data

All the data in this course is made up. The venues, the events, the organizers, the email addresses and the orders that will appear from Module 7 onwards correspond to no real people or companies.

This is not a formality: it is a professional practice you should carry over into your own work.

  • Never use real customer data in development, in testing or in demos. Not even "just this once", not even a copy of the production database on your laptop.
  • Development databases are populated with synthetic data, generated or anonymized. In Module 7 we will see how to write seeds for exactly that.
  • Personal data is regulated. Names, email addresses, phone numbers and payment details carry legal obligations around processing and minimization.
  • Never push secrets to the repository: payment gateway keys, database passwords or tokens. They go in environment variables (Module 11), and that is why .gitignore has included .env since the very first lesson.

When we register the user [email protected] in Module 8, notice the .test domain: it is reserved by standard for testing and will never correspond to a real mailbox.

Common Mistakes and Tips

Mistake 1: storing prices as decimals "because it is more convenient". It is, for about two weeks. Then comes the first one-cent discrepancy in a report covering 3,000 orders, and there is no way to work out where it came from.

Mistake 2: using local date formats in the data. 03/10/2026 forces you to know who wrote it in order to interpret it. ISO 8601 in storage, local format only for display.

Mistake 3: putting capacity on the event instead of on the session. It works until the first session with reduced capacity, and then you have to migrate data.

Mistake 4: leaving the JSON file as one giant line. Every change shows up in version control as "the whole line modified" and reviews become impossible. null, 2 always.

Mistake 5: mixing languages in identifiers. getEventoById, listarEvents, precioPrice. Pick one — English in this course — and be systematic.

Tip 1: go back to the road map at the start of each module. Knowing which piece you are building completely changes the way you study an API.

Tip 2: use git from day one. A git commit at the end of each lesson gives you a point of return and a history of your own learning.

Tip 3: do not run ahead. It is tempting to set up the Express server right now. Each module introduces the concepts in a deliberate order; skipping it usually ends in code that works without you understanding why.

Tip 4: when in doubt, look at the domain model. A good share of coding decisions answer themselves once you look at the entity table.

Exercises

Exercise 1: prepare the project skeleton

Leave the project ready for Module 2. It must satisfy:

  1. The escena-viva/ folder contains src/, data/ and reports/.
  2. data/events.json exists with the three events and is valid JSON.
  3. reports/ is under version control even though it is empty (hint: .gitkeep).
  4. .gitignore excludes node_modules/, .env, *.log and the generated contents of reports/ without excluding the folder.
  5. .nvmrc contains the LTS version you installed.
  6. There is a README.md with the project name, the three events and the commands to get started.
  7. A first git commit with all of the above.

Write out the full sequence of commands and the contents of each file.

Exercise 2: extend the seed with a fourth event

Add a fourth event to data/events.json with these characteristics:

  • Title: Cuentos al Anochecer
  • Venue: Teatro Almendra (organizer org-almendra)
  • Category: family, duration 55 minutes, status published
  • Three sessions on Saturdays 7, 14 and 21 November 2026 at 18:00
  • Capacity 200 in each session (the Teatro Almendra holds back part of the stalls for this format)
  • Tickets sold: 200, 145 and 30 respectively
  • Price: 1200 cents for the first two and 900 for the third

Respect the project's id, date and price formats. Then verify with node -p commands that:

  1. The file is still valid JSON and has 4 events.
  2. The catalog now has 10 sessions.
  3. The total capacity of the Teatro Almendra (adding up all its events) is correct.
  4. There is exactly one sold-out session.

Exercise 3: apply the conventions

A colleague has written this snippet. Rewrite it respecting all the project conventions and explain each change.

var Sesiones = [
  {ID: "S1", fecha: "10/10/2026 21:30", aforo: 120, vendidas: 118, precio: 18.00},
  {ID: "S2", fecha: "11/10/2026 21:30", aforo: 120, vendidas: 45, precio: 18.00}
];

function getTotalRevenue(sesiones){
  var total = 0
  for(var i=0;i<sesiones.length;i++){
    total = total + (sesiones[i].vendidas * sesiones[i].precio)
  }
  return total
}

console.log("Revenue: " + getTotalRevenue(Sesiones))

Solutions

Solution 1

# 1. Folder structure
mkdir -p escena-viva/src escena-viva/data escena-viva/reports
cd escena-viva

# 3. Keep "reports" in the repository even while it is empty
touch reports/.gitkeep

# 5. The project's Node version
node -v > .nvmrc

.gitignore (point 4). The key is the ! exception, so the contents are ignored but the folder is kept:

node_modules/
.env
*.log

# We ignore what is generated in reports, but keep the folder
reports/*
!reports/.gitkeep

README.md (point 6):

# Escena Viva

Ticketing platform for cultural events at the Teatro Almendra,
the Sala Boveda and the Auditorio Ribera.

## Events on sale

- Concierto de Otono (Teatro Almendra)
- Noche de Monologos (Sala Boveda)
- Festival de Jazz de Primavera (Auditorio Ribera)

## Requirements

- Node.js: the version given in `.nvmrc`

## Getting started

    nvm use
    node src/catalog.js

## Structure

- `src/`     source code
- `data/`    starting data (events.json)
- `reports/` generated output (not version-controlled)

Verification and first commit (points 2 and 7):

node -p "require('./data/events.json').length"   # 3

git init
git add .
git commit -m "Escena Viva project skeleton with seed catalog"
git log --oneline

Solution 2

The block to insert at the end of the array in data/events.json (remember the comma after the previous event's closing brace):

  {
    "id": "evt-004",
    "title": "Cuentos al Anochecer",
    "venue": "Teatro Almendra",
    "organizer": "org-almendra",
    "category": "family",
    "durationMinutes": 55,
    "status": "published",
    "description": "Oral storytelling for family audiences as the evening falls.",
    "sessions": [
      {
        "id": "ses-004-1",
        "dateTime": "2026-11-07T18:00:00",
        "capacity": 200,
        "sold": 200,
        "priceCents": 1200
      },
      {
        "id": "ses-004-2",
        "dateTime": "2026-11-14T18:00:00",
        "capacity": 200,
        "sold": 145,
        "priceCents": 1200
      },
      {
        "id": "ses-004-3",
        "dateTime": "2026-11-21T18:00:00",
        "capacity": 200,
        "sold": 30,
        "priceCents": 900
      }
    ]
  }

Verifications:

# 1. Valid JSON and number of events
node -p "require('./data/events.json').length"
# 4

# 2. Total sessions
node -p "require('./data/events.json').reduce((t, e) => t + e.sessions.length, 0)"
# 10

# 3. Total capacity of the Teatro Almendra: 840 (evt-001) + 600 (evt-004) = 1440
node -p "require('./data/events.json').filter(e => e.venue === 'Teatro Almendra').flatMap(e => e.sessions).reduce((t, s) => t + s.capacity, 0)"
# 1440

# 4. Sold-out sessions
node -p "require('./data/events.json').flatMap(e => e.sessions).filter(s => s.sold >= s.capacity).map(s => s.id)"
# [ 'ses-004-1' ]

A note on point 4: ses-002-1 has 118 of 120 sold, so it is not sold out. The only sold-out one is the first session of the new event.

Solution 3

// src/utils/revenue.js
// Calculating the revenue of a set of sessions.

const 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 }
];

// Returns the total revenue in cents (an integer).
function calculateRevenueCents(sessions) {
  return sessions.reduce(
    (total, session) => total + session.sold * session.priceCents,
    0
  );
}

const revenueCents = calculateRevenueCents(sessions);

console.log(`Revenue: ${(revenueCents / 100).toFixed(2)} EUR`);
// Revenue: 2934.00 EUR

The changes applied and why:

Change Reason
var → const Block scope and no accidental reassignments
Sesiones → sessions Identifiers in English and camelCase (PascalCase is reserved for classes)
ID, fecha, aforo, vendidas, precio → id, dateTime, capacity, sold, priceCents Domain model names, in English
"S1" → 'ses-002-1' The project's identifier format, with a prefix and traceability
"10/10/2026 21:30" → '2026-10-10T21:30:00' ISO 8601: unambiguous and sortable as text
precio: 18.00 → priceCents: 1800 Money as whole cents; the suffix makes it explicit
getTotalRevenue → calculateRevenueCents English and carrying the suffix that states the unit returned
for loop with an index → reduce More declarative and with no mutable variable
Concatenation with + → a template literal Readability
Double quotes → single The project's JavaScript convention (double quotes are left for JSON)
Semicolons added Project convention
Comments in English added Project convention

Checking the calculation: 118 × 1800 + 45 × 1800 = 212,400 + 81,000 = 293,400 cents = 2,934.00 EUR. With decimal prices the result would have been the same in this particular case, but a price of 18.99 and a few hundred operations are enough for phantom cents to start showing up.

Conclusion

Escena Viva is no longer a backdrop: it is a project with a context, actors, a domain model, data and rules of its own. You have seen who uses the platform — attendee, organizer and administrator — and how that separation anticipates role-based control; you have formalized the entities event, session, ticket, order, user, organizer and venue, with their relationships and their statuses; you have written the seed file data/events.json that will be the data source until the database arrives; and you have settled the conventions that will not be debated again: identifiers in English with no accents, ISO 8601 dates, prices as whole cents, ticket codes like EV-2026-000123, JSON with null, 2 and always fictional data.

Above all, you have the road map. You know that in Module 3 the catalog will stop being hard-coded and start being read from disk, that in Module 4 it will surface over HTTP, that in Module 7 it will move into a database with transactions that make overselling impossible, and that in Module 10 you will learn to withstand the traffic spike of a festival going on sale.

That closes Module 1. You have the environment installed with an LTS version managed through .nvmrc, you know how to run and debug a script, you handle the REPL as a laboratory, you have command of the modern JavaScript we will be using, and the project is standing with its data seed in place.

What you still do not know is how Node.js works on the inside, and that is the missing piece for writing real server code. In Module 2: Core Concepts we open the box: Node's architecture, the event loop and its phases, callbacks, promises and async/await, events with EventEmitter, and the module system with require and module.exports. There, at last, the array of events hard-coded in src/catalog-data.js will become a reusable module, the Event and Session classes will be born in src/domain/, and the waiter who never waits from the first lesson will stop being an analogy and turn into the code you write.

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