Files
MemeMind-Server/SERVER_ANALYSIS.md
2026-04-05 13:38:12 +08:00

34 KiB
Raw Blame History

MemeMind-Server Backend Analysis

Date: 2026-04-05 Project: MemeMind-Server (NestJS + TypeORM) Location: /Users/richard/Documents/code/xieyingeng/MemeMind-Server

Executive Summary

MemeMind-Server is a NestJS backend for a WeChat mini-game with a clean layered architecture. It provides two main modules: WechatGame (game level and config management) and AppConfig (environment setup). The server uses TypeORM with MySQL as the database and follows enterprise patterns including repositories, DTOs, and global error handling.


1. Project Structure

MemeMind-Server/
├── src/
│   ├── main.ts                                  # App entry point
│   ├── app.module.ts                            # Root module (imports config & wechat-game)
│   ├── common/
│   │   ├── dto/
│   │   │   └── api-response.dto.ts             # Generic API response wrapper
│   │   └── filters/
│   │       └── http-exception.filter.ts         # Global exception handler
│   ├── config/
│   │   ├── config.module.ts                     # Environment config setup
│   │   ├── database.config.ts                   # TypeORM & MySQL config
│   │   └── env.validation.ts                    # Environment variable validation
│   └── modules/
│       └── wechat-game/
│           ├── wechat-game.module.ts           # Module definition
│           ├── wechat-game.controller.ts        # 4 REST endpoints
│           ├── wechat-game.service.ts           # Business logic
│           ├── entities/
│           │   ├── level.entity.ts             # Level data model
│           │   └── game-config.entity.ts        # Configuration model
│           ├── dto/
│           │   ├── level-response.dto.ts       # Level API response
│           │   └── game-config-response.dto.ts # Config API response
│           └── repositories/
│               ├── level.repository.ts          # Level DB access
│               ├── level.repository.interface.ts
│               ├── game-config.repository.ts    # Config DB access
│               └── game-config.repository.interface.ts
├── test/
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── package.json
├── tsconfig.json
├── tsconfig.build.json
└── nest-cli.json

2. Package Dependencies

Core Framework

  • @nestjs/core ^11.0.1 - NestJS core
  • @nestjs/common ^11.0.1 - Common utilities
  • @nestjs/platform-express ^11.0.1 - Express integration
  • @nestjs/config ^4.0.3 - Environment management
  • @nestjs/typeorm ^11.0.0 - TypeORM integration
  • @nestjs/swagger ^11.2.6 - API documentation

Database

  • typeorm ^0.3.28 - ORM framework
  • mysql2 ^3.19.1 - MySQL driver

Data Validation & Transformation

  • class-validator ^0.15.1 - DTO validation
  • class-transformer ^0.5.1 - DTO transformation
  • reflect-metadata ^0.2.2 - Reflection for decorators

Utilities

  • rxjs ^7.8.1 - Reactive programming

Development Tools

  • @nestjs/cli ^11.0.0
  • @nestjs/testing ^11.0.1
  • typescript ^5.7.3
  • ts-jest ^29.2.5
  • jest ^30.0.0
  • prettier ^3.4.2
  • eslint ^9.18.0

3. Application Setup (main.ts)

File: src/main.ts (46 lines)

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Global prefix for all routes: /api
  app.setGlobalPrefix('api');

  // CORS enabled (supports WeChat mini-game cross-origin requests)
  app.enableCors({
    origin: true,
    credentials: true,
  });

  // Global validation pipe
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,              // Strip unknown properties
      forbidNonWhitelisted: true,    // Throw on unknown properties
      transform: true,              // Auto-transform DTOs
    }),
  );

  // Global exception filter
  app.useGlobalFilters(new HttpExceptionFilter());

  // Swagger documentation at /api/docs
  const config = new DocumentBuilder()
    .setTitle('MemeMind Server API')
    .setDescription('微信小游戏 MemeMind 服务端 API 文档')
    .setVersion('1.0')
    .build();

  const port = process.env.PORT ?? 3000;
  await app.listen(port);
}

Key Setup Details

  • Global prefix: All routes start with /api
  • CORS: Enabled for WeChat mini-game frontend
  • Validation: Auto-validates all DTO inputs, strips unknown fields
  • Exception handling: Global filter wraps all errors in ApiResponseDto
  • Swagger: Auto-generated API docs at /api/docs
  • Default port: 3000 (configurable via PORT env var)

