Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
40 prioritized NestJS best practices across architecture, DI, security, performance, testing, and microservices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/error-throw-http-exceptions.md
1---2title: Throw HTTP Exceptions from Services3impact: HIGH4impactDescription: Keeps controllers thin and simplifies error handling5tags: error-handling, exceptions, services6---78## Throw HTTP Exceptions from Services910It's acceptable (and often preferable) to throw `HttpException` subclasses from services in HTTP applications. This keeps controllers thin and allows services to communicate appropriate error states. For truly layer-agnostic services, use domain exceptions that map to HTTP status codes.1112**Incorrect (return error objects instead of throwing):**1314```typescript15// Return error objects instead of throwing16@Injectable()17export class UsersService {18async findById(id: string): Promise<{ user?: User; error?: string }> {19const user = await this.repo.findOne({ where: { id } });20if (!user) {21return { error: 'User not found' }; // Controller must check this22}23return { user };24}25}2627@Controller('users')28export class UsersController {29@Get(':id')30async findOne(@Param('id') id: string) {31const result = await this.usersService.findById(id);32if (result.error) {33throw new NotFoundException(result.error);34}35return result.user;36}37}38```3940**Correct (throw exceptions directly from service):**4142```typescript43// Throw exceptions directly from service44@Injectable()45export class UsersService {46constructor(private readonly repo: UserRepository) {}4748async findById(id: string): Promise<User> {49const user = await this.repo.findOne({ where: { id } });50if (!user) {51throw new NotFoundException(`User #${id} not found`);52}53return user;54}5556async create(dto: CreateUserDto): Promise<User> {57const existing = await this.repo.findOne({58where: { email: dto.email },59});60if (existing) {61throw new ConflictException('Email already registered');62}63return this.repo.save(dto);64}6566async update(id: string, dto: UpdateUserDto): Promise<User> {67const user = await this.findById(id); // Throws if not found68Object.assign(user, dto);69return this.repo.save(user);70}71}7273// Controller stays thin74@Controller('users')75export class UsersController {76@Get(':id')77findOne(@Param('id') id: string): Promise<User> {78return this.usersService.findById(id);79}8081@Post()82create(@Body() dto: CreateUserDto): Promise<User> {83return this.usersService.create(dto);84}85}8687// For layer-agnostic services, use domain exceptions88export class EntityNotFoundException extends Error {89constructor(90public readonly entity: string,91public readonly id: string,92) {93super(`${entity} with ID "${id}" not found`);94}95}9697// Map to HTTP in exception filter98@Catch(EntityNotFoundException)99export class EntityNotFoundFilter implements ExceptionFilter {100catch(exception: EntityNotFoundException, host: ArgumentsHost) {101const ctx = host.switchToHttp();102const response = ctx.getResponse<Response>();103104response.status(404).json({105statusCode: 404,106message: exception.message,107entity: exception.entity,108id: exception.id,109});110}111}112```113114Reference: [NestJS Exception Filters](https://docs.nestjs.com/exception-filters)115