Skip to content

模块

什么是模块?

想象一下,你在组织一个大型公司:

  • 公司有不同的部门(模块):人事部、财务部、技术部等
  • 每个部门有自己的员工(服务)和职责(控制器)
  • 部门之间可以相互合作(导入/导出)
  • 有些资源可以共享给其他部门使用

在 NestJS 中,模块(Module) 就是这样的"部门",它是应用程序的基本组织单元。

模块的基本结构

1. 最简单的模块

typescript
import { Module } from '@nestjs/common';

@Module({
  controllers: [], // 控制器:处理HTTP请求
  providers: [], // 服务提供者:业务逻辑
  imports: [], // 导入其他模块
  exports: [], // 导出给其他模块使用
})
export class AppModule {}

2. 实际的模块示例

typescript
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController], // 这个模块的控制器
  providers: [CatsService], // 这个模块的服务
})
export class CatsModule {}

模块的四个核心属性详解

1. Controllers(控制器)

控制器负责处理 HTTP 请求:

typescript
// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { CatsService } from './cats.service';

@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @Get()
  findAll() {
    return this.catsService.findAll();
  }

  @Post()
  create(@Body() createCatDto: any) {
    return this.catsService.create(createCatDto);
  }
}

// cats.module.ts
@Module({
  controllers: [CatsController], // 注册控制器
  providers: [CatsService],
})
export class CatsModule {}

2. Providers(服务提供者)

服务提供者包含业务逻辑:

typescript
// cats.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class CatsService {
  private cats = [];

  findAll() {
    return this.cats;
  }

  create(cat: any) {
    this.cats.push(cat);
    return cat;
  }
}

// cats.module.ts
@Module({
  controllers: [CatsController],
  providers: [CatsService], // 注册服务
})
export class CatsModule {}

3. Imports(导入)

导入其他模块来使用它们的服务:

typescript
// database.module.ts
@Module({
  providers: [DatabaseService],
  exports: [DatabaseService], // 导出服务供其他模块使用
})
export class DatabaseModule {}

// users.module.ts
@Module({
  imports: [DatabaseModule], // 导入数据库模块
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

4. Exports(导出)

导出服务供其他模块使用:

typescript
// shared.module.ts
@Module({
  providers: [SharedService, LoggerService],
  exports: [SharedService, LoggerService], // 导出多个服务
})
export class SharedModule {}

// feature.module.ts
@Module({
  imports: [SharedModule], // 现在可以使用 SharedService 和 LoggerService
  controllers: [FeatureController],
  providers: [FeatureService],
})
export class FeatureModule {}

实际应用示例

1. 用户管理模块

typescript
// user.entity.ts
export class User {
  id: number;
  name: string;
  email: string;
}

// user.service.ts
import { Injectable } from '@nestjs/common';
import { User } from './user.entity';

@Injectable()
export class UserService {
  private users: User[] = [];

  findAll(): User[] {
    return this.users;
  }

  findById(id: number): User {
    return this.users.find((user) => user.id === id);
  }

  create(userData: Partial<User>): User {
    const user = {
      id: Date.now(),
      ...userData,
    } as User;
    this.users.push(user);
    return user;
  }

  update(id: number, updateData: Partial<User>): User {
    const userIndex = this.users.findIndex((user) => user.id === id);
    if (userIndex !== -1) {
      this.users[userIndex] = { ...this.users[userIndex], ...updateData };
      return this.users[userIndex];
    }
    return null;
  }

  delete(id: number): boolean {
    const userIndex = this.users.findIndex((user) => user.id === id);
    if (userIndex !== -1) {
      this.users.splice(userIndex, 1);
      return true;
    }
    return false;
  }
}

// user.controller.ts
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Body,
  Param,
} from '@nestjs/common';
import { UserService } from './user.service';

@Controller('users')
export class UserController {
  constructor(private userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findById(parseInt(id));
  }

  @Post()
  create(@Body() createUserDto: any) {
    return this.userService.create(createUserDto);
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateUserDto: any) {
    return this.userService.update(parseInt(id), updateUserDto);
  }

  @Delete(':id')
  delete(@Param('id') id: string) {
    return this.userService.delete(parseInt(id));
  }
}

// user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';

@Module({
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService], // 导出服务供其他模块使用
})
export class UserModule {}

2. 文章管理模块(使用用户服务)

typescript
// post.entity.ts
export class Post {
  id: number;
  title: string;
  content: string;
  authorId: number;
}

// post.service.ts
import { Injectable } from '@nestjs/common';
import { Post } from './post.entity';
import { UserService } from '../user/user.service'; // 使用用户服务

@Injectable()
export class PostService {
  private posts: Post[] = [];

  constructor(private userService: UserService) {}

