<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Robin Viktorsson</title>
    <description>The latest articles on Forem by Robin Viktorsson (@robinviktorsson).</description>
    <link>https://forem.com/robinviktorsson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3027073%2Ff73802f5-8675-45f8-ac04-75efea0b5446.jpg</url>
      <title>Forem: Robin Viktorsson</title>
      <link>https://forem.com/robinviktorsson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/robinviktorsson"/>
    <language>en</language>
    <item>
      <title>🎮 Learn Design Patterns: Build a “Monster Arena” Game and Master TypeScript Design Patterns (Part 1)</title>
      <dc:creator>Robin Viktorsson</dc:creator>
      <pubDate>Fri, 01 Aug 2025 21:32:27 +0000</pubDate>
      <link>https://forem.com/robinviktorsson/learn-design-patterns-build-a-monster-arena-game-and-master-typescript-design-patterns-part-1-2fp5</link>
      <guid>https://forem.com/robinviktorsson/learn-design-patterns-build-a-monster-arena-game-and-master-typescript-design-patterns-part-1-2fp5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fedpwfks6hhjy34zpukfl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fedpwfks6hhjy34zpukfl.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was originally published on &lt;a href="https://medium.com/gitconnected/learn-design-patterns-build-a-monster-arena-game-and-master-typescript-design-patterns-part-b436fb61e7ab" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Design patterns aren’t just academic concepts — they’re practical tools that bring structure, clarity, and flexibility to your code. And what better way to learn them than by building a game?&lt;/p&gt;

&lt;p&gt;In this two-part series, we’re creating a &lt;strong&gt;Monster Battle Arena&lt;/strong&gt; in &lt;strong&gt;TypeScript&lt;/strong&gt; and &lt;strong&gt;Node.js&lt;/strong&gt;, where players summon goblins and dragons, assign them attack strategies, and trigger combat through an interactive terminal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1&lt;/strong&gt; focuses on implementing the core gameplay mechanics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A central GameManager to manage game state&lt;/li&gt;
&lt;li&gt;Adding players to the game&lt;/li&gt;
&lt;li&gt;Summoning different types of monsters&lt;/li&gt;
&lt;li&gt;Fighting monsters&lt;/li&gt;
&lt;li&gt;Logging events to a UI terminal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Along the way, you’ll learn how to apply essential software design patterns with clean, practical examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🧙‍♂ &lt;strong&gt;Singleton&lt;/strong&gt; — Centralized game state manager&lt;/li&gt;
&lt;li&gt;⚔️ &lt;strong&gt;Strategy&lt;/strong&gt; — Flexible monster attack behaviours&lt;/li&gt;
&lt;li&gt;👁️ &lt;strong&gt;Observer&lt;/strong&gt; — Real-time battle notifications&lt;/li&gt;
&lt;li&gt;🏭 &lt;strong&gt;Factory&lt;/strong&gt; — Dynamic monster creation&lt;/li&gt;
&lt;li&gt;🎮 &lt;strong&gt;Command&lt;/strong&gt; — Encapsulated terminal actions&lt;/li&gt;
&lt;li&gt;🔁 &lt;strong&gt;State&lt;/strong&gt; — Dynamic monster condition changes&lt;/li&gt;
&lt;li&gt;🔗 &lt;strong&gt;Chain of Responsibility&lt;/strong&gt; — Layered damage mitigation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part 2&lt;/strong&gt; will evolve the existing logic with richer mechanics, new design patterns, enhanced UI interactions, and persistence — turning this codebase into a fully modular game simulation.&lt;/p&gt;

&lt;p&gt;All gameplay is simulated via a terminal UI — proving that design patterns are not just powerful, but also &lt;em&gt;fun&lt;/em&gt; to learn.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Are Design Patterns? 🤔
&lt;/h2&gt;

&lt;p&gt;Design patterns are tried-and-true solutions to common software design problems. Rather than reinventing the wheel every time we structure code, design patterns offer &lt;strong&gt;reusable templates&lt;/strong&gt; that make your codebase more understandable, scalable, and maintainable.&lt;/p&gt;

&lt;p&gt;They’re not libraries or frameworks, but &lt;strong&gt;language-agnostic blueprints&lt;/strong&gt; that help you solve recurring architectural challenges — whether you’re building a backend service, a UI component, or, in our case, a game.&lt;/p&gt;

&lt;p&gt;Think of them as &lt;strong&gt;battle-tested strategies&lt;/strong&gt; used by experienced developers to communicate intent and reduce complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let’s Code the Monster Arena Game! 🛠️
&lt;/h2&gt;

&lt;p&gt;With the core concepts of our game idea and design patterns under our belt, it’s time to bring them to life. In this section, we’ll dive into coding the Monster Arena step by step — from setting up your environment to implementing gameplay features like player management, monster summoning, and combat logic.&lt;/p&gt;

&lt;p&gt;By following along, you’ll gain hands-on experience applying design patterns in a fun, practical, and interactive way.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prerequisites 🧱
&lt;/h3&gt;

&lt;p&gt;Ensure you have the following installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;npm (comes with Node.js)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Project Structure 📁
&lt;/h3&gt;

&lt;p&gt;Initialize a Node.js project and generate a &lt;code&gt;package.json&lt;/code&gt; file with default values using the &lt;code&gt;npm init -y&lt;/code&gt; command. Create necessary directories and files. The final structure should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/monster-arena-game
├── node_modules/
├── src/                        
│   ├── core/  
│   │   ├── BattleSubject.ts                  
│   │   └── GameManager.ts            
│   ├── combat/                 
│   │   ├── AttackStrategy.ts        
│   │   └── DamageHandler.ts        
│   ├── logs/                          
│   │   └── ConsoleLogger.ts       
│   ├── factories/               
│   │   └── MonsterFactory.ts    
│   ├── ui/               
│   │   └── Terminal.ts  
│   └── commands/ 
│       ├── Command.ts
│       ├── AddPlayerCommand.ts
│       ├── SummonMonsterCommand.ts
│       ├── ShowPlayersCommand.ts
│       ├── ShowLogsCommand.ts                     
│       └── AttackMonsterCommand.ts           
├── package.json                
├── package-lock.json           
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Install Dependencies 📦
&lt;/h3&gt;

&lt;p&gt;Let’s set up the project with the necessary tools for a smooth TypeScript and Node.js development experience.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Install TypeScript and related packages
npm install typescript ts-node @types/node

// Initialize the TypeScript configuration
npx tsc --init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a start script to your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "start": "ts-node src/index.ts"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you run your game with a simple command: &lt;code&gt;npm run start&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Singleton Pattern — Global Game Manager 🧙‍♂️
&lt;/h3&gt;

&lt;p&gt;We begin with the &lt;strong&gt;Singleton Pattern&lt;/strong&gt;, which ensures that a class has &lt;strong&gt;only one instance&lt;/strong&gt; and provides a global point of access to it.&lt;/p&gt;

&lt;p&gt;In our game, we use a &lt;code&gt;GameManager&lt;/code&gt; to control player registration and manage shared game-wide state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/core/GameManager.ts
import { Monster } from './Monster';

export class GameManager {
  private static instance: GameManager;
  private players: string[] = [];
  private monsters: Map&amp;lt;string, Monster&amp;gt; = new Map();

  private constructor() {}

  static getInstance(): GameManager {
    if (!GameManager.instance) {
      GameManager.instance = new GameManager();
    }
    return GameManager.instance;
  }

  addPlayer(name: string) {
    this.players.push(name);
    console.log(`[GameManager] ${name} joined the arena.`);
  }

  getPlayers(): string[] {
    return this.players;
  }