4. Root Module Configuration (app.module.ts)

File: src/app.module.ts (30 lines)

@Module({
  imports: [
    AppConfigModule,                    // Environment setup
    TypeOrmModule.forRootAsync({        // Dynamic DB config
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get<string>('database.host'),
        port: configService.get<number>('database.port'),
        username: configService.get<string>('database.username'),
        password: configService.get<string>('database.password'),
        database: configService.get<string>('database.database'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: NODE_ENV !== 'production',  // Auto-create tables in dev
        logging: NODE_ENV !== 'production',      // Log queries in dev
        autoLoadEntities: true,                  // Auto-discover entities
      }),
    }),
    WechatGameModule,                   // Game module
  ],
})
export class AppModule {}

Key Configuration

  • Async TypeORM config: Reads database settings from environment
  • Auto-synchronization: In development, automatically creates/updates tables
  • Auto-load entities: Discovers all .entity.ts files
  • Logging: Database queries logged in development mode

5. Environment Configuration

Files

  • config.module.ts: Global config setup
  • database.config.ts: Database connection parameters
  • env.validation.ts: Type-safe environment variables

Configuration Structure (env.validation.ts)

class EnvironmentVariables {
  @IsEnum(Environment)
  NODE_ENV: Environment = 'development';  // 'development' | 'production' | 'test'

  @IsNumber()
  PORT: number = 3000;

  @IsString()
  DB_HOST: string = 'localhost';

  @IsNumber()
  DB_PORT: number = 3306;

  @IsString()
  DB_USERNAME: string = 'meme_user';

  @IsString()
  DB_PASSWORD: string = '';

  @IsString()
  DB_DATABASE: string = 'meme_mind';
}

Environment Files

The app looks for environment files in this order:

  1. .env.local (local override)
  2. .env.production (production override)
  3. .env (default)

Database Configuration (database.config.ts)

  • Default MySQL connection: localhost:3306
  • Fallback credentials: meme_user / (no password)
  • Default database: meme_mind
  • Auto-synchronize: Enabled in development
  • Query logging: Enabled in development

6. Global Response Format

ApiResponseDto (src/common/dto/api-response.dto.ts)

All API responses follow this generic wrapper:

interface ApiResponseDto<T> {
  success: boolean;      // Request successful?
  data: T | null;        // Response data (null on error)
  message: string | null; // Error message (null on success)
  timestamp: Date;       // Response timestamp

  // Static factory methods
  static success<T>(data: T): ApiResponseDto<T>
  static error<T>(message: string): ApiResponseDto<T | null>
}

Example Success Response

{
  "success": true,
  "data": {
    "levels": [...],
    "total": 50
  },
  "message": null,
  "timestamp": "2026-04-05T10:30:00.000Z"
}

Example Error Response

{
  "success": false,
  "data": null,
  "message": "Level with id 'xyz' not found",
  "timestamp": "2026-04-05T10:30:00.000Z",
  "path": "/api/v1/wechat-game/levels/xyz"
}

7. Global Exception Handler

HttpExceptionFilter (src/common/filters/http-exception.filter.ts)

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    // Extracts HTTP status code from exception
    // Wraps error in ApiResponseDto format
    // Includes request path for debugging
    // Logs unexpected errors
  }
}

Features:

  • Catches all exceptions (HTTP and runtime errors)
  • Wraps all errors in standard ApiResponseDto format
  • Includes request URL in error response
  • Logs errors to console
  • Proper HTTP status codes (404, 400, 500, etc.)

8. WechatGame Module Architecture

Module Structure (wechat-game.module.ts)

@Module({
  imports: [TypeOrmModule.forFeature([GameConfig, Level])],
  controllers: [WechatGameController],
  providers: [WechatGameService, GameConfigRepository, LevelRepository],
  exports: [WechatGameService],
})
export class WechatGameModule {}

Layer Architecture

┌─────────────────────────────────────┐
│ HTTP Requests (REST Controller)     │
│ Base: /api/v1/wechat-game           │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│ Business Logic (Service)            │
│ • Query logic                        │
│ • DTO mapping                        │
│ • Error handling                     │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│ Data Access (Repositories)          │
│ • Level Repository                  │
│ • GameConfig Repository             │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│ TypeORM (Database Layer)            │
│ • MySQL queries                     │
│ • Transaction management            │
└─────────────────────────────────────┘

