Classes are common in Node services, Angular and many libraries, and TypeScript adds important checks on top of JS classes.
TypeScript classes are JavaScript classes plus declarations for their members.
Fields must be declared with a type. With strictPropertyInitialization (part of strict), each field must be initialised in its declaration or the constructor; name!: string (definite assignment) opts out when something else sets it.
Access modifiers:
public(the default),protected(the class and subclasses),private(only the class).- These are compile-time only —
privatefields are ordinary properties at runtime. For real runtime privacy use JavaScript's#fieldsyntax, which TypeScript fully supports. readonlyprevents reassignment after the constructor.
Parameter properties declare and assign a field in one go: constructor(private readonly repo: Repo) {}. It's concise and very common in Angular and NestJS code, but it is TypeScript-only syntax that emits code, so erasableSyntaxOnly and Node's type stripping reject it.
`implements` checks that a class satisfies an interface. It doesn't change the class's type or provide any implementation — it's purely a check, and the methods' parameter types are not inferred from the interface.
`abstract` classes can't be instantiated and may declare abstract methods that subclasses must implement. Use `override` on methods that replace a parent's method; with noImplicitOverride the compiler requires it, catching typos and renamed base methods.
Remember a class declaration creates both a value (the constructor) and a type (the instance shape). Because typing is structural, any object with the right members is assignable to the class type — unless the class has private or # members, which make it effectively nominal.
Example
interface Repository<T> { find(id: string): T | undefined }
abstract class Entity {
constructor(public readonly id: string) {} // parameter property
abstract describe(): string;
}
class User extends Entity {
#loginCount = 0; // runtime-private
protected role: "admin" | "member" = "member";
constructor(id: string, public name: string) { super(id); }
login(): number { return ++this.#loginCount; }
override describe(): string { return `${this.name} (${this.role})`; }
}
class MemoryRepo implements Repository<User> {
private items = new Map<string, User>();
add(user: User): void { this.items.set(user.id, user); }
find(id: string): User | undefined { return this.items.get(id); }
}
const repo = new MemoryRepo();
repo.add(new User("u1", "Ada"));
console.log(repo.find("u1")?.describe()); // "Ada (member)"Remember
- Declare fields with types; strict mode requires them to be initialised
private/protectedare compile-time;#privateis enforced at runtimeimplementsonly checks the class; it doesn't type its members for you- Use
override(withnoImplicitOverride) on overriding methods
Watch out for
- Expecting
privateto hide data at runtime (JSON.stringify and plain property access still see it).