34 KiB
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
PORTenv 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.tsfiles - 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:
.env.local(local override).env.production(production override).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
ApiResponseDtoformat - ✅ 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: trueconfigurations - 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
levelnumber (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
levelnumber
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
- Level Loading: Client calls
GET /api/v1/wechat-game/levels - Single Level Fetch: Client calls
GET /api/v1/wechat-game/levels/:id(though it doesn't currently) - Config Management: Client can call
GET /api/v1/wechat-game/configsfor 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
IRepositoryinterfaces - 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
- Add JWT authentication for admin endpoints
- Add rate limiting (e.g.,
@nestjs/throttler) - Add request logging (e.g., Morgan middleware)
- Add input validation for text fields (sanitize HTML)
- 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_orderfor sorting - Consider adding index on
game_configs.config_keyfor 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 ✅
- Clean architecture: Proper separation of concerns (Controller → Service → Repository)
- Type safety: Full TypeScript with DTOs and entity types
- Error handling: Centralized exception filter with consistent format
- Documentation: Swagger auto-documentation on all endpoints
- Configuration: Type-safe environment variables with validation
- Database: TypeORM with MySQL, auto-sync in dev
- Validation: Global validation pipe with whitelist protection
- Modularity: Feature-based module structure
Potential Issues ⚠️
- Performance:
getLevelById()fetches all levels instead of direct query - Authentication: No auth mechanism for sensitive endpoints
- Rate limiting: No rate limiting implemented
- Caching: No caching layer (clients could cache locally)
- Pagination: No pagination on large datasets
- Middleware: Limited middleware (no logging, no request IDs)
- Testing: No E2E tests implemented yet
Missing Features 🔧
- Authentication & Authorization (guards, JWT tokens)
- POST/PUT/DELETE endpoints (read-only currently)
- User management module
- Score/points tracking
- Level submission endpoints
- 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