9. REST API Endpoints

Base URL: /api/v1/wechat-game

Endpoint 1: Get All Game Configs

GET /api/v1/wechat-game/configs
Response: ApiResponseDto<GameConfigListResponseDto>
Status: 200

Endpoint 2: Get Config by Key

GET /api/v1/wechat-game/configs/:key
Response: ApiResponseDto<GameConfigResponseDto>
Status: 200 | 404 (not found)

Endpoint 3: Get All Levels

GET /api/v1/wechat-game/levels
Response: ApiResponseDto<LevelListResponseDto>
Status: 200
Description: Returns levels sorted by sort_order

Endpoint 4: Get Level by ID

GET /api/v1/wechat-game/levels/:id
Response: ApiResponseDto<LevelResponseDto>
Status: 200 | 404 (not found)

10. Data Models

Entity 1: Level (src/modules/wechat-game/entities/level.entity.ts)

@Entity('levels')
export class Level {
  @PrimaryColumn({ type: 'varchar', length: 191 })
  id: string;                          // UUID or custom ID

  @Column({ type: 'varchar', length: 191, name: 'image_url' })
  imageUrl: string;                    // Game image URL

  @Column({ type: 'varchar', length: 191 })
  answer: string;                      // Correct answer (single string)

  @Column({ type: 'varchar', length: 191, nullable: true })
  hint1: string | null;                // First hint (free)

  @Column({ type: 'varchar', length: 191, nullable: true })
  hint2: string | null;                // Second hint (costs 1 life)

  @Column({ type: 'varchar', length: 191, nullable: true })
  hint3: string | null;                // Third hint (costs 1 life)

  @Column({ type: 'int', name: 'sort_order', default: 0 })
  sortOrder: number;                   // Order of display

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;
}

Database Table: levels

Entity 2: GameConfig (src/modules/wechat-game/entities/game-config.entity.ts)

@Entity('game_configs')
export class GameConfig {
  @PrimaryGeneratedColumn('uuid')
  id: string;                          // Auto-generated UUID

  @Column({ type: 'varchar', length: 255, name: 'config_key' })
  configKey: string;                   // Unique config identifier

  @Column({ type: 'text', name: 'config_value' })
  configValue: string;                 // Config value (stored as text)

  @Column({
    type: 'varchar',
    length: 100,
    nullable: true,
  })
  description: string | null;          // Optional description

  @Column({ type: 'boolean', default: true, name: 'is_active' })
  isActive: boolean;                   // Is this config active?

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;
}

Database Table: game_configs


11. Service Layer (WechatGameService)

File: src/modules/wechat-game/wechat-game.service.ts (93 lines)

Methods

1. getAllConfigs()

async getAllConfigs(): Promise<GameConfigListResponseDto> {
  const configs = await this.gameConfigRepository.findActiveConfigs();
  return {
    configs: configs.map((config) => this.toResponseDto(config)),
    total: configs.length,
  };
}
  • Returns only isActive: true configurations
  • Maps entities to DTOs
  • Returns list with total count

2. getConfigByKey(key: string)

async getConfigByKey(key: string): Promise<GameConfigResponseDto> {
  const config = await this.gameConfigRepository.findByKey(key);
  
  if (!config) {
    throw new NotFoundException(`Game config with key "${key}" not found`);
  }
  
  return this.toResponseDto(config);
}
  • Throws 404 if not found
  • Maps entity to DTO

3. getAllLevels()

async getAllLevels(): Promise<LevelListResponseDto> {
  const levels = await this.levelRepository.findAllOrdered();
  
  return {
    levels: levels.map((level, index) =>
      this.toLevelResponseDto(level, index + 1),  // 1-indexed level number
    ),
    total: levels.length,
  };
}
  • Fetches levels in sort_order
  • Adds computed level number (1-indexed)
  • Returns list with total count

4. getLevelById(id: string)

async getLevelById(id: string): Promise<LevelResponseDto> {
  const levels = await this.levelRepository.findAllOrdered();
  const levelIndex = levels.findIndex((l) => l.id === id);
  
  if (levelIndex === -1) {
    throw new NotFoundException(`Level with id "${id}" not found`);
  }
  
  return this.toLevelResponseDto(levels[levelIndex], levelIndex + 1);
}
  • Fetches all levels and finds by ID (not optimal - see improvements)
  • Throws 404 if not found
  • Adds computed level number

