Skip to main content

Configuring

MBC CQRS Serverless framework allows you to customize your project to meet specific requirements. This guide covers all configuration options available in the framework.

Project Configuration

serverless.yml

The main configuration file for your serverless application.

Table Prefix

The framework generates DynamoDB table names automatically using NODE_ENV and APP_NAME environment variables plus a type suffix. The format is {NODE_ENV}-{APP_NAME}-{tableName}-command for command tables and {NODE_ENV}-{APP_NAME}-{tableName}-data for data tables. For example, with NODE_ENV=dev, APP_NAME=my-app, and tableName: 'order', the tables are dev-my-app-order-command and dev-my-app-order-data.

service: my-app

frameworkVersion: '3'

plugins:
- serverless-offline
- serverless-dynamodb

provider:
name: aws
runtime: nodejs20.x
stage: ${opt:stage, 'dev'}
region: ${opt:region, 'ap-northeast-1'}
memorySize: 512
timeout: 30

environment:
NODE_ENV: ${self:provider.stage}
APP_NAME: ${self:service}
COGNITO_USER_POOL_ID: ${env:COGNITO_USER_POOL_ID}
COGNITO_USER_POOL_CLIENT_ID: ${env:COGNITO_USER_POOL_CLIENT_ID}

iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- "arn:aws:dynamodb:${self:provider.region}:*:table/${self:provider.stage}-${self:service}-*"

custom:
serverless-offline:
httpPort: 3000
lambdaPort: 3002

serverless-dynamodb:
stages:
- dev
start:
port: 8000
inMemory: true
migrate: false

NestJS Configuration

Configure NestJS modules in your application.

// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { CommandModule } from '@mbc-cqrs-serverless/core';

@Module({
imports: [
// Load environment variables
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env.local', '.env'],
}),

// Configure Command Module
CommandModule.register({
tableName: 'order', // Logical name; actual table: {NODE_ENV}-{APP_NAME}-order-command
dataSyncHandlers: [],
}),
],
})
export class AppModule {}

TypeScript Configuration

tsconfig.json

Recommended TypeScript configuration for MBC CQRS Serverless projects.