  findAll(): Post[] {
    return this.posts;
  }

  findByAuthor(authorId: number): Post[] {
    return this.posts.filter((post) => post.authorId === authorId);
  }

  create(postData: Partial<Post>): Post {
    // 验证作者是否存在
    const author = this.userService.findById(postData.authorId);
    if (!author) {
      throw new Error('作者不存在');
    }

    const post = {
      id: Date.now(),
      ...postData,
    } as Post;
    this.posts.push(post);
    return post;
  }

  getPostWithAuthor(postId: number) {
    const post = this.posts.find((p) => p.id === postId);
    if (!post) return null;

    const author = this.userService.findById(post.authorId);
    return {
      ...post,
      author,
    };
  }
}

// post.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { PostService } from './post.service';

@Controller('posts')
export class PostController {
  constructor(private postService: PostService) {}

  @Get()
  findAll() {
    return this.postService.findAll();
  }

  @Get(':id/with-author')
  getPostWithAuthor(@Param('id') id: string) {
    return this.postService.getPostWithAuthor(parseInt(id));
  }

  @Get('author/:authorId')
  findByAuthor(@Param('authorId') authorId: string) {
    return this.postService.findByAuthor(parseInt(authorId));
  }

  @Post()
  create(@Body() createPostDto: any) {
    return this.postService.create(createPostDto);
  }
}

// post.module.ts
import { Module } from '@nestjs/common';
import { PostController } from './post.controller';
import { PostService } from './post.service';
import { UserModule } from '../user/user.module'; // 导入用户模块

@Module({
  imports: [UserModule], // 导入用户模块以使用 UserService
  controllers: [PostController],
  providers: [PostService],
})
export class PostModule {}

共享模块

1. 创建共享模块

typescript
// shared/logger.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class LoggerService {
  log(message: string) {
    console.log(`[${new Date().toISOString()}] ${message}`);
  }

  error(message: string, error?: any) {
    console.error(`[${new Date().toISOString()}] ERROR: ${message}`, error);
  }
}

// shared/config.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class ConfigService {
  private config = {
    apiUrl: 'https://api.example.com',
    maxRetries: 3,
    timeout: 5000,
  };

  get(key: string): any {
    return this.config[key];
  }

  set(key: string, value: any): void {
    this.config[key] = value;
  }
}

// shared/shared.module.ts
import { Module } from '@nestjs/common';
import { LoggerService } from './logger.service';
import { ConfigService } from './config.service';

@Module({
  providers: [LoggerService, ConfigService],
  exports: [LoggerService, ConfigService], // 导出供其他模块使用
})
export class SharedModule {}

2. 在多个模块中使用共享模块

typescript
// user/user.service.ts
import { Injectable } from '@nestjs/common';
import { LoggerService } from '../shared/logger.service';

@Injectable()
export class UserService {
  constructor(private logger: LoggerService) {}

  findAll() {
    this.logger.log('获取所有用户');
    return this.users;
  }

  create(userData: any) {
    this.logger.log(`创建用户: ${userData.name}`);
    // 创建用户逻辑
  }
}

// user/user.module.ts
@Module({
  imports: [SharedModule], // 导入共享模块
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

// post/post.service.ts
import { Injectable } from '@nestjs/common';
import { LoggerService } from '../shared/logger.service';
import { ConfigService } from '../shared/config.service';

@Injectable()
export class PostService {
  constructor(private logger: LoggerService, private config: ConfigService) {}

  findAll() {
    this.logger.log('获取所有文章');
    const maxRetries = this.config.get('maxRetries');
    // 使用配置
  }
}

// post/post.module.ts
@Module({
  imports: [SharedModule, UserModule], // 导入多个模块
  controllers: [PostController],
  providers: [PostService],
})
export class PostModule {}

全局模块

有些模块(如日志、配置)需要在整个应用中使用,可以设置为全局模块:

typescript
import { Module, Global } from '@nestjs/common';
import { LoggerService } from './logger.service';

@Global() // 设置为全局模块
@Module({
  providers: [LoggerService],
  exports: [LoggerService],
})
export class GlobalModule {}

使用全局模块后,其他模块不需要导入就可以使用:

typescript
// 不需要在 imports 中添加 GlobalModule
@Module({
  controllers: [SomeController],
  providers: [SomeService], // SomeService 可以直接注入 LoggerService
})
export class SomeModule {}

动态模块

1. 创建动态模块

typescript
// database/database.module.ts
import { Module, DynamicModule } from '@nestjs/common';
import { DatabaseService } from './database.service';

@Module({})
export class DatabaseModule {
  static forRoot(config: {
    host: string;
    port: number;
    database: string;
  }): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        {
          provide: 'DATABASE_CONFIG',
          useValue: config,
        },
        DatabaseService,
      ],
      exports: [DatabaseService],
    };
  }

  static forFeature(entities: any[]): DynamicModule {
    return {
      module: DatabaseModule,
      providers: [
        {
          provide: 'ENTITIES',
          useValue: entities,
        },
      ],
    };
  }
}

