Skip to content

Getting Started

Prerequisites

  • Node.js >= 22
  • pnpm >= 10
  • Access to the framework's private registry — see Registry Access for the .npmrc setup. Without it, pnpm install cannot resolve @modularityjs/* packages.

Quick start

The fastest way to spin up a new app is the @modularityjs/create scaffolder. It writes the project, wires a working boot script for the contracts you pick, installs dependencies, and initialises git:

bash
pnpm create @modularityjs my-app
cd my-app
pnpm dev

Add --yes to skip the prompts and accept the defaults (Fastify + Zod + console logger). See the Create tool docs for every flag.

If you'd rather wire things up by hand, follow the manual installation below.

Installation

Install the framework primitives and a DI driver:

bash
pnpm add @modularityjs/modularity @modularityjs/di @modularityjs/di-inversify

Then add any contract + driver pairs your app needs. For a minimal HTTP server:

bash
pnpm add @modularityjs/http @modularityjs/http-fastify

For CLI support:

bash
pnpm add @modularityjs/cli @modularityjs/cli-commander

Creating Your First App

Every ModularityJS application starts with createApp(). You pass it a DI driver and a list of modules:

typescript
import { ModularityModule, createApp } from '@modularityjs/modularity';
import { inversify } from '@modularityjs/di-inversify';

const app = await createApp({
  di: inversify,
  modules: [ModularityModule],
});

await app.start();

Adding a Module

Modules are classes decorated with @Module(). They declare providers, imports, and configuration:

typescript
import { Inject, Injectable } from '@modularityjs/di';
import { Module } from '@modularityjs/modularity';

@Injectable()
class GreeterService {
  greet(name: string): string {
    return `Hello, ${name}!`;
  }
}

@Module({
  name: 'greeter',
  providers: [GreeterService],
})
class GreeterModule {}

Register the module with createApp():

typescript
const app = await createApp({
  di: inversify,
  modules: [ModularityModule, GreeterModule],
});

const greeter = app.get(GreeterService);
console.log(greeter.greet('World')); // Hello, World!

Adding Drivers

Drivers implement abstract service contracts. For example, to add caching:

typescript
import { CacheModule } from '@modularityjs/cache';
import { CacheMemoryModule } from '@modularityjs/cache-memory';

const app = await createApp({
  di: inversify,
  modules: [
    ModularityModule,
    CacheModule,
    CacheMemoryModule, // in-memory for development
    GreeterModule,
  ],
});

To switch to Redis in production, replace the driver:

typescript
import { CacheRedisModule } from '@modularityjs/cache-redis';
import { RedisModule } from '@modularityjs/redis';

const app = await createApp({
  di: inversify,
  modules: [
    ModularityModule,
    RedisModule.forRoot({ host: 'redis.prod.internal' }),
    CacheModule,
    CacheRedisModule, // Redis for production
    GreeterModule,
  ],
});

No changes to your application code. The CacheService contract is the same — only the driver changed.

Lifecycle

Applications have a two-phase lifecycle:

  1. Boot (createApp()) — loads modules, resolves DI, validates contracts, runs afterLoad then onInit hooks
  2. Start (app.start()) — runs onReady hooks (starts HTTP servers, schedulers, etc.)

Shutdown is handled automatically via signal handlers, or manually:

typescript
await app.shutdown(); // runs onShutdown, then onDestroy

Next Steps

  • Architecture — understand how the module system works
  • Modules — deep dive into @Module, lifecycle hooks, and pools
  • Configuration — config sources, schemas, and validation
  • Packages — available contracts and drivers