feat: Expand fleet to 23 skills across all domains
New skills (14): - nestjs-best-practices: 40 priority-ranked rules (kadajett) - fastapi: Pydantic v2, async SQLAlchemy, JWT auth (jezweb) - architecture-patterns: Clean Architecture, Hexagonal, DDD (wshobson) - python-performance-optimization: Profiling and optimization (wshobson) - ai-sdk: Vercel AI SDK streaming and agent patterns (vercel) - create-agent: Modular agent architecture with OpenRouter (openrouterteam) - proactive-agent: WAL Protocol, compaction recovery, self-improvement (halthelobster) - brand-guidelines: Brand identity enforcement (anthropics) - ui-animation: Motion design with accessibility (mblode) - marketing-ideas: 139 ideas across 14 categories (coreyhaines31) - pricing-strategy: SaaS pricing and tier design (coreyhaines31) - programmatic-seo: SEO at scale with playbooks (coreyhaines31) - competitor-alternatives: Comparison page architecture (coreyhaines31) - referral-program: Referral and affiliate programs (coreyhaines31) README reorganized by domain: Code Quality, Frontend, Backend, Auth, AI/Agent Building, Marketing, Design, Meta. Mosaic Stack is not limited to coding — the Orchestrator serves coding, business, design, marketing, writing, logistics, and analysis. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Throw HTTP Exceptions from Services
|
||||
impact: HIGH
|
||||
impactDescription: Keeps controllers thin and simplifies error handling
|
||||
tags: error-handling, exceptions, services
|
||||
---
|
||||
|
||||
## Throw HTTP Exceptions from Services
|
||||
|
||||
It'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.
|
||||
|
||||
**Incorrect (return error objects instead of throwing):**
|
||||
|
||||
```typescript
|
||||
// Return error objects instead of throwing
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
async findById(id: string): Promise<{ user?: User; error?: string }> {
|
||||
const user = await this.repo.findOne({ where: { id } });
|
||||
if (!user) {
|
||||
return { error: 'User not found' }; // Controller must check this
|
||||
}
|
||||
return { user };
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
@Get(':id')
|
||||
async findOne(@Param('id') id: string) {
|
||||
const result = await this.usersService.findById(id);
|
||||
if (result.error) {
|
||||
throw new NotFoundException(result.error);
|
||||
}
|
||||
return result.user;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Correct (throw exceptions directly from service):**
|
||||
|
||||
```typescript
|
||||
// Throw exceptions directly from service
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(private readonly repo: UserRepository) {}
|
||||
|
||||
async findById(id: string): Promise<User> {
|
||||
const user = await this.repo.findOne({ where: { id } });
|
||||
if (!user) {
|
||||
throw new NotFoundException(`User #${id} not found`);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
async create(dto: CreateUserDto): Promise<User> {
|
||||
const existing = await this.repo.findOne({
|
||||
where: { email: dto.email },
|
||||
});
|
||||
if (existing) {
|
||||
throw new ConflictException('Email already registered');
|
||||
}
|
||||
return this.repo.save(dto);
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateUserDto): Promise<User> {
|
||||
const user = await this.findById(id); // Throws if not found
|
||||
Object.assign(user, dto);
|
||||
return this.repo.save(user);
|
||||
}
|
||||
}
|
||||
|
||||
// Controller stays thin
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string): Promise<User> {
|
||||
return this.usersService.findById(id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(@Body() dto: CreateUserDto): Promise<User> {
|
||||
return this.usersService.create(dto);
|
||||
}
|
||||
}
|
||||
|
||||
// For layer-agnostic services, use domain exceptions
|
||||
export class EntityNotFoundException extends Error {
|
||||
constructor(
|
||||
public readonly entity: string,
|
||||
public readonly id: string,
|
||||
) {
|
||||
super(`${entity} with ID "${id}" not found`);
|
||||
}
|
||||
}
|
||||
|
||||
// Map to HTTP in exception filter
|
||||
@Catch(EntityNotFoundException)
|
||||
export class EntityNotFoundFilter implements ExceptionFilter {
|
||||
catch(exception: EntityNotFoundException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
response.status(404).json({
|
||||
statusCode: 404,
|
||||
message: exception.message,
|
||||
entity: exception.entity,
|
||||
id: exception.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Reference: [NestJS Exception Filters](https://docs.nestjs.com/exception-filters)
|
||||
Reference in New Issue
Block a user