{
"compilerOptions": {
"module": "commonjs",
"target": "ES2021",
"lib": ["ES2021"],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@shared/*": ["src/shared/*"]
},
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

The paths option enables module path aliases (e.g. @/ for src/). See Absolute Imports and Module Path Aliases for details.

ESLint Configuration

.eslintrc.js

Recommended ESLint configuration.

module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js', 'dist', 'node_modules'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
},
};

Module Configuration

CommandModule Options

Configure the core CommandModule.

import { Module } from '@nestjs/common';
import { CommandModule } from '@mbc-cqrs-serverless/core';
import { OrderRdsSyncHandler } from './order-rds-sync.handler'; // Your IDataSyncHandler implementation — see service-patterns

@Module({
imports: [
CommandModule.register({
// Required: DynamoDB table name
tableName: 'order',

// Optional: Data sync handlers for RDS synchronization
dataSyncHandlers: [OrderRdsSyncHandler],

// Optional: Reserved for future use (not yet implemented)
skipError: false,

// Optional: Disable default DynamoDB data sync handler
disableDefaultHandler: false,
}),
],
})
export class OrderModule {}

SequencesModule Options

Configure auto-incrementing sequences.

import { Module } from '@nestjs/common';
import { SequencesModule } from '@mbc-cqrs-serverless/sequence';

@Module({
imports: [
SequencesModule.register({
// Optional: Enable or disable default sequence controller
enableController: true,
}),
],
})
export class AppModule {}

Note: Rotation strategy (day, month, year, fiscal_yearly, none) is configured through master data settings, not module options. See the Sequence documentation for details.

TenantModule Options

Configure multi-tenant support.

import { Module } from '@nestjs/common';
import { TenantModule } from '@mbc-cqrs-serverless/tenant';
import { TenantRdsSyncHandler } from './tenant-rds-sync.handler'; // Your IDataSyncHandler implementation — see service-patterns

@Module({
imports: [
TenantModule.register({
// Optional: Enable or disable default tenant controller
enableController: true,

// Optional: Data sync handlers for RDS synchronization
dataSyncHandlers: [TenantRdsSyncHandler],
}),
],
})
export class AppModule {}

NotificationModule

Configure email and real-time notifications via environment variables.

import { Module } from '@nestjs/common';
import { NotificationModule } from '@mbc-cqrs-serverless/core';

@Module({
imports: [
// NotificationModule is a global module - just import it
NotificationModule,
],
})
export class AppModule {}

NotificationModule is configured through environment variables:

# Email (SES) configuration
# Required: Default sender email address
SES_FROM_EMAIL=noreply@example.com

# Optional: AWS SES endpoint (for local development)
SES_ENDPOINT=http://localhost:4566

# Optional: AWS SES region
SES_REGION=ap-northeast-1

# Real-time transport configuration
# Optional: Active transports — default is appsync-graphql (GraphQL Subscriptions)
# Set to appsync-event for AppSync Events API (v1.3.0+), or comma-separated for dual-publish
NOTIFICATION_TRANSPORTS=appsync-graphql

# Required when NOTIFICATION_TRANSPORTS includes appsync-graphql
APPSYNC_ENDPOINT=https://xxxx.appsync-api.ap-northeast-1.amazonaws.com/graphql
APPSYNC_API_KEY=da2-xxxx

# Required when NOTIFICATION_TRANSPORTS includes appsync-event (v1.3.0+)
# APPSYNC_EVENTS_ENDPOINT=https://xxxx.appsync-api.ap-northeast-1.amazonaws.com/event

See Notification Module for full AppSync transport setup and real-time subscription patterns.

Logging Configuration

Configure Logger

Set up structured logging for production.

import { Injectable, Logger } from '@nestjs/common';

// Configure log level based on environment
const LOG_LEVELS = {
production: ['error', 'warn', 'log'],
development: ['error', 'warn', 'log', 'debug', 'verbose'],
test: ['error', 'warn'],
};

// In main.ts or bootstrap
const app = await NestFactory.create(AppModule, {
logger: LOG_LEVELS[process.env.NODE_ENV] || LOG_LEVELS.development,
});

// Structured logging in services
@Injectable()
export class OrderService {
private readonly logger = new Logger(OrderService.name);

async createOrder(dto: CreateOrderDto): Promise<Order> {
this.logger.log({
message: 'Creating order',
code: dto.code,
tenantCode: dto.tenantCode,
});

// ... implementation
}
}

Validation Configuration

Global Validation Pipe

Configure validation for all endpoints.

import { BadRequestException, ValidationPipe } from '@nestjs/common';

// In main.ts or bootstrap
app.useGlobalPipes(
new ValidationPipe({
// Transform payloads to DTO instances
transform: true,

// Strip properties not in DTO
whitelist: true,

// Throw error for extra properties
forbidNonWhitelisted: true,

// Transform primitive types
transformOptions: {
enableImplicitConversion: true,
},

// Custom error messages
exceptionFactory: (errors) => {
const messages = errors.map(error => ({
field: error.property,
errors: Object.values(error.constraints || {}),
}));
return new BadRequestException({
message: 'Validation failed',
errors: messages,
});
},
}),
);

CORS Configuration

Configure CORS

Set up Cross-Origin Resource Sharing.

// In main.ts
app.enableCors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || [
'http://localhost:3000',
],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-Id'],
credentials: true,
maxAge: 3600,
});

Stage-Specific Configuration

Environment-Based Settings

Configure different settings per environment.

// config/configuration.ts
export default () => ({
stage: process.env.STAGE || 'dev',

database: {
tablePrefix: `myapp-${process.env.STAGE}`,
commandTable: `myapp-${process.env.STAGE}-command`,
dataTable: `myapp-${process.env.STAGE}-data`,
},

aws: {
region: process.env.AWS_REGION || 'ap-northeast-1',
dynamodbEndpoint: process.env.DYNAMODB_ENDPOINT, // For local dev
},

auth: {
userPoolId: process.env.COGNITO_USER_POOL_ID,
clientId: process.env.COGNITO_USER_POOL_CLIENT_ID,
},

features: {
enableDebugLogs: process.env.STAGE === 'dev',
enableMetrics: process.env.STAGE === 'prod',
},
});

// Usage
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AppService {
constructor(private configService: ConfigService) {}

getTableName(): string {
return this.configService.get<string>('database.commandTable');
}
}