Recipe: Feature Module
Feature modules are the primary extension mechanism in CruzJS. They allow you to register DI bindings, tRPC routers, React Router routes, and event listeners using the @Module decorator and createCruzApp().
Creating a Module
Section titled “Creating a Module”Use the @Module decorator to declare everything a feature contributes:
import { Module } from '@cruzjs/core/di';import { AnalyticsService } from './analytics.service';import { AnalyticsTracker } from './analytics-tracker.service';import { analyticsRouter } from './analytics.router';import { UserRegisteredEvent } from '@cruzjs/core/auth/events/user-registered.event';import { trackRegistration } from './listeners/track-registration.listener';
@Module({ providers: [ AnalyticsService, AnalyticsTracker, ], trpcRouters: { analytics: analyticsRouter, }, events: [ { event: UserRegisteredEvent, listener: trackRegistration, }, ],})export class AnalyticsModule {}Register it in createCruzApp():
import { createCruzApp } from '@cruzjs/core';import { CloudflareAdapter } from '@cruzjs/adapter-cloudflare';import * as schema from './database/schema';import { AnalyticsModule } from './features/analytics/analytics.module';
export default createCruzApp({ schema, modules: [AnalyticsModule], adapter: new CloudflareAdapter(), pages: () => import('virtual:react-router/server-build'),});What @Module Does
Section titled “What @Module Does”The @Module decorator declares:
providers— Classes to register in the DI container (auto-bound as singletons)trpcRouters— tRPC routers to merge into the application routerevents— Event-listener pairs to register with the EventEmitterServicepageRoutes— React Router page routes contributed by this feature
Module Lifecycle
Section titled “Module Lifecycle”The framework loads modules in a defined order:
- Core modules load first (Auth, Org, Billing, Email, Jobs, Admin, Upload, AI)
- User modules load next (your app’s modules, in the order specified in
createCruzApp)
Within each module, the lifecycle is:
- Module loading —
@Moduleproviders, routers, and events are collected - Router registration — tRPC routers are merged
- Route registration — React Router routes are added
- Event registration — Event listeners are attached
Advanced DI Bindings
Section titled “Advanced DI Bindings”For bindings that go beyond simple class registration, use provider objects in @Module:
import { Module } from '@cruzjs/core/di';import { SearchService } from './search.service';import { SEARCH_CLIENT } from './tokens';import { ConfigService } from '@cruzjs/core';
@Module({ providers: [ SearchService, // Factory-based binding with runtime logic { provide: SEARCH_CLIENT, useFactory: (config: ConfigService) => { const apiKey = config.get<string>('SEARCH_API_KEY'); if (!apiKey) return new LocalSearchClient(); return new AlgoliaSearchClient(apiKey); }, inject: [ConfigService], }, ], trpcRouters: { search: searchRouter, },})export class SearchModule {}Adding Event Listeners
Section titled “Adding Event Listeners”@Module({ providers: [NotificationService], events: [ { event: MemberAddedEvent, listener: sendWelcomeNotification }, { event: InvitationCreatedEvent, listener: sendInvitationNotification }, { event: PaymentFailedEvent, listener: alertBillingAdmin }, ],})export class NotificationModule {}Multiple Feature Modules
Section titled “Multiple Feature Modules”For larger applications, organize features into separate modules and pass them all to createCruzApp():
import { createCruzApp } from '@cruzjs/core';import { CloudflareAdapter } from '@cruzjs/adapter-cloudflare';import * as schema from './database/schema';import { ProjectModule } from './features/projects/project.module';import { ReportModule } from './features/reports/report.module';import { NotificationModule } from './features/notifications/notification.module';import { IntegrationModule } from './features/integrations/integration.module';
export default createCruzApp({ schema, modules: [ProjectModule, ReportModule, NotificationModule, IntegrationModule], adapter: new CloudflareAdapter(), pages: () => import('virtual:react-router/server-build'),});Extending Core Behavior
Section titled “Extending Core Behavior”Override or extend core services by rebinding tokens in your module:
import { Module } from '@cruzjs/core/di';import { SessionService } from '@cruzjs/core/auth/session.service';import { CustomSessionService } from './custom-session.service';
@Module({ providers: [ // Replace the default session service with a custom one { provide: SessionService, useClass: CustomSessionService }, ],})export class AuthExtensionModule {}Next Steps
Section titled “Next Steps”- CRUD Feature Recipe — Build a complete feature
- Adding a Package — Create new monorepo packages
- Architecture — Understand the module lifecycle