Skip to content
JavaScript Basics: Model Data with Classes

JavaScript Basics: Model Data with Classes

October 17, 2026

A class groups data with methods that work on it. Start with a public field, then use private fields when changes should go through your own methods.

These examples use built-in features and were tested on Linux with Node.js v24.19.0. No packages are needed. Assertions check the results before the program finishes.

Prepare a scratch directory

You need Bash, a text editor, Node.js on your PATH, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. These commands stop the shell if setup fails. Save each named source file as UTF-8; .mjs selects ES modules.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Create objects with a class

Save this as reading-task.mjs:

import assert from 'node:assert/strict';

class ReadingTask {
  completed = false;

  constructor(title) {
    this.title = title;
  }

  finish() {
    this.completed = true;
  }
}

const firstTask = new ReadingTask('Read chapter one');
const secondTask = new ReadingTask('Review the examples');
firstTask.finish();

assert.equal(firstTask.completed, true);
assert.equal(secondTask.completed, false);
assert.equal(firstTask.finish, secondTask.finish);
console.log(`${firstTask.title}: ${firstTask.completed}`);
console.log(`${secondTask.title}: ${secondTask.completed}`);

Run it:

node reading-task.mjs

Output:

Read chapter one: true
Review the examples: false

new creates an instance and runs its constructor. Each task has its own fields, while the finish method is shared through the class prototype. In firstTask.finish(), this refers to firstTask. Both fields are public, so other code can change them directly. See the class reference.

2. Keep a field private

Save this as private-score.mjs:

import assert from 'node:assert/strict';

class ScoreCard {
  #points = 0;

  awardPoint() {
    this.#points += 1;
  }

  readPoints() {
    return this.#points;
  }
}

const firstCard = new ScoreCard();
const secondCard = new ScoreCard();
firstCard.awardPoint();
firstCard.awardPoint();
firstCard.points = 99;

assert.equal(firstCard.readPoints(), 2);
assert.equal(secondCard.readPoints(), 0);
assert.equal(firstCard.points, 99);
console.log(`Private points: ${firstCard.readPoints()}`);
console.log(`Other card: ${secondCard.readPoints()}`);
console.log(`Separate public property: ${firstCard.points}`);

Run it:

node private-score.mjs

Output:

Private points: 2
Other card: 0
Separate public property: 99

The #points declaration creates a private field. It is distinct from the public points property. Writing firstCard.#points outside the class would be a syntax error in this file. A private field controls access through JavaScript syntax; it is not encryption for secrets.

3. Validate changes before storing them

Save this as stock-item.mjs:

import assert from 'node:assert/strict';

class StockItem {
  #quantity = 0;

  constructor(quantity) {
    this.setQuantity(quantity);
  }

  get quantity() {
    return this.#quantity;
  }

  setQuantity(quantity) {
    if (!Number.isSafeInteger(quantity) || quantity < 0) {
      throw new RangeError('Quantity must be a nonnegative safe integer');
    }
    this.#quantity = quantity;
  }
}

const pencils = new StockItem(12);
pencils.setQuantity(9);
assert.equal(pencils.quantity, 9);
assert.throws(() => pencils.setQuantity(-1), RangeError);
assert.throws(() => pencils.setQuantity(1.5), RangeError);
assert.throws(() => new StockItem('12'), RangeError);
assert.equal(pencils.quantity, 9);
console.log(`Quantity after valid change: ${pencils.quantity}`);
console.log('Invalid quantities rejected; quantity is still 9');

Run it:

node stock-item.mjs

Output:

Quantity after valid change: 9
Invalid quantities rejected; quantity is still 9

The getter makes pencils.quantity readable without a method call. Changes use setQuantity(), which checks the value before assigning the private field. The constructor uses the same check. A rejected update leaves the previous value intact. Private fields do not validate data by themselves; the method supplies that rule.

Clean up

In the same terminal, remove only the example files, return to your original directory, and remove the empty scratch directory:

rm -- reading-task.mjs private-score.mjs stock-item.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on