5. toResponseDto(config)

private toResponseDto(config: GameConfig): GameConfigResponseDto {
  return {
    id: config.id,
    configKey: config.configKey,
    configValue: config.configValue,
    description: config.description,
    isActive: config.isActive,
    createdAt: config.createdAt,
    updatedAt: config.updatedAt,
  };
}

6. toLevelResponseDto(level, levelNumber)

private toLevelResponseDto(level: Level, levelNumber: number): LevelResponseDto {
  return {
    level: levelNumber,              // Computed field
    id: level.id,
    imageUrl: level.imageUrl,
    answer: level.answer,
    hint1: level.hint1,
    hint2: level.hint2,
    hint3: level.hint3,
    sortOrder: level.sortOrder,
    createdAt: level.createdAt,
    updatedAt: level.updatedAt,
  };
}

12. Repository Pattern

Repository Interfaces

ILevelRepository:

interface ILevelRepository {
  findAll(): Promise<Level[]>;
  findById(id: string): Promise<Level | null>;
  findAllOrdered(): Promise<Level[]>;  // Ordered by sortOrder ASC
}

IGameConfigRepository:

interface IGameConfigRepository {
  findAll(): Promise<GameConfig[]>;
  findById(id: string): Promise<GameConfig | null>;
  findByKey(key: string): Promise<GameConfig | null>;
  findActiveConfigs(): Promise<GameConfig[]>;  // Where isActive = true
}

Implementations

LevelRepository (src/modules/wechat-game/repositories/level.repository.ts):

@Injectable()
export class LevelRepository implements ILevelRepository {
  constructor(
    @InjectRepository(Level)
    private readonly repository: Repository<Level>,
  ) {}

  async findAll(): Promise<Level[]> {
    return this.repository.find();
  }

  async findById(id: string): Promise<Level | null> {
    return this.repository.findOne({ where: { id } });
  }

  async findAllOrdered(): Promise<Level[]> {
    return this.repository.find({
      order: { sortOrder: 'ASC' },
    });
  }
}

GameConfigRepository (src/modules/wechat-game/repositories/game-config.repository.ts):

@Injectable()
export class GameConfigRepository implements IGameConfigRepository {
  constructor(
    @InjectRepository(GameConfig)
    private readonly repository: Repository<GameConfig>,
  ) {}

  async findAll(): Promise<GameConfig[]> {
    return this.repository.find();
  }

  async findById(id: string): Promise<GameConfig | null> {
    return this.repository.findOne({ where: { id } });
  }

  async findByKey(key: string): Promise<GameConfig | null> {
    return this.repository.findOne({ where: { configKey: key } });
  }

  async findActiveConfigs(): Promise<GameConfig[]> {
    return this.repository.find({ where: { isActive: true } });
  }
}

13. Data Transfer Objects (DTOs)

LevelResponseDto (src/modules/wechat-game/dto/level-response.dto.ts)

export class LevelResponseDto {
  @ApiProperty({ description: '关卡编号' })
  level: number;                      // Computed level number (1-indexed)

  @ApiProperty({ description: '关卡ID' })
  id: string;

  @ApiProperty({ description: '图片URL' })
  imageUrl: string;

  @ApiProperty({ description: '答案' })
  answer: string;

  @ApiProperty({ description: '提示1', nullable: true })
  hint1: string | null;

  @ApiProperty({ description: '提示2', nullable: true })
  hint2: string | null;

  @ApiProperty({ description: '提示3', nullable: true })
  hint3: string | null;

  @ApiProperty({ description: '排序顺序' })
  sortOrder: number;

  @ApiProperty({ description: '创建时间' })
  createdAt: Date;

  @ApiProperty({ description: '更新时间' })
  updatedAt: Date;
}

export class LevelListResponseDto {
  @ApiProperty({ type: [LevelResponseDto], description: '关卡列表' })
  levels: LevelResponseDto[];

  @ApiProperty({ description: '关卡总数' })
  total: number;
}

GameConfigResponseDto (src/modules/wechat-game/dto/game-config-response.dto.ts)

export class GameConfigResponseDto {
  @ApiProperty({ description: '配置ID' })
  id: string;

