nestjs · sanjeed5 — Cursor rule: NestJS structure and types
A Cursor rule that sets NestJS project structure: feature modules, explicit types, validated DTOs and global exception filters.
.cursor/rules/nestjs.mdcWhat nestjs does
The rule lays out how to organise a NestJS codebase: every business domain gets its own module instead of a shared controllers or services folder. Inside a module you keep controllers, services, repositories and DTOs in separate directories. Files and folders use kebab-case, classes PascalCase, interfaces an I prefix, constants UPPER_SNAKE_CASE.
Types get their own section. Variables, parameters and return values must be explicit, and any is off the table. Strict TypeScript goes into tsconfig.json. Incoming request bodies and query params go through DTOs with class-validator decorators such as @IsString, @IsEmail and @MinLength. Dependencies are injected through the constructor, so new on a service breaks the pattern.
Errors are handled in one place: a class with @Catch(HttpException) formats the response, and main.ts registers it with app.useGlobalFilters. No try/catch in every method. Route access is closed off by a guard that reads roles through Reflector. Worth knowing before you drop it in: the examples assume a NestJS app in TypeScript, and the DTO snippets need class-validator installed.
What it helps with
- split code into feature modules instead of shared controllers and services folders
- drop any and add explicit types across services
- validate request bodies with class-validator DTOs
- return one consistent error shape instead of try/catch in every method
- protect routes with a role-checking guard
Example prompts for the agent
- Restructure the users module by feature: controller, service, repository and DTO in separate folders
- Add a create-user DTO with class-validator instead of an any body
- Register the global HttpExceptionFilter in main.ts and remove try/catch from the controllers
How to install nestjs
Manually: put the files in place
.cursor/rules/nestjs.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/nestjs.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/nestjs.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/nestjs.mdManually: put the files in place
.clinerules/nestjs.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about nestjs
How do I install the nestjs rule in Cursor?
Create .cursor/rules/nestjs.mdc in your project root and paste the rule contents into it.
What does the nestjs rule do?
It defines NestJS layout and conventions: feature modules, naming rules, explicit types, validated DTOs, a global exception filter and guards for roles.
What do I need for the rule's examples to work?
A NestJS project in TypeScript with strict mode in tsconfig.json. The DTO examples also need class-validator.
Source text
nestjs.mdc · CC0-1.0
---
description: This guide provides opinionated, actionable best practices for building robust, scalable, and maintainable NestJS applications using TypeScript, emphasizing modern patterns and common pitfalls.
globs: **/*.{ts,tsx}
---
# NestJS Best Practices
NestJS is a powerful framework for building scalable Node.js applications. Adhering to these guidelines ensures your projects are maintainable, performant, and secure.
## 1. Code Organization and Structure
Adopt a feature-based, semi-hexagonal architecture. Each business domain gets its own module.
### ✅ GOOD: Feature-based Modules
Organize code by feature, not by type.
```typescript
// src/modules/users/users.module.ts
@Module({
controllers: [UsersController],
providers: [UsersService, UsersRepository],
exports: [UsersService],
})
export class UsersModule {}
// src/modules/users/controllers/users.controller.ts
// src/modules/users/services/users.service.ts
// src/modules/users/repositories/users.repository.ts
// src/modules/users/dtos/create-user.dto.ts
```
### ❌ BAD: Type-based Flat Structure
Avoid scattering related logic across different top-level folders.
```
// src/controllers/users.controller.ts
// src/services/users.service.ts
```
### Naming Conventions
Follow standard TypeScript conventions for clarity:
* **Files/Folders**: `kebab-case` (e.g., `user-profile.module.ts`)
* **Classes**: `PascalCase` (e.g., `UsersService`, `CreateUserDto`)
* **Interfaces**: `IPascalCase` (e.g., `IUserRepository`)
* **Functions/Methods**: `camelCase` (e.g., `createUser`, `findAllUsers`)
* **Constants**: `UPPER_SNAKE_CASE` (e.g., `JWT_SECRET`)
## 2. Type Safety and Strictness
Always use explicit types. Avoid `any`. Enable strict TypeScript in `tsconfig.json`.
### ✅ GOOD: Explicit Types
Every variable, parameter, and return value must have an explicit type.
```typescript
interface IUser { id: string; name: string; email: string; }
async function fetchUserById(id: string): Promise<IUser | null> {
const user: IUser | undefined = await this.userRepository.findOne(id);
return user ?? null;
}
```
### ❌ BAD: Implicit `any`
Leads to runtime errors and defeats TypeScript's purpose.
```typescript
function fetchUserById(id) { // id is implicitly 'any'
const user = this.userRepository.findOne(id);
return user;
}
```
## 3. Common Patterns
### Dependency Injection (DI)
Leverage NestJS's DI system for loose coupling and testability.
### ✅ GOOD: Constructor Injection
Always inject dependencies via the constructor.
```typescript
@Injectable()
export class UsersService {
constructor(private readonly userRepository: IUserRepository) {}
async findAll(): Promise<IUser[]> { return this.userRepository.findAll(); }
}
```
### ❌ BAD: Manual Instantiation
Avoid `new` keyword for services/providers.
```typescript
const userService = new UsersService(); // Breaks DI and testability
```
### Data Transfer Objects (DTOs)
Use DTOs with `class-validator` for all incoming request bodies and query parameters.
### ✅ GOOD: Validated DTOs
Ensure data integrity at the edge of your application.
```typescript
// src/modules/users/dtos/create-user.dto.ts
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString() @MinLength(3) name: string;
@IsEmail() email: string;
@IsString() @MinLength(8) password: string;
}
// src/modules/users/controllers/users.controller.ts
@Post()
async createUser(@Body() createUserDto: CreateUserDto): Promise<IUser> {
return this.userService.create(createUserDto);
}
```
### ❌ BAD: Direct Request Body Usage
Unvalidated data is a security risk.
```typescript
@Post()
async createUser(@Body() body: any): Promise<IUser> { /* ... */ }
```
### Controllers and Services
Controllers handle HTTP requests and delegate business logic to services. Services contain the core business logic and interact with data layers.
### ✅ GOOD: Lean Controllers, Rich Services
Controllers should be thin, focusing on request/response.
```typescript
// src/modules/users/controllers/users.controller.ts
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
async findOne(@Param('id') id: string): Promise<IUser> {
return this.usersService.findById(id);
}
}
// src/modules/users/services/users.service.ts
@Injectable()
export class UsersService {
constructor(private readonly userRepository: IUserRepository) {}
async findById(id: string): Promise<IUser> {
const user = await this.userRepository.findOne(id);
if (!user) { throw new NotFoundException(`User ID "${id}" not found.`); }
return user;
}
}
```
### ❌ BAD: Business Logic in Controllers
Makes controllers hard to test and re-use.
```typescript
@Controller('users')
export class UsersController {
constructor(private readonly userRepository: IUserRepository) {}
@Get(':id')
async findOne(@Param('id') id: string): Promise<IUser> {
const user = await this.userRepository.findOne(id); // Logic here
if (!user) { throw new NotFoundException(`User ID "${id}" not found.`); }
return user;
}
}
```
## 4. Error Handling
Centralize error handling using Exception Filters.
### ✅ GOOD: Global Exception Filters
Catch and format all unhandled exceptions consistently.
```typescript
// src/shared/common/filters/http-exception.filter.ts
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>(); const status = exception.getStatus();
response.status(status).json({
statusCode: status, timestamp: new Date().toISOString(), path: request.url,
message: exception.message,
});
}
}
// src/main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter()); // Apply globally
await app.listen(3000);
}
```
### ❌ BAD: `try/catch` in Every Method
Leads to boilerplate and inconsistent error responses.
```typescript
@Get(':id')
async findOne(@Param('id') id: string): Promise<IUser> {
try { return await this.usersService.findById(id); }
catch (error) { throw new HttpException('Failed', HttpStatus.INTERNAL_SERVER_ERROR); }
}
```
## 5. Security Best Practices
### Guards for Authorization
Use `Guards` for declarative route protection.
### ✅ GOOD: Role-Based Access Control (RBAC)
```typescript
// src/modules/auth/guards/roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(ROLES_KEY, [
context.getHandler(), context.getClass(),
]);
if (!requiredRoles) { return true; }
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
// src/modules/users/controllers/users.controller.ts
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard) // Apply guards
export class UsersController {
@Get() @Roles(UserRole.Admin) // Require Admin role
async findAll(): Promise<IUser[]> { return this.usersService.findAll(); }
}
```
### Environment Variables
Manage configuration securely using `@nestjs/config`.
### ✅ GOOD: `ConfigModule`
```typescript
// src/app.module.ts
@Module({
imports: [ConfigModule.forRoot({ isGlobal: true, envFilePath: `.env.${process.env.NODE_ENV}` })],
})
export class AppModule {}
// In a service
@Injectable()
export class DatabaseService {
constructor(private configService: ConfigService) {
const dbHost = this.configService.get<string>('DATABASE_HOST');
}
}
```
### ❌ BAD: Hardcoding Secrets
Never hardcode sensitive information.
```typescript
const JWT_SECRET = 'super-secret-key'; // BAD
```
## 6. Performance Considerations
### Fastify Adapter
Use Fastify for improved performance over Express.
### ✅ GOOD: Fastify Integration
```typescript
// src/main.ts
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule, new FastifyAdapter() // Use Fastify
);
await app.listen(3000);
}
bootstrap();
```
### Caching
Implement caching for frequently accessed, slow-changing data.
### ✅ GOOD: `CacheModule`
```typescript
// src/app.module.ts
@Module({
imports: [CacheModule.register({ store: redisStore, host: 'localhost', port: 6379, ttl: 300 })],
})
export class AppModule {}
// In a service method
@Get() @UseInterceptors(CacheInterceptor) @CacheKey('all_users') @CacheTTL(60)
async findAll(): Promise<IUser[]> { return this.usersService.findAll(); }
```
## 7. Testing
Prioritize unit tests for services and e2e tests for controllers.
### ✅ GOOD: Unit Testing Services
Mock external
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc
Similar IDE rules
All IDE rules for Backend and APIs →nestjs by other authors comes first.