registerAsによる名前空間付きConfig
関連する設定を型付きのconfig namespaceにまとめ、ConfigService.getで注入します
「registerAsによる名前空間付きConfig」はCoddyKit上の無料NestJS Enterprise Backend APIsレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはNestJS Enterprise Backend APIs学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 NestJS Enterprise Backend APIsコースには全4レッスンが含まれています。
このレッスンの一部はまだ翻訳されておらず、英語で表示されています。
The Problem With Flat Config
As an API grows, dumping every setting into one giant ConfigService namespace becomes unmanageable. You end up with calls like config.get('DB_HOST'), config.get('REDIS_PORT'), and config.get('JWT_SECRET') scattered everywhere with no grouping and no type safety.
- No structure — database, cache, and auth keys all live at the same flat level.
- No types —
get()returnsunknownor a loosely typed value. - Hard to refactor — renaming a key means hunting raw strings across the codebase.
NestJS solves this with namespaced configuration via the registerAs helper from @nestjs/config.
Introducing registerAs
registerAs(token, factory) creates a configuration factory bound to a namespace token. The factory reads from process.env and returns a typed object grouping related settings together.
Each namespace becomes its own logical unit — database, jwt, redis — that you can register, inject, and test independently.
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
host: process.env.DB_HOST ?? 'localhost',
port: parseInt(process.env.DB_PORT ?? '5432', 10),
username: process.env.DB_USER ?? 'postgres',
password: process.env.DB_PASSWORD ?? '',
name: process.env.DB_NAME ?? 'app',
}));Registering the Namespace
You load namespaced factories through the load array of ConfigModule.forRoot. Each entry is a factory created by registerAs.
Mark the module isGlobal: true so the ConfigService is available everywhere without re-importing.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import databaseConfig from './config/database.config';
import jwtConfig from './config/jwt.config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [databaseConfig, jwtConfig],
}),
],
})
export class AppModule {}Reading a Namespace With get()
Once registered, the whole namespace is available under its token. Calling configService.get('database') returns the entire grouped object.
You can also reach a single nested value with dotted-path access: configService.get('database.host').
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class DbConnector {
constructor(private readonly config: ConfigService) {}
connect() {
const host = this.config.get<string>('database.host');
const port = this.config.get<number>('database.port');
return `connecting to ${host}:${port}`;
}
}Typing the Namespace
To get real type safety, derive a type from the factory using ConfigType. This infers the exact shape returned by your registerAs factory — no manual interface to keep in sync.
ConfigType<typeof databaseConfig>gives you{ host: string; port: number; ... }.- Autocomplete and compile-time checks now work on every field.
import { ConfigType } from '@nestjs/config';
import databaseConfig from './config/database.config';
// Inferred: { host: string; port: number; username: string; ... }
type DatabaseConfig = ConfigType<typeof databaseConfig>;
function describe(db: DatabaseConfig): string {
return `${db.username}@${db.host}:${db.port}/${db.name}`;
}Injecting the Namespace Directly
The most ergonomic pattern is to inject the namespace token directly instead of going through ConfigService.get everywhere. Use the @Inject(databaseConfig.KEY) decorator — registerAs attaches a KEY property to the factory for exactly this.
Now the field is fully typed and the dependency is explicit in the constructor.
import { Inject, Injectable } from '@nestjs/common';
import { ConfigType } from '@nestjs/config';
import databaseConfig from './config/database.config';
@Injectable()
export class UserRepository {
constructor(
@Inject(databaseConfig.KEY)
private readonly db: ConfigType<typeof databaseConfig>,
) {}
dsn(): string {
return `postgres://${this.db.host}:${this.db.port}/${this.db.name}`;
}
}Why Inject the Token Over ConfigService
Both approaches work, but injecting the namespace token has clear advantages at scale:
- Type-safe — the injected object is fully typed; no
get<T>casts that can silently drift. - Explicit dependencies — the constructor declares exactly which config it needs.
- Smaller surface — a class touching only database config never sees JWT or Redis keys.
- Easier tests — provide a plain object for
databaseConfig.KEYinstead of mockingConfigService.
Multiple Namespaces Side by Side
Real enterprise apps have several namespaces. Each is its own file and factory, registered together in the load array. Keeping them separate means a JWT change never risks breaking database config.
import { registerAs } from '@nestjs/config';
export const jwtConfig = registerAs('jwt', () => ({
secret: process.env.JWT_SECRET ?? 'dev-secret',
accessTtl: process.env.JWT_ACCESS_TTL ?? '15m',
refreshTtl: process.env.JWT_REFRESH_TTL ?? '7d',
}));
export const redisConfig = registerAs('redis', () => ({
host: process.env.REDIS_HOST ?? 'localhost',
port: parseInt(process.env.REDIS_PORT ?? '6379', 10),
ttl: parseInt(process.env.REDIS_TTL ?? '60', 10),
}));Coercion Lives in the Factory
Environment variables are always strings. The namespace factory is the right place to coerce and default them once, so consumers always receive correct types.
This pure helper shows the coercion logic you would put inside a registerAs factory — it can run standalone.
function buildDbConfig(env: Record<string, string | undefined>) {
return {
host: env.DB_HOST ?? 'localhost',
port: parseInt(env.DB_PORT ?? '5432', 10),
ssl: env.DB_SSL === 'true',
poolSize: parseInt(env.DB_POOL_SIZE ?? '10', 10),
};
}
const cfg = buildDbConfig({ DB_PORT: '6543', DB_SSL: 'true' });
console.log(cfg.port, typeof cfg.port); // 6543 'number'
console.log(cfg.ssl, typeof cfg.ssl); // true 'boolean'
console.log(cfg.host); // localhostUsing a Namespace in forRootAsync
Other modules can consume a namespace asynchronously. Inject the namespace token via inject and read the typed object in useFactory — for example, wiring TypeORM from your database namespace.
import { ConfigType } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import databaseConfig from './config/database.config';
TypeOrmModule.forRootAsync({
inject: [databaseConfig.KEY],
useFactory: (db: ConfigType<typeof databaseConfig>) => ({
type: 'postgres',
host: db.host,
port: db.port,
username: db.username,
password: db.password,
database: db.name,
}),
});Common Pitfalls
Watch out for these mistakes when working with namespaced config:
- Forgetting to load the factory in
load: [...]— the namespace will beundefined. - Mixing tokens and paths —
get('database')returns the object;get('database.host')returns the leaf. Don't confuse them. - Re-coercing in consumers — coerce once in the factory; consumers should trust the types.
- Using
process.envdirectly in services instead of injecting the namespace, losing types and testability.
Quick Check
Test your understanding of namespaced config injection.
Recap
You learned how to group settings into typed config namespaces with registerAs:
- registerAs(token, factory) groups related env vars into one named, typed object.
- Register factories via
ConfigModule.forRoot({ load: [...] }). - Read a namespace with
config.get('database')or a leaf withconfig.get('database.host'). - Prefer @Inject(config.KEY) with
ConfigType<typeof config>for full type safety and explicit dependencies. - Do all coercion and defaults inside the factory so consumers trust the types.
This pattern keeps configuration structured, typed, and testable as your enterprise API scales.
AI チューターと学ぶ TypeScript — 無料
ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。
- コース
- 20
- レッスン
- 76
よくある質問
「registerAsによる名前空間付きConfig」レッスンは無料ですか?
はい。「registerAsによる名前空間付きConfig」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、NestJS Enterprise Backend APIsコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 NestJS Enterprise Backend APIsコースには全4レッスンが含まれています。
「registerAsによる名前空間付きConfig」で何を学びますか?
関連する設定を型付きのconfig namespaceにまとめ、ConfigService.getで注入します ブラウザで直接実行するハンズオンコードでNestJS Enterprise Backend APIsを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
NestJS Enterprise Backend APIsを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのNestJS Enterprise Backend APIsは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「registerAsによる名前空間付きConfig」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このNestJS Enterprise Backend APIsレッスンでコードを書いて実行できますか?
はい。すべてのNestJS Enterprise Backend APIsレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- JoiとforRootによるスキーマ検証済みEnv
- registerAsによる名前空間付きConfig
- HashiCorp VaultとAWS SSMからSecretsを読み込む
- 環境別Configと安全なデフォルト値