2. 使用动态模块

typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { UserModule } from './user/user.module';

@Module({
  imports: [
    DatabaseModule.forRoot({
      host: 'localhost',
      port: 5432,
      database: 'myapp',
    }),
    UserModule,
  ],
})
export class AppModule {}

// user/user.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from '../database/database.module';
import { User } from './user.entity';

@Module({
  imports: [
    DatabaseModule.forFeature([User]), // 为特定功能配置
  ],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

模块的最佳实践

1. 模块组织结构

src/
├── app.module.ts
├── shared/
│   ├── shared.module.ts
│   ├── logger.service.ts
│   └── config.service.ts
├── user/
│   ├── user.module.ts
│   ├── user.controller.ts
│   ├── user.service.ts
│   └── user.entity.ts
├── post/
│   ├── post.module.ts
│   ├── post.controller.ts
│   ├── post.service.ts
│   └── post.entity.ts
└── auth/
    ├── auth.module.ts
    ├── auth.controller.ts
    ├── auth.service.ts
    └── auth.guard.ts

2. 根模块的组织

typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { UserModule } from './user/user.module';
import { PostModule } from './post/post.module';
import { AuthModule } from './auth/auth.module';
import { SharedModule } from './shared/shared.module';

@Module({
  imports: [SharedModule, AuthModule, UserModule, PostModule],
})
export class AppModule {}

3. 功能模块的组织

typescript
// feature/feature.module.ts
import { Module } from '@nestjs/common';
import { FeatureController } from './feature.controller';
import { FeatureService } from './feature.service';
import { SharedModule } from '../shared/shared.module';

@Module({
  imports: [SharedModule],
  controllers: [FeatureController],
  providers: [FeatureService],
  exports: [FeatureService], // 如果其他模块需要使用
})
export class FeatureModule {}

命令行工具

1. 创建模块

bash
# 创建一个新模块
nest generate module users
# 或简写
nest g module users

# 创建完整的CRUD资源(包含模块、控制器、服务)
nest generate resource users
# 或简写
nest g resource users

2. 生成的文件结构

src/
├── users/
│   ├── users.module.ts
│   ├── users.controller.ts
│   ├── users.service.ts
│   └── dto/
│       ├── create-user.dto.ts
│       └── update-user.dto.ts

常见问题和解决方案

1. 循环依赖问题

typescript
// ❌ 错误:会导致循环依赖
// user.module.ts
@Module({
  imports: [PostModule],
  exports: [UserService],
})
export class UserModule {}

// post.module.ts
@Module({
  imports: [UserModule],
  exports: [PostService],
})
export class PostModule {}

// ✅ 解决方案1:使用 forwardRef
import { Module, forwardRef } from '@nestjs/common';

@Module({
  imports: [forwardRef(() => PostModule)],
  exports: [UserService],
})
export class UserModule {}

// ✅ 解决方案2:创建共享模块
@Module({
  providers: [UserService, PostService],
  exports: [UserService, PostService],
})
export class SharedModule {}

2. 服务注入问题

typescript
// ❌ 错误:忘记导入模块
@Module({
  controllers: [PostController],
  providers: [PostService], // PostService 需要 UserService,但没有导入 UserModule
})
export class PostModule {}

// ✅ 正确:导入依赖的模块
@Module({
  imports: [UserModule], // 导入 UserModule
  controllers: [PostController],
  providers: [PostService],
})
export class PostModule {}

3. 服务导出问题

typescript
// ❌ 错误:没有导出服务
@Module({
  providers: [UserService], // 只在 providers 中声明,没有导出
})
export class UserModule {}

// ✅ 正确:导出服务
@Module({
  providers: [UserService],
  exports: [UserService], // 导出服务供其他模块使用
})
export class UserModule {}

总结

NestJS 模块是应用程序的基础构建块,它们:

  1. 组织代码 - 将相关的控制器、服务、和其他组件组织在一起
  2. 管理依赖 - 通过 imports 和 exports 管理模块之间的依赖关系
  3. 提供封装 - 每个模块都有自己的作用域
  4. 支持重用 - 模块可以在不同的地方重复使用

记住这些要点:

  • 每个应用至少有一个根模块(AppModule)
  • 使用 @Module() 装饰器定义模块
  • 通过 imports 导入其他模块
  • 通过 exports 导出服务供其他模块使用
  • 合理组织模块结构,避免循环依赖

从简单的模块开始,逐步构建复杂的应用程序架构!