  @ApiProperty({ description: '配置键名' })
  configKey: string;

  @ApiProperty({ description: '配置值' })
  configValue: string;

  @ApiProperty({ description: '配置描述', nullable: true })
  description: string | null;

  @ApiProperty({ description: '是否激活' })
  isActive: boolean;

  @ApiProperty({ description: '创建时间' })
  createdAt: Date;

  @ApiProperty({ description: '更新时间' })
  updatedAt: Date;
}

export class GameConfigListResponseDto {
  @ApiProperty({ type: [GameConfigResponseDto], description: '配置列表' })
  configs: GameConfigResponseDto[];

  @ApiProperty({ description: '配置总数' })
  total: number;
}

14. Controller (WechatGameController)

File: src/modules/wechat-game/wechat-game.controller.ts (69 lines)

@ApiTags('微信小游戏')
@Controller('v1/wechat-game')
export class WechatGameController {
  constructor(private readonly wechatGameService: WechatGameService) {}

  @Get('configs')
  @ApiOperation({
    summary: '获取所有游戏配置',
    description: '获取所有激活的游戏配置列表',
  })
  async getAllConfigs(): Promise<ApiResponseDto<GameConfigListResponseDto>> {
    const data = await this.wechatGameService.getAllConfigs();
    return ApiResponseDto.success(data);
  }

  @Get('configs/:key')
  @ApiOperation({
    summary: '根据key获取配置',
  })
  async getConfigByKey(
    @Param('key') key: string,
  ): Promise<ApiResponseDto<GameConfigResponseDto>> {
    const data = await this.wechatGameService.getConfigByKey(key);
    return ApiResponseDto.success(data);
  }

  @Get('levels')
  @ApiOperation({
    summary: '获取所有关卡',
    description: '获取所有关卡列表按sort_order排序',
  })
  async getAllLevels(): Promise<ApiResponseDto<LevelListResponseDto>> {
    const data = await this.wechatGameService.getAllLevels();
    return ApiResponseDto.success(data);
  }

  @Get('levels/:id')
  @ApiOperation({
    summary: '根据ID获取关卡',
  })
  async getLevelById(
    @Param('id') id: string,
  ): Promise<ApiResponseDto<LevelResponseDto>> {
    const data = await this.wechatGameService.getLevelById(id);
    return ApiResponseDto.success(data);
  }
}

Features

  • All endpoints wrapped in generic ApiResponseDto
  • Full Swagger documentation (@ApiOperation, @ApiResponse)
  • Proper HTTP methods and status codes
  • Route parameters validated by NestJS

15. Integration with Client

Client API Call Pattern

From the Cocos Creator client (LevelDataManager.ts), the server is called:

// Client calls
const response = await HttpUtil.get<ApiResponse>('https://ilookai.cn/api/v1/wechat-game/levels');

// Server Response
{
  "success": true,
  "data": {
    "levels": [
      {
        "level": 1,
        "id": "level-001",
        "imageUrl": "https://example.com/image.jpg",
        "answer": "answer text",
        "hint1": "hint 1",
        "hint2": "hint 2",
        "hint3": "hint 3",
        "sortOrder": 0,
        "createdAt": "2026-04-05T00:00:00Z",
        "updatedAt": "2026-04-05T00:00:00Z"
      },
      ...
    ],
    "total": 50
  },
  "message": null,
  "timestamp": "2026-04-05T10:30:00.000Z"
}

