Skapa konfigurerbara dynamiska moduler
Skapa providers förRoot och forRootAsync med ConfigurableModuleBuilder för återanvändbara klientmoduler.
Skapa konfigurerbara dynamiska moduler är en gratis lektion i NestJS: backend-API:er för företag på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för NestJS: backend-API:er för företag, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i NestJS: backend-API:er för företag innehåller totalt 4 lektioner.
Varför dynamiska moduler?
En vanlig NestJS-modul exporterar en fast uppsättning providers. Men en återanvändbar modul – som en tenant-identifierare, en cacheklient eller en HTTP-wrapper – behöver konfiguration från användaren: en anslutningssträng, en API-nyckel eller en tenant-strategi.
En dynamisk modul är en modul vars providers, imports och exports beräknas vid import utifrån alternativ som tillhandahålls av anroparen. Konventionen är en statisk factory-metod, nästan alltid med namnet forRoot() (eller register()), som returnerar ett DynamicModule-objekt.
forRoot()– konfigurera en gång, globalt (databas, tenant-register)register()– konfigurera per import, eventuellt flera gångerforFeature()– registrera funktionsspecifika underresurser
DynamicModule-strukturen
En statisk factory måste returnera ett objekt som följer gränssnittet DynamicModule. Det viktigaste extra fältet är module, som pekar tillbaka på värdklassen. Allt annat motsvarar en vanlig @Module()-decorator.
Här omvandlar TenantModule.forRoot() anroparens alternativ till en provider som identifieras av en token och exporterar den sedan så att andra moduler kan injicera den.
import { DynamicModule, Module } from '@nestjs/common';
export interface TenantOptions {
registryUrl: string;
defaultTenant: string;
}
export const TENANT_OPTIONS = 'TENANT_OPTIONS';
@Module({})
export class TenantModule {
static forRoot(options: TenantOptions): DynamicModule {
return {
module: TenantModule,
providers: [{ provide: TENANT_OPTIONS, useValue: options }],
exports: [TENANT_OPTIONS],
};
}
}Använda forRoot
Användaren importerar den dynamiska modulen genom att anropa factoryn, inte genom att bara referera till klassen. Providern för de returnerade alternativen kan injiceras var som helst inom modulens scope.
En tjänst injicerar token TENANT_OPTIONS för att läsa konfigurationen.
import { Inject, Injectable, Module } from '@nestjs/common';
import { TenantModule, TENANT_OPTIONS, TenantOptions } from './tenant.module';
@Injectable()
export class TenantService {
constructor(@Inject(TENANT_OPTIONS) private readonly opts: TenantOptions) {}
resolve(header?: string): string {
return header ?? this.opts.defaultTenant;
}
}
@Module({
imports: [
TenantModule.forRoot({
registryUrl: 'https://registry.internal/tenants',
defaultTenant: 'acme',
}),
],
})
export class AppModule {}Problemet: asynkron konfiguration
Synkrona forRoot(options) fungerar endast när ni redan har de konkreta värdena. I verkliga backend-system finns registryUrl i ConfigService, en secrets manager eller en annan asynkron källa.
Därför exponerar återanvändbara moduler en andra factory: forRootAsync(). Den låter anroparen tillhandahålla alternativ via useFactory, useClass eller useExisting – och framför allt injicera andra providers som hämtar värdena.
- useFactory – inline asynkron funktion som returnerar alternativen
- useClass / useExisting – en klass som implementerar ett options factory-gränssnitt
Skriva forRootAsync för hand
Innan ni tar hjälp av hjälpfunktioner är det värdefullt att se hur mekaniken fungerar. forRootAsync tar emot ett objekt med asynkrona alternativ, deklarerar en lista med imports (så att ConfigModule är synlig) och kopplar anroparens useFactory till options-providern.
Factoryns array inject matar in beroenden i useFactory, precis som hos alla andra providers.
import { DynamicModule, Module, Provider } from '@nestjs/common';
import { TenantOptions, TENANT_OPTIONS } from './tenant.module';
export interface TenantAsyncOptions {
imports?: any[];
inject?: any[];
useFactory: (...args: any[]) => Promise<TenantOptions> | TenantOptions;
}
@Module({})
export class TenantModule {
static forRootAsync(async: TenantAsyncOptions): DynamicModule {
const optionsProvider: Provider = {
provide: TENANT_OPTIONS,
useFactory: async.useFactory,
inject: async.inject ?? [],
};
return {
module: TenantModule,
imports: async.imports ?? [],
providers: [optionsProvider],
exports: [TENANT_OPTIONS],
};
}
}Anropa forRootAsync
Nu hämtar användaren värden från ConfigService vid körning. Fältet imports gör ConfigModule tillgänglig inuti den dynamiska modulen, så att factoryn kan injicera ConfigService.
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TenantModule } from './tenant.module';
@Module({
imports: [
ConfigModule.forRoot(),
TenantModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
registryUrl: config.getOrThrow<string>('TENANT_REGISTRY_URL'),
defaultTenant: config.get<string>('DEFAULT_TENANT', 'acme'),
}),
}),
],
})
export class AppModule {}Introduktion till ConfigurableModuleBuilder
Att skriva båda factory-metoderna för hand är standardkod som varje återanvändbar modul upprepar. Sedan NestJS 9 genererar ConfigurableModuleBuilder kopplingen för forRoot/forRootAsync åt er från en enda optionstyp.
Ni skapar en liten fil med namnet *.module-definition.ts som anropar buildern och exporterar:
ConfigurableModuleClass– en basklass som modulen utökarMODULE_OPTIONS_TOKEN– injektionstoken för de upplösta alternativenOPTIONS_TYPE/ASYNC_OPTIONS_TYPE– typade signaturer för de genererade metoderna
Definiera moduldefinitionen
Byggaren är generisk utifrån ert gränssnitt för alternativ. När ni anropar .build() returneras allt som modulen behöver. Er modul utökar sedan ConfigurableModuleClass och får direkt forRoot() och forRootAsync().
import { ConfigurableModuleBuilder } from '@nestjs/common';
export interface TenantModuleOptions {
registryUrl: string;
defaultTenant: string;
}
export const {
ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN,
OPTIONS_TYPE,
ASYNC_OPTIONS_TYPE,
} = new ConfigurableModuleBuilder<TenantModuleOptions>().build();Koppla in modulk Klassen
Värdmodulen utökar den genererade basklassen och registrerar sina faktiska providers. Tjänster injicerar MODULE_OPTIONS_TOKEN för att läsa de fastställda alternativen — oavsett om de kom från forRoot eller forRootAsync är token samma.
Obs! När ni lägger till egna providers/exports tillsammans med de genererade ska ni behålla dekoratorn @Module() — byggaren slår ihop den dynamiska delen med era statiska metadata.
import { Module } from '@nestjs/common';
import {
ConfigurableModuleClass,
MODULE_OPTIONS_TOKEN,
} from './tenant.module-definition';
import { TenantService } from './tenant.service';
@Module({
providers: [TenantService],
exports: [TenantService, MODULE_OPTIONS_TOKEN],
})
export class TenantModule extends ConfigurableModuleClass {}Anpassat metodnamn och extra alternativ
Byggaren kan konfigureras. Använd .setClassMethodName('register') när registrering per import passar bättre än forRoot. Använd .setExtras() för att lägga till fält som inte ingår i de injicerade alternativen — vanligtvis isGlobal — samt en transformering som injicerar dem i DynamicModule (till exempel genom att ange global: true).
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { TenantModuleOptions } from './tenant.types';
export interface TenantExtras {
isGlobal?: boolean;
}
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =
new ConfigurableModuleBuilder<TenantModuleOptions>()
.setExtras<TenantExtras>(
{ isGlobal: false },
(definition, extras) => ({
...definition,
global: extras.isGlobal,
}),
)
.setClassMethodName('register')
.build();Vinsten med flera klientorganisationer
Sammantaget blir en klientorganisationsmodul ett färdigt beroende för alla tjänster i plattformen. En provider med request-scope kan fastställa den aktiva klientorganisationen från en header och falla tillbaka till det konfigurerade standardvärdet — allt styrs av de alternativ som den konsumerande appen angav via register/registerAsync.
Den viktiga insikten är att konfigurationen flödar genom en enda token (MODULE_OPTIONS_TOKEN), så interna tjänster behöver aldrig bry sig om huruvida konfigurationen gjordes synkront eller asynkront, eller om värdena angavs direkt eller hämtades från en fabrik.
import { Inject, Injectable, Scope } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
import { MODULE_OPTIONS_TOKEN } from './tenant.module-definition';
import { TenantModuleOptions } from './tenant.types';
@Injectable({ scope: Scope.REQUEST })
export class TenantContext {
constructor(
@Inject(MODULE_OPTIONS_TOKEN) private readonly opts: TenantModuleOptions,
@Inject(REQUEST) private readonly req: Request,
) {}
get tenantId(): string {
const header = this.req.headers['x-tenant-id'];
return (Array.isArray(header) ? header[0] : header) ?? this.opts.defaultTenant;
}
}Snabb kontroll
Er återanvändbara TenantModule måste läsa registryUrl från ConfigService, som i sin tur laddas asynkront vid uppstarten. Vilket tillvägagångssätt låter konsumenten koppla in detta korrekt?
Sammanfattning
Ni byggde en konfigurerbar dynamisk modul från början till slut:
- En dynamisk modul returnerar en
DynamicModulefrån en statisk fabrik (forRoot/register) och omvandlar anroparens alternativ till en tokenbaserad provider. - forRootAsync lägger till
imports,injectochuseFactory, så att alternativen kan hämtas frånConfigServiceeller andra asynkrona källor. ConfigurableModuleBuildergenererar båda fabrikerna från en enda alternativtyp och exponerarConfigurableModuleClassochMODULE_OPTIONS_TOKEN..setClassMethodName()byter namn på fabriken och.setExtras()lägger till fristående flaggor somisGlobal.- Alla interna tjänster injicerar samma alternativtoken och förblir oberoende av hur konfigurationen tillhandahölls — grunden för återanvändbara moduler för flera klientorganisationer.
Lär dig TypeScript med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 20
- Lektioner
- 76
Vanliga frågor
Är lektionen ”Skapa konfigurerbara dynamiska moduler” gratis?
Ja – hela texten till ”Skapa konfigurerbara dynamiska moduler” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i NestJS: backend-API:er för företag, kan Ni uppgradera till CoddyKit PRO. Kursen i NestJS: backend-API:er för företag innehåller totalt 4 lektioner.
Vad lär jag mig i ”Skapa konfigurerbara dynamiska moduler”?
Skapa providers förRoot och forRootAsync med ConfigurableModuleBuilder för återanvändbara klientmoduler. Ni övar på NestJS: backend-API:er för företag med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig NestJS: backend-API:er för företag?
Du behöver inga förkunskaper. Utbildningen i NestJS: backend-API:er för företag på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.
Hur lång tid tar lektionen ”Skapa konfigurerbara dynamiska moduler”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här NestJS: backend-API:er för företag-lektionen?
Ja. Varje NestJS: backend-API:er för företag-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Identifiering av klient via middleware och AsyncLocalStorage
- Databasanslutningar med schema per klient
- Skapa konfigurerbara dynamiska moduler
- Request-scopeade providers och deras avvägningar