Imported from wuwb/com.tongdelove.lab (
services/server/AGENTS.md). Install upstream withnpx skills add wuwb/com.tongdelove.lab --skill server. Copyright stays with the author.
AGENTS.md - Server (NestJS API)
Last Updated: 2025-02-27
Framework: NestJS (TypeScript)
Database: PostgreSQL + Prisma ORM
API Styles: REST + GraphQL
Architecture: Modular (Feature-based Modules)
šļø Architecture Overview
The server is a NestJS application serving as the central backend API for the monorepo.
services/server/
āāā src/
ā āāā main.ts # Application entry point
ā āāā app.module.ts # Root module
ā āāā config/ # Configuration providers
ā ā āāā database.module.ts # Prisma connection
ā ā āāā graphql.module.ts # GraphQL server setup
ā āāā modules/ # Feature modules
ā ā āāā auth/ # Authentication & authorization
ā ā āāā users/ # User management
ā ā āāā articles/ # Article/Content management
ā ā āāā books/ # Book management
ā ā āāā comments/ # Comments system
ā ā āāā products/ # E-commerce products
ā ā āāā ... # Other domain modules
ā āāā common/ # Shared utilities
ā ā āāā guards/ # Route guards
ā ā āāā decorators/ # Custom decorators
ā ā āāā interceptors/ # Request/response interceptors
ā ā āāā filters/ # Exception filters
ā ā āāā pipes/ # Data transformation pipes
ā āāā generated/ # Auto-generated files
ā āāā prisma-pothos-types # Prisma + Pothos types
ā āāā ...
āāā prisma/ # Prisma schema & migrations
ā āāā schema.prisma
āāā test/ # Test files
āāā nest-cli.json # NestJS CLI config
āāā package.json
š Core Concepts
1. Modular Architecture
NestJS modules are organized by domain/feature:
Example Module Structure:
users/
āāā users.module.ts # Module definition
āāā users.controller.ts # HTTP routes (REST)
āāā users.resolver.ts # GraphQL resolvers
āāā users.service.ts # Business logic
āāā entities/ # GraphQL entities
āāā dto/ # Data transfer objects
ā āāā create-user.dto.ts
ā āāā update-user.dto.ts
āāā users.spec.ts # Unit tests
Module Dependencies:
@Module({
imports: [PrismaModule, AuthModule],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
2. Dual API Style (REST + GraphQL)
The server exposes both REST and GraphQL endpoints:
| Style | Entry Point | Use Case |
|---|---|---|
| REST | @Controller() |
CRUD operations, file uploads, webhooks |
| GraphQL | @Resolver() |
Complex queries, data fetching, real-time subscriptions |
Example:
// REST
@Post('users')
async createUser(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
// GraphQL
@Mutation(() => User)
async createUser(@Args('input') input: CreateUserDto) {
return this.usersService.create(input);
}
3. Prisma Integration
Database Module: src/config/database.module.ts
@Module({
providers: [
{
provide: PrismaService,
useFactory: () => new PrismaClient(),
},
],
exports: [PrismaService],
})
export class DatabaseModule {}
Usage in Services:
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll() {
return this.prisma.user.findMany()
}
}
4. Authentication & Authorization
Guards (src/common/guards/):
AuthGuard- JWT token verificationRolesGuard- Role-based access controlPermissionsGuard- Fine-grained permissions
Decorators (src/common/decorators/):
@Public()- Allow public access (bypass auth)@Roles()- Required roles@CurrentUser()- Inject current user
Usage:
@UseGuards(AuthGuard)
@Get('profile')
async getProfile(@CurrentUser() user: User) {
return user;
}
@UseGuards(AuthGuard, RolesGuard)
@Roles('admin')
@Post('admin-action')
async adminAction() {
// Only admins can access
}
5. Type Safety with Prisma + Zod
Generated Types:
// prisma/generated/prisma-pothos-types
import * as types from '../generated/prisma-pothos-types'
Validation:
- DTOs use class-validator decorators
- Zod schemas can be generated from Prisma via
zod-prisma - Type inference from Prisma models
š Module Patterns
Standard Module File List
| File | Purpose | Required |
|---|---|---|
{module}.module.ts |
Module definition, imports/exports | ā Yes |
{module}.controller.ts |
REST routes | ā Yes |
{module}.resolver.ts |
GraphQL operations | ā Yes |
{module}.service.ts |
Business logic | ā Yes |
entities/ |
GraphQL entities | Optional |
dto/ |
Validation schemas | Optional |
{module}.spec.ts |
Tests | Recommended |
Controller Pattern (REST)
@Controller('articles')
export class ArticlesController {
constructor(private articlesService: ArticlesService) {}
@Get()
findAll(): Promise<Article[]> {
return this.articlesService.findAll()
}
@Get(':id')
findOne(@Param('id') id: string): Promise<Article> {
return this.articlesService.findOne(id)
}
@Post()
create(@Body() dto: CreateArticleDto): Promise<Article> {
return this.articlesService.create(dto)
}
@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateArticleDto) {
return this.articlesService.update(id, dto)
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.articlesService.remove(id)
}
}
Resolver Pattern (GraphQL)
@Resolver(() => Article)
export class ArticlesResolver {
constructor(private articlesService: ArticlesService) {}
@Query(() => [Article])
articles(): Promise<Article[]> {
return this.articlesService.findAll()
}
@Mutation(() => Article)
createArticle(@Args('input') input: CreateArticleInput): Promise<Article> {
return this.articlesService.create(input)
}
@ResolveField(() => Author)
author(@Parent() article: Article) {
return this.articlesService.getAuthor(article.authorId)
}
}
Service Pattern
@Injectable()
export class ArticlesService {
constructor(private prisma: PrismaService) {}
async findAll(): Promise<Article[]> {
return this.prisma.article.findMany({
include: { author: true, tags: true },
})
}
async findOne(id: string): Promise<Article> {
const article = await this.prisma.article.findUnique({
where: { id },
})
if (!article) {
throw new NotFoundException('Article not found')
}
return article
}
async create(createArticleDto: CreateArticleDto): Promise<Article> {
return this.prisma.article.create({
data: createArticleDto,
})
}
}
šÆ Working Conventions
1. Error Handling
Custom Exceptions:
export class UserNotFoundException extends NotFoundException {
constructor(id: string) {
super(`User with ID ${id} not found`)
}
}
Exception Filters:
import { Catch, ExceptionFilter } from '@nestjs/common'
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
// Standardized error response format
}
}
2. DTO Validation with class-validator
import { IsString, IsEmail, MinLength } from 'class-validator'
export class CreateUserDto {
@IsString()
@MinLength(3)
username: string
@IsEmail()
email: string
@IsString()
@MinLength(6)
password: string
}
3. Interceptors for Cross-Cutting Concerns
Logging Interceptor:
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const now = Date.now()
return next
.handle()
.pipe(tap(() => console.log(`After... ${Date.now() - now}ms`)))
}
}
4. Testing Patterns
Unit Tests (.spec.ts):
describe('UsersService', () => {
let service: UsersService
let prisma: PrismaService
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
UsersService,
{ provide: PrismaService, useValue: mockPrisma },
],
}).compile()
service = module.get<UsersService>(UsersService)
})
it('should create a user', async () => {
expect(service.create(dto)).resolves.toEqual(expectedUser)
})
})
š§ Development Workflow
Running the Server
# Development mode (with hot reload)
pnpm dev
# Production build
pnpm build
# Start production server
pnpm start:prod
# Run tests
pnpm test
# Run e2e tests
pnpm test:e2e
Adding a New Module
# Generate using NestJS CLI
nest g module modules/my-feature
nest g controller modules/my-feature
nest g service modules/my-feature
nest g resolver modules/my-feature
# Or create manually following the pattern
Database Migrations
# Create migration
npx prisma migrate dev --create-only
# Apply migration
npx prisma migrate dev
# Reset database (dev only)
npx prisma migrate reset
# Open Prisma Studio
pnpm db:studio
š« Anti-Patterns to Avoid
-
Direct database access in controllers
- ā Use Prisma directly in controller
- ā Always go through service layer
-
Skipping validation
- ā Use
@Body() data: any - ā Use DTOs with class-validator
- ā Use
-
Missing error handling
- ā Throw raw exceptions
- ā Use custom exceptions or filters
-
Circular dependencies
- ā Module A imports Module B, B imports A
- ā Use forward reference or extract common logic
-
Hardcoded configuration
- ā
const API_KEY = 'hardcoded' - ā
Use
@Injectable()with ConfigService
- ā
š Common Issues & Solutions
| Issue | Solution |
|---|---|
| Prisma client errors | Run pnpm db:generate after schema changes |
| CORS errors | Enable CORS in main.ts |
| GraphQL schema errors | Check resolver return types match Prisma types |
| Slow queries | Add database indexes via Prisma migrations |
š Key Configuration Files
| File | Purpose |
|---|---|
nest-cli.json |
NestJS CLI configuration |
.graphqlconfig |
GraphQL compiler settings |
tsconfig.build.json |
TypeScript build config |
graphql.schema.ts |
Generated GraphQL schema |
ecosystem.config.js |
PM2 process manager config |
š¤ Contributing
When working on the server:
- Understand the module structure (feature-based)
- Services contain business logic
- Controllers are thin (route to service)
- Use DTOs for input validation
- Use guards for authentication/authorization
- Write unit tests for services
- Use Prisma for all database operations
For NestJS best practices, see NestJS Documentation For Prisma patterns, see Prisma Documentation