Client Integration Points

  1. Level Loading: Client calls GET /api/v1/wechat-game/levels
  2. Single Level Fetch: Client calls GET /api/v1/wechat-game/levels/:id (though it doesn't currently)
  3. Config Management: Client can call GET /api/v1/wechat-game/configs for game settings

16. Architecture Patterns

1. Dependency Injection

  • NestJS constructor injection for all dependencies
  • Repository interfaces decoupled from implementations
  • Testable design

2. Repository Pattern

  • Repositories abstract database access
  • Implements IRepository interfaces
  • Easy to mock for testing

3. Service Layer Pattern

  • Services contain business logic
  • Controllers delegate to services
  • Services delegate to repositories

4. DTO Pattern

  • DTOs separate API contracts from internal entities
  • Validation and transformation in DTOs
  • Swagger documentation on DTO fields

5. Global Exception Handling

  • Single exception filter wraps all errors
  • Consistent error response format
  • Centralized logging

6. Configuration Management

  • Environment-based configuration
  • Typed environment variables
  • Validation on startup

17. TypeORM Configuration Details

Auto-Discovery

  • Entities auto-loaded from: **/*.entity{.ts,.js}
  • Repositories auto-created by @InjectRepository()

Development Features

  • Database synchronization (auto-create tables)
  • Query logging to console
  • SQL visible in development

Production Features

  • No auto-sync (safer)
  • No query logging (better performance)
  • Connection pooling via mysql2

Column Mapping

  • Snake_case in database: image_url, sort_order, created_at
  • Camel case in entities: imageUrl, sortOrder, createdAt
  • Auto-converted via TypeORM decorators

18. Security Patterns

Global Validation Pipe

new ValidationPipe({
  whitelist: true,              // ✅ Remove unknown properties
  forbidNonWhitelisted: true,    // ✅ Throw if unknown properties
  transform: true,              // ✅ Auto-transform types
})

Considerations for Production

  • ⚠️ No authentication middleware currently
  • ⚠️ No rate limiting
  • ⚠️ No request logging middleware
  • ⚠️ CORS allows all origins (fine for public API)
  • ⚠️ No input sanitization beyond whitelist

Recommendations

  1. Add JWT authentication for admin endpoints
  2. Add rate limiting (e.g., @nestjs/throttler)
  3. Add request logging (e.g., Morgan middleware)
  4. Add input validation for text fields (sanitize HTML)
  5. Add API key authentication for WeChat client

19. Performance Considerations

Current Implementation

GET /api/v1/wechat-game/levels/:id
  └─> getAllLevels() - Fetches ALL levels
      └─> findAllOrdered() - Database query for all levels
          └─> Memory search for :id - O(n) in application

Problem: getLevelById() fetches all levels, then searches in memory

Optimization

// BETTER - Direct database query
async getLevelById(id: string): Promise<LevelResponseDto> {
  const level = await this.levelRepository.findById(id);
  if (!level) throw new NotFoundException();
  
  // Need to get level index for number calculation
  const index = await this.levelRepository.getLevelIndex(id);
  return this.toLevelResponseDto(level, index + 1);
}

Database Indexing

  • Consider adding index on levels.sort_order for sorting
  • Consider adding index on game_configs.config_key for lookups
  • Consider adding composite index on game_configs(config_key, is_active)

20. API Response Flow Diagram

┌─────────────────────────┐
│   HTTP Request          │
│  GET /api/v1/...        │
└────────────┬────────────┘
             │
┌────────────▼────────────┐
│  Global Validation Pipe │ ← Validates path params
└────────────┬────────────┘
             │
┌────────────▼────────────────────────┐
│  Controller Method                  │
│  @Get('levels/:id')                 │
└────────────┬────────────────────────┘
             │
┌────────────▼────────────────────────┐
│  Service Method                     │
│  getLevelById(id)                   │
│  - Repository query                 │
│  - DTO mapping                      │
│  - Null check → NotFoundException    │
└────────────┬────────────────────────┘
             │
        Success ───────────────┐
        or Error               │
             │                 │
┌────────────▼────────────────────────┐
│  Global Exception Filter (if error) │
│  Wraps in ApiResponseDto            │
└────────────┬────────────────────────┘
             │
┌────────────▼────────────────────────┐
│  ApiResponseDto.success() or error()│
│  Wraps in standard response format  │
└────────────┬────────────────────────┘
             │
┌────────────▼────────────────────────┐
│  HTTP Response (200, 400, 404, 500) │
│  JSON: {success, data, message}     │
└─────────────────────────────────────┘

21. Key Findings

Strengths

  1. Clean architecture: Proper separation of concerns (Controller → Service → Repository)
  2. Type safety: Full TypeScript with DTOs and entity types
  3. Error handling: Centralized exception filter with consistent format
  4. Documentation: Swagger auto-documentation on all endpoints
  5. Configuration: Type-safe environment variables with validation
  6. Database: TypeORM with MySQL, auto-sync in dev
  7. Validation: Global validation pipe with whitelist protection
  8. Modularity: Feature-based module structure

Potential Issues ⚠️

  1. Performance: getLevelById() fetches all levels instead of direct query
  2. Authentication: No auth mechanism for sensitive endpoints
  3. Rate limiting: No rate limiting implemented
  4. Caching: No caching layer (clients could cache locally)
  5. Pagination: No pagination on large datasets
  6. Middleware: Limited middleware (no logging, no request IDs)
  7. Testing: No E2E tests implemented yet

Missing Features 🔧

  1. Authentication & Authorization (guards, JWT tokens)
  2. POST/PUT/DELETE endpoints (read-only currently)
  3. User management module
  4. Score/points tracking
  5. Level submission endpoints
  6. Statistics/leaderboard endpoints

22. Database Schema

levels table

CREATE TABLE levels (
  id VARCHAR(191) PRIMARY KEY,
  image_url VARCHAR(191) NOT NULL,
  answer VARCHAR(191) NOT NULL,
  hint1 VARCHAR(191),
  hint2 VARCHAR(191),
  hint3 VARCHAR(191),
  sort_order INT DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_sort_order (sort_order)
);

game_configs table

CREATE TABLE game_configs (
  id CHAR(36) PRIMARY KEY,           -- UUID
  config_key VARCHAR(255) NOT NULL UNIQUE,
  config_value TEXT NOT NULL,
  description VARCHAR(100),
  is_active BOOLEAN DEFAULT TRUE,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY idx_config_key (config_key),
  KEY idx_active (is_active)
);

23. Extension Points

Adding User Authentication

// 1. Create users.entity.ts
@Entity('users')
export class User {
  @PrimaryGeneratedColumn('uuid')
  id: string;
  
  @Column({ unique: true })
  wxOpenId: string;           // WeChat OpenID
  
  @Column()
  nickname: string;
  
  @Column()
  lives: number;              // Current lives
  
  @Column()
  currentLevel: number;       // Progress
}

// 2. Create user.module.ts with UserController, UserService, UserRepository
// 3. Add JWT guard to protected endpoints
// 4. Add user context to requests

Adding Level Submission Endpoint

// POST /api/v1/wechat-game/levels/:id/submit
@Post('levels/:id/submit')
async submitAnswer(
  @Param('id') id: string,
  @Body() dto: SubmitAnswerDto,
): Promise<ApiResponseDto<SubmitAnswerResponseDto>> {
  // Validate answer
  // Update user progress
  // Award points/lives
  // Return result
}

Adding Caching

// Import cache module
import { CacheModule } from '@nestjs/cache-manager';

// In WechatGameModule
@Module({
  imports: [
    CacheModule.register({
      ttl: 3600, // 1 hour
    }),
    TypeOrmModule.forFeature([GameConfig, Level]),
  ],
})

// In service
constructor(
  private cacheManager: Cache,
  private levelRepository: LevelRepository,
) {}

async getAllLevels() {
  const cached = await this.cacheManager.get('all_levels');
  if (cached) return cached;
  
  const levels = await this.levelRepository.findAllOrdered();
  await this.cacheManager.set('all_levels', levels, 3600);
  return levels;
}

24. Quick Reference

Starting the Server

npm install                    # Install dependencies
npm run start:dev             # Development with watch mode
npm run start:prod            # Production mode
npm run build                 # Build TypeScript
npm test                      # Run tests
npm run test:e2e              # End-to-end tests

Project Commands

npm run format                # Format code with Prettier
npm run lint                  # Lint and fix with ESLint
npm run test:watch            # Watch tests
npm run test:cov              # Test coverage

Environment Setup

# .env file
NODE_ENV=development
PORT=3000
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=meme_user
DB_PASSWORD=password
DB_DATABASE=meme_mind

Swagger UI

  • Location: http://localhost:3000/api/docs
  • API Endpoint: http://localhost:3000/api/docs-json
  • Auto-updated from decorators

Database Connection

Host: localhost
Port: 3306
Username: meme_user
Database: meme_mind

Summary

MemeMind-Server is a well-structured NestJS backend with:

  • Clean layered architecture (Controller → Service → Repository)
  • Type-safe with full TypeScript
  • Comprehensive error handling and validation
  • Auto-generated Swagger documentation
  • MySQL persistence with TypeORM
  • Environment-based configuration
  • Ready for feature expansion (auth, scoring, leaderboards)

The server currently provides read-only access to game levels and configurations, with endpoints designed to support the WeChat mini-game client's level loading and configuration management needs.


Generated: 2026-04-05 | Last Updated: 2026-04-05