  addMonster(monster: Monster) {
    this.monsters.set(monster.name.toLowerCase(), monster);
    console.log(`[GameManager] ${monster.name} has entered the battlefield.`);
  }

  getMonster(name: string): Monster | undefined {
    return this.monsters.get(name.toLowerCase());
  }

  getMonsters(): string[] {
    return Array.from(this.monsters.keys());
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Singleton pattern enforces a single source of truth for global game state — ideal for managing sessions, player data, and coordination logic.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Strategy Pattern — Monster Behaviours ⚔️
&lt;/h3&gt;

&lt;p&gt;Monsters in our game attack in different ways — some slash with claws, others hurl fireballs. Instead of hardcoding behaviour, we define them as interchangeable strategies using the &lt;strong&gt;Strategy Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This pattern enables behaviour to be selected at runtime, keeping monster logic clean and flexible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/combat/AttackStrategy.ts
export interface AttackStrategy {
  attack(monsterName: string): void;
}

export class MeleeAttack implements AttackStrategy {
  attack(name: string) {
    console.log(`[Strategy] ${name} performs a claw slash!`);
  }
}

export class RangedAttack implements AttackStrategy {
  attack(name: string) {
    console.log(`[Strategy] ${name} hurls a fireball!`);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Strategy pattern lets monsters &lt;strong&gt;dynamically switch&lt;/strong&gt; their attack style (e.g., from melee to ranged) without altering their core structure.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Observer Pattern — Broadcast Battle Updates 👁️
&lt;/h3&gt;

&lt;p&gt;During combat, we want to keep different parts of the system — like logs or future UI components — in sync with what’s happening. The &lt;strong&gt;Observer Pattern&lt;/strong&gt; lets us broadcast important events to multiple listeners without tightly coupling them to the source.&lt;/p&gt;

&lt;p&gt;Here’s how we implement it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/core/BattleSubject.ts
export interface Observer {
  update(event: string): void;
}

export class BattleSubject {
  private observers: Observer[] = [];

  subscribe(observer: Observer) {
    this.observers.push(observer);
  }

  notify(event: string) {
    for (const obs of this.observers) obs.update(event);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here’s a simple observer that logs updates to the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/logs/ConsoleLogger.ts
import { Observer } from '../core/BattleSubject';

export class ConsoleLogger implements Observer {
  update(event: string): void {
    console.log(`[Observer] ${event}`);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Observer Pattern decouples the &lt;strong&gt;event source&lt;/strong&gt; (e.g., monsters, combat actions) from the &lt;strong&gt;event handlers&lt;/strong&gt; (e.g., console logs, UI panels). This makes the system more modular and extensible — you can add more observers (like a file logger, WebSocket publisher, or in-game notification panel) without changing the battle logic.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Monster Class With Strategy and State 🧬
&lt;/h3&gt;

&lt;p&gt;Our &lt;code&gt;Monster&lt;/code&gt; class leverages both the &lt;strong&gt;Strategy&lt;/strong&gt; and &lt;strong&gt;State&lt;/strong&gt; patterns. It uses Strategy to handle different attack styles and State to reflect changing health conditions (like being healthy, wounded, or critical).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/core/Monster.ts
import { AttackStrategy } from '../combat/AttackStrategy';
import { MonsterState, HealthyState } from './MonsterState';

export class Monster {
  private state: MonsterState = new HealthyState();

  constructor(
    public name: string,
    private strategy: AttackStrategy
  ) {}

  performAttack() {
    this.strategy.attack(this.name);
    this.state.handleState(this.name);
  }

  setAttackStrategy(strategy: AttackStrategy) {
    this.strategy = strategy;
  }

  setState(state: MonsterState) {
    this.state = state;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows the power of composition over conditionals. We can change both &lt;strong&gt;attack behaviour&lt;/strong&gt; and &lt;strong&gt;state-based responses&lt;/strong&gt; cleanly and independently.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Factory Pattern — Summon Monsters Easily 🏭
&lt;/h3&gt;

&lt;p&gt;Instead of manually creating monsters with specific configurations, we use the &lt;strong&gt;Factory Pattern&lt;/strong&gt; to encapsulate creation logic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/factories/MonsterFactory.ts
import { Monster } from '../core/Monster';
import { MeleeAttack, RangedAttack } from '../combat/AttackStrategy';

export class MonsterFactory {
  static create(type: 'goblin' | 'dragon'): Monster {
    switch (type) {
      case 'goblin': return new Monster('Goblin', new MeleeAttack());
      case 'dragon': return new Monster('Dragon', new RangedAttack());
      default: throw new Error('Unknown monster type');
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Factory pattern centralizes and abstracts the &lt;strong&gt;creation of complex objects&lt;/strong&gt;. Need more monster types later? Just add a new case.&lt;/p&gt;




&lt;h3&gt;
  
  
  9. Command Pattern — Queue and Execute Attacks 🎮
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Command Pattern&lt;/strong&gt; encapsulates requests as objects — perfect for queuing, logging, or undoing actions. Instead of invoking &lt;code&gt;.performAttack()&lt;/code&gt; directly, we wrap it in a command object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/commands/AttackMonsterCommand.ts
import { Command } from './Command';
import { GameManager } from '../core/GameManager';

export class AttackMonsterCommand implements Command {
  constructor(private name: string) {}

  execute(): void {
    const monster = GameManager.getInstance().getMonster(this.name);
    if (monster) {
      monster.performAttack();
    } else {
      console.log(`[Error] Monster "${this.name}" not found.`);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Command pattern separates &lt;strong&gt;what should happen&lt;/strong&gt; from &lt;strong&gt;when it happens&lt;/strong&gt; — ideal for turn-based combat, replays, or action batching.&lt;/p&gt;




&lt;h3&gt;
  
  
  10. State Pattern — Monster Condition Handling 🔁
&lt;/h3&gt;

&lt;p&gt;Monsters behave differently based on how injured they are. Instead of using conditionals everywhere, we apply the &lt;strong&gt;State Pattern&lt;/strong&gt; to encapsulate each behaviour.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/core/MonsterState.ts
export interface MonsterState {
  handleState(name: string): void;
}

export class HealthyState implements MonsterState {
  handleState(name: string) {
    console.log(`[State] ${name} is strong and confident.`);
  }
}

export class WoundedState implements MonsterState {
  handleState(name: string) {
    console.log(`[State] ${name} is hurt but fighting.`);
  }
}

export class CriticalState implements MonsterState {
  handleState(name: string) {
    console.log(`[State] ${name} is barely alive.`);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The State pattern eliminates complex &lt;code&gt;if-else&lt;/code&gt; chains and allows you to &lt;strong&gt;swap state objects&lt;/strong&gt; to change monster behaviour dynamically.&lt;/p&gt;




&lt;h3&gt;
  
  
  11. Chain of Responsibility Pattern — Damage Reduction 🔗
&lt;/h3&gt;

&lt;p&gt;Incoming damage might be mitigated by multiple defences — like armour, shields, or magical wards. The &lt;strong&gt;Chain of Responsibility&lt;/strong&gt; Pattern lets us pass damage through each handler in sequence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/combat/DamageHandler.ts
export interface DamageHandler {
  setNext(handler: DamageHandler): DamageHandler;
  handle(damage: number): number;
}

export class ArmorHandler implements DamageHandler {
  private nextHandler?: DamageHandler;

  setNext(handler: DamageHandler): DamageHandler {
    this.nextHandler = handler;
    return handler;
  }

  handle(damage: number): number {
    const reduced = damage * 0.9;
    console.log(`[Chain] Armor reduced damage to ${reduced}`);
    return this.nextHandler?.handle(reduced) ?? reduced;
  }
}

export class ShieldHandler implements DamageHandler {
  private nextHandler?: DamageHandler;

  setNext(handler: DamageHandler): DamageHandler {
    this.nextHandler = handler;
    return handler;
  }

  handle(damage: number): number {
    const reduced = damage - 5;
    console.log(`[Chain] Shield blocked 5 points. New damage: ${reduced}`);
    return this.nextHandler?.handle(reduced) ?? reduced;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each handler &lt;strong&gt;focuses on its own logic&lt;/strong&gt; and passes the result forward. New handlers (e.g., &lt;code&gt;MagicBarrierHandler&lt;/code&gt;) can be added without modifying existing code.&lt;/p&gt;




&lt;h3&gt;
  
  
  12. Command Pattern: Powering the Terminal Interface 🧩
&lt;/h3&gt;

&lt;p&gt;We’ve already used the &lt;strong&gt;Command pattern&lt;/strong&gt; to encapsulate game actions. Now, let’s take it a step further — by turning our terminal into a fully interactive UI powered entirely by commands.&lt;/p&gt;

&lt;p&gt;Every user input is parsed into a &lt;code&gt;Command&lt;/code&gt; object, which is then executed. This clean separation allows us to decouple user input logic from game logic.&lt;/p&gt;

&lt;h4&gt;
  
  
  Command Interface 💻
&lt;/h4&gt;

&lt;p&gt;A simple contract that ensures all commands implement the same &lt;code&gt;execute&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/commands/Command.ts
export interface Command {
  execute(): void;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  AddPlayerCommand 👤
&lt;/h4&gt;

&lt;p&gt;Adds a new player to the game through the &lt;code&gt;GameManager&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/commands/AddPlayerCommand.ts
import { Command } from './Command';
import { GameManager } from '../core/GameManager';

export class AddPlayerCommand implements Command {
  constructor(private name: string) {}

  execute(): void {
    GameManager.getInstance().addPlayer(this.name);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  SummonMonsterCommand 🐲
&lt;/h4&gt;

&lt;p&gt;Creates and adds a monster (goblin or dragon) to the arena, then triggers its attack behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/commands/SummonMonsterCommand.ts
import { Command } from './Command';
import { MonsterFactory } from '../factories/MonsterFactory';
import { GameManager } from '../core/GameManager';

export class SummonMonsterCommand implements Command {
  constructor(private type: 'goblin' | 'dragon') {}

  execute(): void {
    const monster = MonsterFactory.create(this.type);
    GameManager.getInstance().addMonster(monster);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  ShowPlayersCommand 👥
&lt;/h4&gt;

&lt;p&gt;Displays a list of all current players in the game.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/commands/ShowPlayersCommand.ts
import { Command } from './Command';
import { GameManager } from '../core/GameManager';

export class ShowPlayersCommand implements Command {
  execute(): void {
    console.log('[Players]', GameManager.getInstance().getPlayers());
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  ShowLogsCommand 📜
&lt;/h4&gt;

&lt;p&gt;Outputs a placeholder log message (you can expand this later with Event Sourcing).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/commands/ShowLogsCommand.ts
import { Command } from './Command';

export class ShowLogsCommand implements Command {
  execute(): void {
    console.log('[Logs] No persistent log storage yet.');
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Terminal: Parsing Commands and Executing Actions 👩‍💻
&lt;/h4&gt;

&lt;p&gt;This class powers our command-line interface. It reads user input, translates it into commands, and runs them. It keeps UI logic separate from game mechanics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/ui/Terminal.ts
import readline from 'readline';
import { AddPlayerCommand } from '../commands/AddPlayerCommand';
import { SummonMonsterCommand } from '../commands/SummonMonsterCommand';
import { ShowPlayersCommand } from '../commands/ShowPlayersCommand';
import { Command } from '../commands/Command';
import { GameManager } from '../core/GameManager';
import { AttackMonsterCommand } from '../commands/AttackMonsterCommand';

export class Terminal {
  private rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });

  start() {
    console.log('Welcome to the Monster Battle Game!');
    this.printHelp();
    this.prompt();
  }

  private printHelp(): void {
    console.log('\nAvailable commands:');
    console.log('  add-player &amp;lt;name&amp;gt;         - Add a new player');
    console.log('  summon-monster &amp;lt;type&amp;gt;     - Summon a monster (goblin or dragon)');
    console.log('  attack &amp;lt;monsterName&amp;gt;      - Make a monster attack');
    console.log('  show-players              - Show all current players');
    console.log('  help                      - Show this help menu');
    console.log('  exit                      - Quit the game');
  }

  private prompt() {
    this.rl.question('&amp;gt; ', (input) =&amp;gt; {
      const [command, ...args] = input.trim().split(' ');

      if (command === 'help') {
        this.printHelp();
      } else if (command === 'exit') {
        console.log('Exiting game. Goodbye!');
        this.rl.close();
        return;
      } else {
        const cmd = this.createCommand(command, args);
        if (cmd) cmd.execute();
        else console.log('Unknown command or invalid arguments. Type "help" to see available options.');
      }

      this.prompt();
    });
  }

  private createCommand(command: string, args: string[]): Command | null {
    switch (command) {
      case 'add-player':
        if (!args[0]) return null;
        return new AddPlayerCommand(args[0]);

      case 'summon-monster':
        if (args[0] !== 'goblin' &amp;amp;&amp;amp; args[0] !== 'dragon') return null;
        return new SummonMonsterCommand(args[0] as 'goblin' | 'dragon');

      case 'attack': {
        const name = args[0]?.toLowerCase();
        if (!name) return null;

        const monster = GameManager.getInstance().getMonster(name);
        if (!monster) {
          console.log(`[Error] Monster "${args[0]}" not found.`);
          return null;
        }

        return new AttackMonsterCommand(monster.name);
      }

      case 'show-players':
        return new ShowPlayersCommand();

      default:
        return null;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  14. Initializing the Game Runtime ⚙️
&lt;/h3&gt;

&lt;p&gt;Let’s wire everything together by creating the main entry point of the application. This is where your Monster Arena game kicks off and the terminal interface comes to life.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// src/index.ts
import { Terminal } from './ui/Terminal';

new Terminal().start();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple snippet initializes the terminal UI and starts listening for user input, allowing players to join the game, summon monsters, and initiate battles — all from the command line.&lt;/p&gt;




&lt;h3&gt;
  
  
  15. Running the Game ▶️
&lt;/h3&gt;

&lt;p&gt;The game can be started using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run start&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;You’ll be greeted with the interactive terminal interface, ready for action. Here’s a quick example of what a session might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\monster-arena-game&amp;gt; npm run start

&amp;gt; monster-arena-game@1.0.0 start
&amp;gt; ts-node src/index.ts

Welcome to the Monster Battle Game!

Available commands:
  add-player &amp;lt;name&amp;gt;         - Add a new player
  summon-monster &amp;lt;type&amp;gt;     - Summon a monster (goblin or dragon)
  attack &amp;lt;monsterName&amp;gt;      - Make a monster attack
  show-players              - Show all current players
  help                      - Show this help menu
  exit                      - Quit the game
&amp;gt; add-player Robin
[GameManager] Robin joined the arena.
&amp;gt; show-players
[Players] [ 'Robin' ]
&amp;gt; summon-monster dragon
[GameManager] Dragon has entered the battlefield.
&amp;gt; attack dragon
[Strategy] Dragon hurls a fireball!
[State] Dragon is strong and confident.
&amp;gt; help

Available commands:
  add-player &amp;lt;name&amp;gt;         - Add a new player
  summon-monster &amp;lt;type&amp;gt;     - Summon a monster (goblin or dragon)
  attack &amp;lt;monsterName&amp;gt;      - Make a monster attack
  show-players              - Show all current players
  help                      - Show this help menu
  exit                      - Quit the game
&amp;gt; exit
Exiting game. Goodbye!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just like that — you’ve created a fully functional, design-pattern-powered monster game right in your terminal. But this is only the beginning.&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt; of this series, we’ll deepen the architecture by refining existing code base, introducing new design patterns, and adding exciting features: a turn-based system for player actions, health and life mechanics for both players and monsters, plus loot and experience systems to level up your gameplay, and more.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion 📣
&lt;/h2&gt;

&lt;p&gt;Design patterns are more than just theoretical concepts — they’re practical tools that can transform your code from a tangled mess into a well-structured, flexible system. By building the &lt;strong&gt;Monster Arena game&lt;/strong&gt;, you’ve seen first-hand how these patterns can be applied to solve common software design challenges in a modular and maintainable way.&lt;/p&gt;

&lt;p&gt;From managing global state with &lt;strong&gt;Singleton&lt;/strong&gt;, to crafting dynamic monster behaviours using &lt;strong&gt;Strategy&lt;/strong&gt; and &lt;strong&gt;State&lt;/strong&gt;, to enabling real-time updates with &lt;strong&gt;Observer&lt;/strong&gt;, and streamlining object creation with &lt;strong&gt;Factory&lt;/strong&gt;, each pattern brought a unique and powerful advantage to our game architecture. Wrapping actions in &lt;strong&gt;Command&lt;/strong&gt; objects not only made our game logic cleaner but also paved the way for an extensible terminal UI. And finally, the &lt;strong&gt;Chain of Responsibility&lt;/strong&gt; pattern showed how layered logic like damage mitigation can be elegantly handled.&lt;/p&gt;

&lt;p&gt;This hands-on project proves that design patterns are not just abstract ideas — they’re practical, approachable, and fun. Whether you’re building games, web apps, or backend services, mastering these patterns in TypeScript will empower you to write cleaner, more scalable, and easier-to-maintain code.&lt;/p&gt;




&lt;p&gt;🙏 Thanks for reading to the end! If you have any questions, feel free to drop a comment below. But I am also curious. Would you have designed it different? Can you find flaws with this design? Let me know and we'll discuss it 😊&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, follow me on Medium and social media — I’d love to connect and follow you back:&lt;/p&gt;

&lt;p&gt;Robin Viktorsson (&lt;a class="mentioned-user" href="https://dev.to/robinviktorsson"&gt;@robinviktorsson&lt;/a&gt;) / Medium&lt;br&gt;
Robin Viktorsson (&lt;a class="mentioned-user" href="https://dev.to/robinviktorsson"&gt;@robinviktorsson&lt;/a&gt;) / X&lt;br&gt;
Robin Viktorsson (@&lt;a href="mailto:robinviktorsson@mastodon.social"&gt;robinviktorsson@mastodon.social&lt;/a&gt;) — Mastodon&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>designpatterns</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>5 Practices That Help Me Grow as a Software Developer (and Engineer)</title>
      <dc:creator>Robin Viktorsson</dc:creator>
      <pubDate>Sun, 06 Jul 2025 10:15:18 +0000</pubDate>
      <link>https://forem.com/robinviktorsson/5-practices-that-help-me-grow-as-a-software-developer-and-engineer-3d38</link>
      <guid>https://forem.com/robinviktorsson/5-practices-that-help-me-grow-as-a-software-developer-and-engineer-3d38</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ose3sf8s6z67a9lwql7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ose3sf8s6z67a9lwql7.jpg" alt="Programming and cooking" width="640" height="589"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://medium.com/@robinviktorsson/five-practices-that-help-me-grow-as-a-software-developer-and-engineer-d1e15c3b8070" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s be real — becoming a skilled, professional software developer is no easy feat. It takes years (sometimes decades) to truly master this craft. Without a deliberate effort to grow daily, it’s easy to get stuck in the same place, falling behind in an industry that evolves faster than any other.&lt;/p&gt;

&lt;p&gt;That’s why I’ve adopted these five practices. They’ve helped me sharpen my skills, stay current, and cultivate a mindset that’s always evolving.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Feed the Hunger 🍽️🔥
&lt;/h2&gt;

&lt;p&gt;What do I mean by “feed the hunger”? To grow as a software developer, you need more than just a job — you need a genuine curiosity for the craft. I call it &lt;em&gt;hunger&lt;/em&gt;. It is the desire to explore new programming languages, dig into architectural patterns, experiment with frameworks, absorb new ideas, etc.&lt;/p&gt;

&lt;p&gt;Every day, I attempt to feed this hunger by either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading blog posts, technical articles, or &lt;a href="https://amzn.eu/d/2cAOtO7" rel="noopener noreferrer"&gt;software-related books&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Writing blog posts like this one (teaching is learning!)&lt;/li&gt;
&lt;li&gt;Exploring source code on GitHub&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tryhackme.com/" rel="noopener noreferrer"&gt;Solving coding challenges&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Learning new tools and methodologies (e.g. &lt;a href="https://en.wikipedia.org/wiki/Model-based_testing" rel="noopener noreferrer"&gt;testing methodologies&lt;/a&gt;, &lt;a href="https://mermaid.js.org/" rel="noopener noreferrer"&gt;diagramming tools&lt;/a&gt;, etc)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether it’s 10 minutes or an hour, I noticed that &lt;strong&gt;consistency matters more than time&lt;/strong&gt;. Keep feeding that curiosity, and it will reward you with growth.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. I added links in the list above to hint at what I’m currently exploring.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Code Like a Chef 👨‍🍳💻
&lt;/h2&gt;

&lt;p&gt;Yes, I’m comparing coding to cooking — Oh god, another food metaphor. I must be hungry. Let me grab a sandwich. Okay, back. The goal is to approach code like a Michelin-star chef: with planning, care, creativity, and the occasional Gordon Ramsay scream (internally, of course).&lt;/p&gt;

&lt;p&gt;Let’s start with &lt;strong&gt;planning&lt;/strong&gt;. Like chefs prepping ingredients the night before, I reflect on challenges before bed or in the shower. Oddly enough, solutions often bubble up when I’m not actively coding. This tiny habit helps me fruit ideas of how I can approach the challenge when waking up tomorrow. This gives me a head start the next day, armed with fresh ideas and a pinch of optimism.&lt;/p&gt;

&lt;p&gt;Regarding &lt;strong&gt;care&lt;/strong&gt;: Just like chefs carefully follow a recipe, I keep software requirements front and center while coding. I stay mindful of design principles and best practices to ensure my code is clean, reliable, maintainable, and testable. I also regularly check in with whoever defined the system design — or reflect on my own intent — to make sure I’m on track. This alignment habit usually results in code I can feel proud of.&lt;/p&gt;

&lt;p&gt;If you want to dive deeper, here are two of my previous articles (published on Medium.com) covering key principles I rely on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@robinviktorsson/software-architecture-mastering-s-o-l-i-d-principles-with-practical-examples-in-typescript-b4932f772920" rel="noopener noreferrer"&gt;Software Architecture: Mastering S.O.L.I.D Principles with Practical Examples in TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://medium.com/@robinviktorsson/software-architecture-explaining-kiss-dry-yagni-with-practical-examples-in-typescript-9bf23c484816" rel="noopener noreferrer"&gt;Software Architecture: Explaining KISS, DRY, YAGNI with Practical Examples in TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And don’t forget to taste test your code! Just like chefs sampling their dish before serving, I regularly test and review my code. That means writing thorough tests and refactoring whenever something tastes off — whether it’s too complex, too slow, or just confusing. A well-tested codebase is like a perfectly balanced meal: reliable and enjoyable for everyone.&lt;/p&gt;

&lt;p&gt;About &lt;strong&gt;creativity&lt;/strong&gt;. Just like a chef experiments with new spices and flavour combinations, I like to bring creativity into my code. This means trying out new patterns, exploring different libraries, or finding clever solutions to tricky problems — without sacrificing clarity or quality. I’m not afraid to remix familiar ingredients in fresh ways, whether it’s refactoring legacy code or architecting a new feature from scratch. Creativity keeps coding exciting and helps me grow beyond just “making it work” to crafting something elegant and enjoyable to maintain.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Share (or Ask for) the Recipe 🔄📖
&lt;/h2&gt;

&lt;p&gt;Alright — we’re way deep into the food metaphor now, so let’s just roll with it. Sharing recipes is what makes great meals — and great teams — possible.&lt;/p&gt;

&lt;p&gt;Don’t keep your secret sauce to yourself. Tap into the wisdom of your colleagues at all levels: seniors, mediors, and juniors alike. Use every opportunity — system design reviews, code reviews, pair programming sessions — to exchange ideas and gather constructive feedback. These reviews aren’t just hoops to jump through; they’re chances to refine your code and learn new techniques.&lt;/p&gt;

&lt;p&gt;I make it a point to both give and receive feedback regularly because knowledge sharing is like passing down a treasured family recipe. When you teach others, you deepen your understanding and help raise the overall quality of the kitchen — er, team.&lt;/p&gt;

&lt;p&gt;Remember: seniors bring hard-earned experience, mediors offer solid knowledge and fresh perspectives, and juniors inject new energy and innovative ideas. By blending these insights, you create a richer, more flavourful learning environment that benefits everyone.&lt;/p&gt;

&lt;p&gt;So don’t be shy — share your recipe and ask for theirs. The best cooks don’t cook alone; they learn from and inspire each other every day. That’s why I enjoy sharing stories on Dev.to 🥳&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Respect the Timing ⏳🍳
&lt;/h2&gt;

&lt;p&gt;Just like cooking requires the right timing — too fast, and the dish is raw; too slow, and it’s overdone — software development needs a balance between speed and patience, and the stakeholders needs to understand this. Rushing can introduce bugs or technical debt, while endless tweaking delays delivery.&lt;/p&gt;

&lt;p&gt;I remind myself to trust the process, ship incrementally, and polish thoughtfully over time. Good things take time to mature, whether it’s a soufflé or a software feature.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Plate Your Work Nicely 🎨🍽️
&lt;/h2&gt;

&lt;p&gt;Presentation matters — whether it’s a gourmet meal or your codebase. I try to “plate” my work by writing clear, readable, and well-documented code so others can easily understand and enjoy it.&lt;/p&gt;

&lt;p&gt;Just like a beautifully arranged dish invites people to dig in, well-organized code invites collaboration and makes debugging or extending features a breeze. Good code is not just about functionality; it’s about creating an experience that’s pleasant for your teammates — and your future self.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion: Keep Cooking, Keep Growing 📣
&lt;/h2&gt;

&lt;p&gt;Becoming a great software developer isn’t a destination — it’s a journey that requires passion, patience, and continuous effort. By feeding your hunger for knowledge, coding thoughtfully like a chef, sharing your recipes, respecting timing, and plating your work with care, you set yourself up for meaningful growth.&lt;/p&gt;

&lt;p&gt;Remember, growth isn’t about perfection or rushing to the finish line. It’s about cultivating habits that nourish your skills and mindset every day. So keep cooking your craft with intention and curiosity — your future self will thank you.&lt;/p&gt;




&lt;p&gt;🙏 Thanks for reading to the end! If you have any questions, feel free to drop a comment below.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, follow me on Medium and social media — I’d love to connect and follow you back:&lt;/p&gt;

&lt;p&gt;Robin Viktorsson (&lt;a class="mentioned-user" href="https://dev.to/robinviktorsson"&gt;@robinviktorsson&lt;/a&gt;) / Medium&lt;br&gt;
Robin Viktorsson (&lt;a class="mentioned-user" href="https://dev.to/robinviktorsson"&gt;@robinviktorsson&lt;/a&gt;) / X&lt;br&gt;
Robin Viktorsson (@&lt;a href="mailto:robinviktorsson@mastodon.social"&gt;robinviktorsson@mastodon.social&lt;/a&gt;) — Mastodon&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>learning</category>
    </item>
    <item>
      <title>Setting Up a Modern TypeScript Project with esbuild (No Framework)</title>
      <dc:creator>Robin Viktorsson</dc:creator>
      <pubDate>Mon, 07 Apr 2025 19:36:49 +0000</pubDate>
      <link>https://forem.com/robinviktorsson/setting-up-a-modern-typescript-project-with-esbuild-no-framework-38ap</link>
      <guid>https://forem.com/robinviktorsson/setting-up-a-modern-typescript-project-with-esbuild-no-framework-38ap</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article was originally published on &lt;a href="https://medium.com/@robinviktorsson/setting-up-a-modern-typescript-project-with-esbuild-no-framework-fe04f6d72f9e" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Modern web development focuses on building fast, scalable, and maintainable applications — and choosing the right tools is essential to achieving that. If you’re working with TypeScript, you’re already prioritizing structure, type safety, and maintainability. But to transform your TypeScript code into a fully functioning, production-ready web application, you need more than just the TypeScript compiler (tsc) — you need a bundler. This is where &lt;strong&gt;esbuild&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;While the TypeScript compiler does an excellent job of transpiling your TypeScript into JavaScript, it doesn’t handle bundling, asset optimization, or other features that streamline both development and production workflows. esbuild steps in to fill these gaps by acting as a fast and efficient bundler that understands your project’s structure, optimizes assets, and produces performant output suitable for the browser.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll walk through creating a TypeScript project from scratch using esbuild as the bundler — without relying on any frameworks. You’ll learn how to compile TypeScript, serve files locally with live reloading, handle assets like CSS and images, and optimize your build for production with features such as tree shaking and minification.&lt;/p&gt;

&lt;p&gt;Whether you’re diving into bundlers for the first time, building a lightweight utility, or laying the groundwork for a larger application, this hands-on tutorial will give you the confidence to leverage esbuild’s speed and efficiency to optimize your development and production workflows.&lt;/p&gt;

&lt;p&gt;Sit back, grab some popcorn 🍿, and enjoy the read!&lt;/p&gt;




&lt;h2&gt;
  
  
  What is esbuild (as a Bundler)? 🤔
&lt;/h2&gt;

&lt;p&gt;When using the TypeScript compiler (tsc), your code is compiled into individual .js files without bundling. While this maintains project organization, it doesn't optimize performance. Modern applications require a bundler to consolidate code, resolve dependencies, and optimize bundles, improving load times and performance. Bundlers also enable techniques like tree-shaking and dead code elimination (CDE), which remove unused code, further reducing file size. Additionally, they ensure compatibility across various module formats and environments and can handle assets like CSS and images. Features like code splitting and hot module replacement (HMR) make bundlers indispensable for optimizing both development and production workflows.&lt;/p&gt;

&lt;p&gt;esbuild is an ultra-fast JavaScript bundler and minifier, designed to be both powerful and simple. The key to esbuild’s speed lies in its implementation. Unlike other bundlers written in JavaScript, esbuild is written in Go, a compiled systems language. This allows it to leverage parallelism and low-level optimizations that result in builds that are often 10–100x faster than tools like Webpack or Rollup.&lt;/p&gt;

&lt;p&gt;At its core, esbuild is a bundler — meaning it takes multiple source files (JavaScript, TypeScript, JSX, etc.), resolves their dependencies, and combines them into optimized output files for the browser or Node.js. But esbuild goes beyond simple bundling by offering a complete toolchain for modern frontend development. This process includes key features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tree-Shaking&lt;/strong&gt;: esbuild has built-in support for tree-shaking, automatically removing unused code from the final bundle. This results in smaller and more efficient output files — especially valuable in large TypeScript projects where minimizing bundle size directly improves load performance and runtime efficiency.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asset Management&lt;/strong&gt;: While esbuild primarily focuses on JavaScript, TypeScript, and CSS, it can be extended to handle static assets like images and fonts via plugins or external tools. With the right setup, esbuild can efficiently include and optimize these assets in your build pipeline, making it a flexible option for production-ready applications.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Plugins and Extensibility&lt;/strong&gt;: esbuild supports a lightweight plugin API, allowing you to customize how files are processed. Although its plugin ecosystem is still growing compared to other bundlers (e.g. Webpack), esbuild covers many common needs out of the box, such as TypeScript transpilation, JSX transformation, and minification, without requiring complex configurations.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Development Experience&lt;/strong&gt;: Thanks to its blazing speed, esbuild significantly improves the development experience. It can rebuild large projects in milliseconds, enabling rapid feedback loops. While esbuild doesn’t provide built-in Hot Module Replacement (HMR), it integrates well with tools like Vite or custom dev servers that offer live reloading and HMR support.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, esbuild’s modern architecture and focus on speed make it a great choice for bundling TypeScript projects, especially when quick builds and simple configuration are key. While it may lack some of the deep customization and mature ecosystem of Webpack and Vite, its performance, simplicity, and native TypeScript support make it ideal for many modern web development workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating a TypeScript Project with esbuild 👨‍💻
&lt;/h2&gt;

&lt;p&gt;Creating a TypeScript project with &lt;strong&gt;esbuild&lt;/strong&gt; is a great way to learn the modern JavaScript build process, optimize your applications, and gain hands-on experience with fast, lightweight tooling. While many web frameworks offer built-in TypeScript and bundling configurations, there are plenty of scenarios where a minimal, framework-free approach is more appropriate.&lt;/p&gt;

&lt;p&gt;Whether you’re building a command-line utility, developing a modular JavaScript/TypeScript library, or simply experimenting with esbuild’s blazing-fast capabilities, this guide will walk you through setting up a TypeScript project from scratch using esbuild as the bundler.&lt;/p&gt;

&lt;p&gt;We’ll cover everything from initializing a Node.js project to configuring esbuild, compiling TypeScript, running a lightweight development server, and preparing your project for production. With esbuild and TypeScript, you can create a lean, efficient, and production-ready build system — all while maintaining complete control over your development workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prerequisites 🧱
&lt;/h3&gt;

&lt;p&gt;Ensure you have the following installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;npm (comes with Node.js)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Project Structure 📁
&lt;/h3&gt;

&lt;p&gt;Let’s begin by setting up the foundational structure of our project. We’ll initialize a new Node.js project and generate a &lt;code&gt;package.json&lt;/code&gt; file with default values using the &lt;code&gt;npm init -y&lt;/code&gt; command. Continue to create the necessary directories and files for our TypeScript and esbuild setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Note: 'touch' is not available in Windows, instead create file manually

mkdir ts-esbuild-app
cd ts-esbuild-app
npm init -y

# Create source and assets directory alongside its files
mkdir src
cd src
touch index.html
touch index.ts
touch styles.css
touch assets.d.ts

mkdir assets
cd assets
touch logo.png 

# Create output directory and index.html
cd ../..
mkdir dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the commands, your project directory should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ts-esbuild-app/
├── dist/
├── src/
│   └── index.ts
│   └── index.html
│   └── assets.d.ts
│   └── styles.css
│   ├── assets/
│   │   └── logo.png
├── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure separates your source code (&lt;code&gt;src/&lt;/code&gt;) from your build output (&lt;code&gt;dist/&lt;/code&gt;), which is a best practice in most modern web projects. We'll populate and refine the contents of all files in the upcoming sections as we configure TypeScript and esbuild.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Install Dependencies 📦
&lt;/h3&gt;

&lt;p&gt;Now that we have our project structure in place, it’s time to install the essential development tools that will allow us to compile TypeScript and bundle our project with esbuild. Run the following command to install the required dependencies as development-only packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev typescript esbuild live-server @types/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s an overview of the purpose of each of these packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;esbuild&lt;/strong&gt;: The core bundler that takes your files and dependencies (JS, CSS, images, etc.) and outputs optimized bundles.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;live-server&lt;/strong&gt;: Serves your app locally with live reloading and Hot Module Replacement (HMR) at &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt; or your configured port.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;typescript&lt;/strong&gt;: The TypeScript compiler (&lt;code&gt;tsc&lt;/code&gt;) for writing &lt;code&gt;.ts&lt;/code&gt; files and compiling them to JavaScript.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are added as dev dependencies (&lt;code&gt;--save-dev&lt;/code&gt;) because they’re only needed during development and build time—not at runtime. With these tools, you now have a full TypeScript + esbuild setup: write in TypeScript, compile and bundle with esbuild, and develop locally with live reloading (if you installed live-server).&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Create &lt;code&gt;tsconfig.json&lt;/code&gt; ✍️
&lt;/h3&gt;

&lt;p&gt;To configure how TypeScript compiles your project, you need to create a &lt;code&gt;tsconfig.json&lt;/code&gt; file. This file serves as the central configuration for the TypeScript compiler (&lt;code&gt;tsc&lt;/code&gt;), allowing you to define the compiler options, project structure, and file inclusion patterns. To generate this file, run the following command in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx tsc --init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates a &lt;code&gt;tsconfig.json&lt;/code&gt; file with a default set of compiler options. You can then customize it to fit the needs of your project. Here’s a recommended configuration for a modern TypeScript + esbuild setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compilerOptions": {
    "target": "ES6",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist"
  },
  "include": ["src"]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tsconfig.json&lt;/code&gt; file contains various compiler options that guide TypeScript in how it should compile your code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;"target": "ESNext"&lt;/code&gt; tells TypeScript to compile your code down to latest available ECMAScript JavaScript version. This ensures modern language features are supported while maintaining reasonable browser compatibility.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;"module": "ESNext"&lt;/code&gt; instructs TypeScript to output native ES module syntax (import/export). This is necessary for esbuild to perform advanced bundling techniques like tree-shaking.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;"moduleResolution": "node"&lt;/code&gt; uses Node.js-style module resolution, which is compatible with most bundling and package systems. This means TypeScript will mimic how Node.js resolves import paths.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;"outDir": "./dist"&lt;/code&gt; specifies the directory where compiled JavaScript files should be emitted. In this setup, all output files will go to the dist folder.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;"esModuleInterop": true&lt;/code&gt; enables interoperability between CommonJS and ES Module systems. This allows you to seamlessly import packages that use module.exports (CommonJS), such as many Node.js packages.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;"strict": true&lt;/code&gt; enables all of TypeScript’s strict type-checking options. This helps catch potential bugs early and enforces best practices in your codebase.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;"skipLibCheck": true&lt;/code&gt; skips type checking of external declaration files (e.g., from node_modules). This speeds up compilation and avoids unnecessary type conflicts from third-party packages.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;"include": ["src"]&lt;/code&gt; tells TypeScript to only include and compile files located in the src folder. This helps keep the build focused and fast by excluding unnecessary files.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These settings work together to create an efficient, modern, and maintainable TypeScript development environment. They ensure compatibility with esbuild, optimize your build speed, and enable strong typing throughout your codebase.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Create &lt;code&gt;esbuild.config.ts&lt;/code&gt; ⚙️
&lt;/h3&gt;

&lt;p&gt;Now that we’ve set up TypeScript, it’s time to configure esbuild — the tool responsible for bundling your code and preparing it for the browser. Create a new file called &lt;code&gt;esbuild.config.ts&lt;/code&gt; in the root of your project. This file will define how esbuild handles your source files, compiles TypeScript, and serves your application during development. Here’s a minimal yet effective configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import esbuild from 'esbuild';
import path from 'path';

// Define the output directory for your build
const outDir = path.resolve(__dirname, 'dist');

// Create the esbuild build configuration
esbuild.build({
  entryPoints: ['src/index.ts'], // Main entry point (change as needed)
  bundle: true, // Enable bundling
  format: 'esm',
  outfile: path.join(outDir, 'index.js'), // Output file
  sourcemap: true, // Generate sourcemaps
  minify: true, // Minify the output for production
  assetNames: 'assets/[name]-[hash]', // Asset file naming convention with hash for cache busting
  loader: {
    '.png': 'file', // Handle PNG images
    '.jpg': 'file', // Handle JPEG images
    '.css': 'css', // Handle CSS files
    '.ts': 'ts', // Handle TypeScript files (optional, as esbuild does this by default)
  },
  external: [], // Exclude specific modules from being bundled (e.g., node_modules)
  define: {
    'process.env.NODE_ENV': '"production"', // Define environment variables for optimization
  }
}).catch(() =&amp;gt; process.exit(1)); // Exit on error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;esbuild.config.ts&lt;/code&gt; file is a configuration file used by esbuild, a lightning-fast JavaScript and TypeScript bundler. It defines how esbuild should process, bundle, and optimize your project’s source files, including JavaScript, TypeScript, CSS, and other assets. Through this configuration, you can customize the build process by specifying entry points, output formats, target environments, external dependencies, and plugin integrations. While esbuild favors simplicity and minimal configuration, the config file allows for advanced customization when needed, especially for large or complex projects.&lt;/p&gt;

&lt;p&gt;The importance of the &lt;code&gt;esbuild.config.ts&lt;/code&gt; file lies in its ability to deliver extremely fast builds with powerful customization. It enables you to fine-tune how files are compiled and bundled, optimize your output for production with built-in minification, and define build-time constants using environment variables. With support for plugins and watch mode, you can extend esbuild’s capabilities and enhance the development experience. Whether you're building a small library or a large application, &lt;code&gt;esbuild.config.ts&lt;/code&gt; provides the flexibility and speed to efficiently manage your build process.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Create Sample Code 💻
&lt;/h3&gt;

&lt;p&gt;Let’s add a small piece of TypeScript to verify that everything is wired up correctly. In the &lt;code&gt;src/index.ts&lt;/code&gt; file, add the following simple function that logs a personalized greeting to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import './styles.css'; // We will populate this file in next sub-chapter
import logo from './assets/logo.png'; // Will throw a TypeScript error which we will resolve later

function greet(name: string): void {
  const heading = document.createElement('h1');
  heading.textContent = `Hello, ${name}!`;
  document.body.appendChild(heading);

  const img = document.createElement('img');
  img.src = logo;
  img.alt = 'Project Logo';
  img.style.width = '200px';

  document.body.appendChild(img);
}

greet('TypeScript + esbuild');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This minimal example serves as a sanity check to ensure TypeScript compiles properly and the esbuild bundler is functioning as expected.&lt;/p&gt;

&lt;p&gt;You will get an TypeScript error &lt;em&gt;“Cannot find module ‘./assets/logo.png’ or its corresponding type declarations.ts(2307)”&lt;/em&gt;. Ignore this for now. We will resolve it in the upcoming chapter &lt;em&gt;8. Bundle Images&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Next, let’s create a basic HTML file to load the compiled JavaScript bundle into the browser. In the &lt;code&gt;src/index.html&lt;/code&gt; file, add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This HTML page simply loads the `index.js` file that esbuild will generate after compilation. Once the esbuild live server is running, this file will be served at `http://127.0.0.1:8080`, and the greeting should appear in your browser's developer console.

With just these two files — `index.ts` and `index.html` — you now have a basic but working TypeScript + esbuild setup ready for further development.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Add and Bundle CSS 🎨
&lt;/h3&gt;

&lt;p&gt;Now that your TypeScript and esbuild setup is working, let’s take it a step further and add support for CSS. esbuild can also bundle stylesheets, making it easy to manage CSS alongside your TypeScript files. Add an &lt;code&gt;src/styles.css&lt;/code&gt; file which contains some simple styling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;body {
  background-color: #f0f0f0;
  font-family: sans-serif;
  padding: 2rem;
}

h1 {
  color: #3366ff;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because you added this loader in &lt;code&gt;esbuild.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loader: {
    '.png': 'file',
    '.css': 'css',
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;esbuild handles &lt;code&gt;.css&lt;/code&gt; files directly if you use the loader: &lt;code&gt;{ '.css': 'css' }&lt;/code&gt; configuration. CSS is bundled into your final output, or injected depending on your preference.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Bundle Images 🖼️
&lt;/h3&gt;

&lt;p&gt;To include images in your TypeScript + esbuild project, you need to configure esbuild to handle static assets like &lt;code&gt;.png&lt;/code&gt;, &lt;code&gt;.jpg&lt;/code&gt;, &lt;code&gt;.svg&lt;/code&gt;, and more. We already created a &lt;code&gt;logo.png&lt;/code&gt; while setting up the folder structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import logo from './assets/logo.png';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, our import of the &lt;code&gt;logo.png&lt;/code&gt; file in &lt;code&gt;index.ts&lt;/code&gt; is still throwing this error: &lt;em&gt;“Cannot find module ‘./assets/logo.png’ or its corresponding type declarations.ts(2307)”&lt;/em&gt;. To solve this, add the following to &lt;code&gt;assets.d.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;declare module '*.png' {
    const value: string;
    export default value;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make it available in the final &lt;code&gt;dist/&lt;/code&gt; folder, we’ll add the following parameter to out build script &lt;code&gt;--loader:.png=file&lt;/code&gt;, but this is something we will add in the next chapter, so hold your horses for now.&lt;/p&gt;

&lt;p&gt;When you run the build, your image (e.g., &lt;code&gt;logo.png&lt;/code&gt;) will be available in the output folder, and your code will reference it correctly thanks to the static module declaration. If you're using something like &lt;code&gt;live-server&lt;/code&gt; to serve your files, it will serve the image alongside your HTML and JavaScript, ensuring it displays properly in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Add Scripts in &lt;code&gt;package.json&lt;/code&gt; 🔨
&lt;/h3&gt;

&lt;p&gt;In the &lt;code&gt;package.json&lt;/code&gt; file, you can define custom scripts to automate common development tasks. These scripts serve as shortcuts for commands you’d otherwise need to run manually in the terminal. When working with esbuild, you can set up similar &lt;code&gt;build&lt;/code&gt; and &lt;code&gt;start&lt;/code&gt; scripts to handle both development and production workflows.&lt;/p&gt;

&lt;p&gt;Start by adding the following entries under the "scripts" section in your &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"scripts": {
  "build": "esbuild --bundle src/index.ts --minify --loader:.png=file src/index.html --loader:.html=copy --outdir=dist",
  "start": "npm run build &amp;amp;&amp;amp; live-server dist"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; script runs esbuild to bundle your TypeScript project. It takes the &lt;code&gt;src/index.ts&lt;/code&gt; file as the entry point, bundles it along with its dependencies, outputs it to the &lt;code&gt;dist/&lt;/code&gt; folder as &lt;code&gt;index.js&lt;/code&gt;, and minifies the output for production use. You can also configure this further using an &lt;code&gt;esbuild.config.ts&lt;/code&gt; file for more advanced setups.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;start&lt;/code&gt; script uses &lt;code&gt;live-server&lt;/code&gt; to serve the content from your &lt;code&gt;dist/&lt;/code&gt; folder at &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt; by default. Unlike Webpack’s Dev Server, esbuild doesn’t include a built-in dev server with Hot Module Replacement (HMR). However, &lt;code&gt;live-server&lt;/code&gt; provides automatic reloading whenever files in the &lt;code&gt;dist/&lt;/code&gt; folder change, giving you a simple and effective way to preview changes during development.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These scripts make it easy to build and serve your project without needing to remember long terminal commands, improving your development experience and keeping your workflow efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  10. Run the Project ▶️
&lt;/h3&gt;

&lt;p&gt;Once you’ve added the scripts to your &lt;code&gt;package.json&lt;/code&gt;, you’re ready to start developing your project using esbuild along with a lightweight development server. To launch the server and view your project in the browser, simply run the following command in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command starts &lt;code&gt;live-server&lt;/code&gt;, which serves your &lt;code&gt;dist/&lt;/code&gt; folder and opens the project in your default web browser at &lt;code&gt;http://127.0.0.1:8080&lt;/code&gt; (or another available port if that one’s taken).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\Robin\ts-esbuild-app&amp;gt;npm run start

&amp;gt; ts-esbuild-app@1.0.0 start
&amp;gt; npm run build &amp;amp;&amp;amp; live-server dist


&amp;gt; ts-esbuild-app@1.0.0 build
&amp;gt; esbuild --bundle src/index.ts --minify --loader:.png=file src/index.html --loader:.html=copy --outdir=dist


  dist\index.js           302b 
  dist\index.html         210b 
  dist\index.css           81b 
  dist\logo-55DNWN2R.png    0b 

Done in 9ms
Serving "dist" at http://127.0.0.1:8080
Ready for changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tog07veqm401us5g7na.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0tog07veqm401us5g7na.PNG" alt="http://127.0.0.1:8080" width="440" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The server watches your output files for changes. So every time you re-run the build command (for example, after editing a TypeScript or CSS file), &lt;code&gt;live-server&lt;/code&gt; will automatically refresh the browser to show your latest updates. This live-reloading behavior speeds up your development workflow by keeping your browser in sync with your code—no need to manually refresh the page.&lt;/p&gt;

&lt;p&gt;While esbuild doesn’t provide Hot Module Replacement (HMR) out of the box like some other bundlers (e.g. Webpack), this simple setup with &lt;code&gt;live-server&lt;/code&gt; gives you a fast, zero-config way to preview changes in real-time and iterate quickly as you build your project.&lt;/p&gt;

&lt;h3&gt;
  
  
  11. Build the Project ⚒️
&lt;/h3&gt;

&lt;p&gt;While the start script is great for development, the build script is essential when you’re ready to prepare your project for production. Running &lt;code&gt;npm run build&lt;/code&gt; will trigger esbuild to bundle and optimize your project for deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command tells esbuild to execute the bundling process according to the configuration set in your &lt;code&gt;esbuild.config.ts&lt;/code&gt; file (or &lt;code&gt;esbuild&lt;/code&gt; options specified in the script). By default, esbuild will process all your assets—JavaScript, TypeScript, images, and CSS—and output them into the &lt;code&gt;dist/&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:\Users\Robin\ts-esbuild-app&amp;gt;npm run build

&amp;gt; ts-esbuild-app@1.0.0 build
&amp;gt; esbuild --bundle src/index.ts --minify --loader:.png=file src/index.html --loader:.html=copy --outdir=dist


  dist\index.js           302b 
  dist\index.html         210b 
  dist\index.css           81b 
  dist\logo-55DNWN2R.png    0b 

Done in 9ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you’ve configured esbuild to run in production mode (by using the &lt;code&gt;--minify&lt;/code&gt; flag, which we did, or through the build configuration), esbuild will automatically apply a series of optimizations to the output files. These optimizations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Minification&lt;/strong&gt;: esbuild will remove unnecessary whitespace, comments, and unused code from your JavaScript and TypeScript files, reducing their size significantly.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tree-shaking&lt;/strong&gt;: esbuild will eliminate any unused code that isn’t referenced anywhere in your project, further reducing the bundle size.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Asset optimization&lt;/strong&gt;: While esbuild doesn’t handle image optimization by default, you can easily use plugins (like &lt;code&gt;esbuild-plugin-image&lt;/code&gt;) to process image files and optimize them during the build.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the build process completes, you’ll find your bundled JavaScript in the &lt;code&gt;dist/&lt;/code&gt; directory (e.g., &lt;code&gt;dist/index.js&lt;/code&gt;). This version of your application is optimized for performance and is ready for production deployment.&lt;/p&gt;

&lt;p&gt;By using the build command, you ensure that your project is fully optimized and prepared to be served to users with the best possible performance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion 📣
&lt;/h2&gt;

&lt;p&gt;In this guide, we’ve explored how to create a lightweight, framework-free TypeScript project using esbuild. From setting up the project structure to configuring esbuild and enabling TypeScript compilation, you now have a streamlined, production-ready environment. Along the way, we’ve touched on key topics such as tree shaking, asset management, and optimizing both development and production builds for performance.&lt;/p&gt;

&lt;p&gt;While frameworks like React or Angular come with pre-configured setups, using a custom TypeScript and esbuild configuration offers you complete control over your build process. This approach is especially beneficial for modular libraries, utilities, or smaller applications that don’t require the overhead of a full framework.&lt;/p&gt;

&lt;p&gt;By harnessing esbuild’s fast bundling and optimization capabilities, you can reduce load times and enhance performance. These improvements make your project more efficient and scalable, enabling it to grow from small utilities to large-scale applications, all while keeping things lightweight.&lt;/p&gt;

&lt;p&gt;Whether you’re new to TypeScript and esbuild or refining your existing workflow, this guide provides a solid foundation for building, bundling, and optimizing modern JavaScript applications. With a deep understanding of esbuild’s features, you’re now equipped to handle complex web projects with complete control over your application’s build process.&lt;/p&gt;




&lt;p&gt;🙏 &lt;strong&gt;Thanks&lt;/strong&gt; for reading to the end! If you have any questions, feel free to drop a comment below.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, follow me on Medium and social media — I’d love to connect and follow you back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Robin Viktorsson (&lt;a href="https://medium.com/@robinviktorsson" rel="noopener noreferrer"&gt;@robinviktorsson&lt;/a&gt;) / Medium&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Robin Viktorsson (&lt;a href="https://x.com/RobinViktorsson" rel="noopener noreferrer"&gt;@RobinViktorsson&lt;/a&gt;) / X&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Robin Viktorsson (&lt;a href="https://mastodon.social/@robinviktorsson" rel="noopener noreferrer"&gt;@robinviktorsson@mastodon.social&lt;/a&gt;) — Mastodon&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>esbuild</category>
      <category>bundler</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
