registerAs를 사용한 네임스페이스 구성
관련 설정을 타입이 지정된 구성 네임스페이스로 묶고 ConfigService.get으로 주입합니다
registerAs를 사용한 네임스페이스 구성은(는) CoddyKit의 무료 NestJS Enterprise Backend APIs 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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.
자주 묻는 질문
“registerAs를 사용한 네임스페이스 구성” 강의는 무료인가요?
네 — “registerAs를 사용한 네임스페이스 구성” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 NestJS Enterprise Backend APIs 강의 전체를 잠금 해제할 수 있습니다. NestJS Enterprise Backend APIs 강의에는 총 4개의 강의가 포함되어 있습니다.
“registerAs를 사용한 네임스페이스 구성”에서 뭘 배우나요?
관련 설정을 타입이 지정된 구성 네임스페이스로 묶고 ConfigService.get으로 주입합니다 브라우저에서 직접 실행하는 실습 코드로 NestJS Enterprise Backend APIs을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
NestJS Enterprise Backend APIs을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 NestJS Enterprise Backend APIs은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.
“registerAs를 사용한 네임스페이스 구성” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 NestJS Enterprise Backend APIs 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 NestJS Enterprise Backend APIs 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- Joi와 forRoot를 사용한 스키마 검증 환경 변수
- registerAs를 사용한 네임스페이스 구성
- HashiCorp Vault와 AWS SSM에서 비밀 값 불러오기
- 환경별 구성과 안전한 기본값