Project Architecture Design
Layered Architecture Design
Typical Layered Structure Implementation:
// User module example
@Module({
controllers: [UserController],
providers: [UserService, UserRepository],
imports: [TypeOrmModule.forFeature([UserEntity])]
})
export class UserModule {}
// Controller layer
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
async getUser(@Param('id') id: string) {
return this.userService.findById(id)
}
}
// Service layer
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
async findById(id: string): Promise<UserDto> {
const user = await this.userRepository.findById(id)
return plainToInstance(UserDto, user)
}
}
// Repository layer
@Repository()
export class UserRepository {
constructor(
@InjectRepository(UserEntity)
private readonly ormRepository: Repository<UserEntity>
) {}
async findById(id: string): Promise<UserEntity> {
return this.ormRepository.findOne({ where: { id } })
}
}Layer Responsibilities Division:
- Controller Layer: Handles HTTP requests/responses, parameter validation, route mapping
- Service Layer: Implements business logic, transaction management, coordinates domain services
- Repository Layer: Manages data persistence operations, optimizes database queries
Modularization and Functional Decomposition
Functional Module Division Example:
src/
├── auth/ # Authentication module
│ ├── auth.module.ts
│ ├── dto/
│ ├── entities/
│ ├── guards/
│ ├── interceptors/
│ ├── services/
│ └── strategies/
├── user/ # User module
│ ├── user.module.ts
│ ├── dto/
│ ├── entities/
│ ├── repositories/
│ └── services/
└── shared/ # Shared module
├── common/
├── config/
└── utils/Dynamic Module Registration:
// shared/config.module.ts
@Module({})
export class ConfigModule {
static forRoot(config: AppConfig): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: 'APP_CONFIG',
useValue: config
}
],
exports: ['APP_CONFIG']
}
}
}
// Usage in other modules
@Module({
imports: [ConfigModule.forRoot(appConfig)]
})
export class UserModule {}Domain-Driven Design (DDD) Practices
Aggregate Root Implementation Example:
// order/aggregate-root/order.aggregate.ts
export class OrderAggregate {
private constructor(
public readonly id: string,
public readonly userId: string,
public readonly items: OrderItem[],
public readonly status: OrderStatus
) {}
static create(userId: string, items: OrderItemDto[]): OrderAggregate {
// Validate business rules
if (items.length === 0) {
throw new BusinessRuleException('Order must contain items')
}
const id = generateId()
const orderItems = items.map(item => new OrderItem(item.productId, item.quantity))
return new OrderAggregate(id, userId, orderItems, 'PENDING')
}
addItem(item: OrderItemDto): void {
if (this.status !== 'PENDING') {
throw new BusinessRuleException('Cannot add items to completed orders')
}
this.items.push(new OrderItem(item.productId, item.quantity))
}
}
// Handling aggregates in Repository
@Injectable()
export class OrderRepository {
async save(aggregate: OrderAggregate): Promise<void> {
await this.ormRepository.save({
id: aggregate.id,
userId: aggregate.userId,
items: aggregate.items.map(i => ({ productId: i.productId, quantity: i.quantity })),
status: aggregate.status
})
}
}Project Directory Structure Standards
Recommended Directory Structure:
src/
├── app.module.ts # Root module
├── main.ts # Application entry point
├── config/ # Configuration-related
│ ├── configuration.ts
│ └── validation.pipe.ts
├── common/ # Shared module
│ ├── decorators/
│ ├── filters/
│ ├── interceptors/
│ └── utils/
├── modules/ # Functional modules
│ ├── auth/
│ ├── user/
│ └── product/
└── test/ # Test code
├── e2e/
└── unit/Naming Conventions:
- Modules:
<feature>.module.ts - Controllers:
<feature>.controller.ts - Services:
<feature>.service.ts - Entities:
<entity>.entity.ts - DTOs:
<feature>.dto.ts
Configuration File Management
Multi-Environment Configuration Implementation:
// config/configuration.ts
export default () => ({
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
username: process.env.DB_USERNAME || 'postgres',
password: process.env.DB_PASSWORD || 'postgres',
database: process.env.DB_NAME || 'nestjs'
},
redis: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT) || 6379
}
})
// app.module.ts
@Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
envFilePath: `.env.${process.env.NODE_ENV || 'development'}`
})
]
})
export class AppModule {}Configuration Validation:
// config/validation.pipe.ts
export class ValidationPipe implements PipeTransform {
constructor(private readonly validator: Validator) {}
transform(value: any, metadata: ArgumentMetadata) {
const object = plainToClass(metadata.metatype, value)
const errors = this.validator.validate(object)
if (errors.length > 0) {
throw new BadRequestException('Validation failed')
}
return object
}
}Engineering Toolchain
NestJS CLI Advanced Configuration
Custom Generator Implementation:
// tools/generators/entity.generator.ts
export class EntityGenerator implements Generator {
generate(options: EntityGeneratorOptions): void {
const { name, path } = options
const fileContent = `
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'
@Entity('${name.toLowerCase()}')
export class ${capitalize(name)} {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
}
`
writeFileSync(join(path, `${name}.entity.ts`), fileContent)
}
}
// Register custom generator
nest g -c tools/generators entity User --path=modules/user/entitiesWebpack Advanced Configuration
Custom Webpack Configuration:
// webpack.config.js
module.exports = (options, webpack) => {
return {
...options,
module: {
rules: [
...options.module.rules,
{
test: /\.graphql$/,
loader: 'graphql-tag/loader'
}
]
},
plugins: [
...options.plugins,
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE ? 'server' : 'disabled'
})
],
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
}
}TypeScript Advanced Configuration
Advanced tsconfig Settings:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@app/*": ["src/*"],
"@config/*": ["src/config/*"],
"@common/*": ["src/common/*"]
}
},
"exclude": ["node_modules", "dist"]
}Code Standardization Toolchain
ESLint Configuration Example:
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module'
},
plugins: ['@typescript-eslint', 'prettier'],
extends: [
'plugin:@typescript-eslint/recommended',
'prettier'
],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'prettier/prettier': 'error'
}
}Prettier Configuration:
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always"
}Automated Testing Framework
Unit Test Example:
// user.service.spec.ts
describe('UserService', () => {
let service: UserService
let repository: UserRepository
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: UserRepository,
useValue: {
findOne: jest.fn(),
create: jest.fn()
}
}
]
}).compile()
service = module.get<UserService>(UserService)
repository = module.get<UserRepository>(UserRepository)
})
it('should be defined', () => {
expect(service).toBeDefined()
})
describe('findOne', () => {
it('should return a user', async () => {
const user = { id: '1', name: 'Test' }
jest.spyOn(repository, 'findOne').mockResolvedValue(user)
const result = await service.findOne('1')
expect(result).toEqual(user)
})
})
})Integration Test Example:
// user.controller.e2e-spec.ts
describe('UserController (e2e)', () => {
let app: INestApplication
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule]
}).compile()
app = moduleFixture.createNestApplication()
await app.init()
})
it('/users (GET) should return 200', () => {
return request(app.getHttpServer())
.get('/users')
.expect(200)
})
})Performance Optimization and Deployment
Cache Strategy Implementation
Redis Cache Integration:
// cache.module.ts
@Module({
imports: [
CacheModule.registerAsync({
useFactory: async (config: ConfigService) => ({
store: redisStore,
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT'),
ttl: 60 // seconds
}),
inject: [ConfigService]
})
],
exports: [CacheModule]
})
export class CacheConfigModule {}
// Using cache in a service
@Injectable()
export class ProductService {
constructor(
private readonly repository: ProductRepository,
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache
) {}
async findOne(id: string): Promise<Product> {
const cached = await this.cacheManager.get<Product>(`product:${id}`)
if (cached) {
return cached
}
const product = await this.repository.findOne(id)
await this.cacheManager.set(`product:${id}`, product, 60)
return product
}
}Load Balancing and Clustering
PM2 Cluster Mode Configuration:
// ecosystem.config.js
module.exports = {
apps: [
{
name: 'nestjs-api',
script: 'dist/main.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
max_memory_restart: '1G'
}
]
}Node.js Cluster Implementation:
// cluster.ts
if (cluster.isPrimary) {
const numCPUs = os.cpus().length
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died`)
cluster.fork()
})
} else {
await app.listen(3000)
console.log(`Worker ${process.pid} started`)
}Database Query Optimization
TypeORM Query Optimization:
// 1. Using indexes
@Entity()
@Index(['email']) // Add index to email field
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true })
email: string
}
// 2. Batch operations
await repository
.createQueryBuilder()
.insert()
.into(User)
.values([
{ name: 'User1' },
{ name: 'User2' }
])
.execute()
// 3. Lazy and eager loading
@Entity()
export class Order {
@PrimaryGeneratedColumn()
id: number
@ManyToOne(() => User, user => user.orders, { eager: true }) // Eager loading
user: User
}Logging and Monitoring
Winston Logging Configuration:
// logger.module.ts
@Module({
providers: [
{
provide: 'LOGGER',
useFactory: () => {
return winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
})
}
}
],
exports: ['LOGGER']
})
export class LoggerModule {}
// Using logger
@Injectable()
export class UserService {
constructor(@Inject('LOGGER') private readonly logger: winston.Logger) {}
async createUser(user: UserDto) {
this.logger.info('Creating user', { user })
try {
// ...
} catch (error) {
this.logger.error('Failed to create user', { error })
throw error
}
}
}ELK Integration:
// elk.logger.ts
export class ElkLogger {
constructor(private readonly config: ConfigService) {}
log(message: string, context?: string, metadata?: any) {
const logEntry = {
timestamp: new Date().toISOString(),
message,
context,
...metadata
}
// Send to Logstash
axios.post(this.config.get('LOGSTASH_URL'), logEntry)
}
}Deployment and CI/CD
Dockerfile Example:
# Build stage
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
EXPOSE 3000
CMD ["node", "dist/main.js"]CI/CD Pipeline Example:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
- run: npm run build
- uses: azure/webapps-deploy@v2
with:
app-name: 'nestjs-api'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: ./dist



