Best Practices Guide
Module Organization
Feature-Based Structure
src/
āāā modules/
ā āāā users/
ā ā āāā users.module.ts
ā ā āāā users.tools.ts
ā ā āāā users.service.ts
ā ā āāā users.guard.ts
ā āāā products/
ā āāā products.module.ts
ā āāā products.tools.ts
ā āāā products.service.ts
ā āāā products.resources.ts
Keep Modules Focused
ā Good: One feature per module
@Module({ name: 'users' }) // User management only
@Module({ name: 'products' }) // Product catalog only
ā Avoid: Kitchen sink modules
@Module({ name: 'everything' }) // Too broad!
Tool Design
Clear Names
ā Good: Verb + noun
@Tool({ name: 'get_user' })
@Tool({ name: 'create_order' })
@Tool({ name: 'update_product' })
ā Avoid: Unclear names
@Tool({ name: 'user' })
@Tool({ name: 'doStuff' })
Descriptive Schemas
ā Good: Use .describe()
inputSchema: z.object({
email: z.string().email().describe('User email address'),
age: z.number().min(18).describe('Must be 18 or older')
})
ā Avoid: No descriptions
inputSchema: z.object({
email: z.string(),
age: z.number()
})
Provide Examples
ā Good: Include examples
@Tool({
name: 'get_product',
examples: {
request: { product_id: 'prod-1' },
response: { id: 'prod-1', name: 'Product', price: 99.99 }
}
})
Service Layer
Business Logic in Services
ā Good: Logic in service
@Injectable()
export class OrderService {
async calculateTotal(items: any[]) {
return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}
}
export class OrderTools {
constructor(private orderService: OrderService) {}
@Tool({ name: 'create_order' })
async createOrder(input: any) {
const total = await this.orderService.calculateTotal(input.items);
return this.orderService.create({ ...input, total });
}
}
ā Avoid: Logic in tools
export class OrderTools {
@Tool({ name: 'create_order' })
async createOrder(input: any) {
const total = input.items.reduce((sum, item) => sum + item.price * item.qty, 0);
// ... lots of business logic here
}
}
Error Handling
Specific Error Messages
ā Good: Helpful messages
if (!product) {
throw new Error(\`Product with ID \${id} not found\`);
}
if (stock < quantity) {
throw new Error(\`Insufficient stock. Available: \${stock}, Requested: \${quantity}\`);
}
ā Avoid: Generic messages
throw new Error('Error occurred');
throw new Error('Invalid input');
Log Errors
ā Good: Log context
try {
return await this.processPayment(input);
} catch (error) {
ctx.logger.error('Payment processing failed:', {
userId: ctx.auth?.subject,
amount: input.amount,
error: error.message
});
throw error;
}
Security
Validate Input
ā Good: Use Zod schemas
@Tool({
inputSchema: z.object({
email: z.string().email(),
password: z.string().min(8)
})
})
Use Guards
ā Good: Protect sensitive tools
@Tool({ name: 'delete_user' })
@UseGuards(JWTGuard, AdminGuard)
async deleteUser(input: any) {
// Protected!
}
Hash Sensitive Data
ā Good: Hash passwords
const hash = await bcrypt.hash(password, 10);
ā Avoid: Plain text
await db.execute('INSERT INTO users VALUES (?, ?)', [email, password]);
Performance
Cache Expensive Operations
ā Good: Cache when appropriate
@Tool({ name: 'get_config' })
@Cache({ ttl: 3600 })
async getConfig() {
return await this.loadConfig();
}
Rate Limit
ā Good: Protect from abuse
@Tool({ name: 'send_email' })
@RateLimit({ requests: 10, window: '1m' })
async sendEmail(input: any) {
// Limited to 10/minute
}
Testing
Write Tests
ā Good: Test coverage
describe('UserService', () => {
it('should create user', async () => {
const result = await service.create({ email: 'test@example.com' });
expect(result.email).toBe('test@example.com');
});
});
Mock Dependencies
ā Good: Use mocks
const mockDb = {
query: jest.fn().mockResolvedValue([])
};
const service = new UserService(mockDb);
Documentation
Document Tools
ā Good: Clear descriptions
@Tool({
name: 'search_products',
description: 'Search products by name, category, or price range with pagination',
inputSchema: z.object({
query: z.string().describe('Search query'),
category: z.string().optional().describe('Filter by category'),
min_price: z.number().optional().describe('Minimum price in USD'),
max_price: z.number().optional().describe('Maximum price in USD'),
page: z.number().default(1).describe('Page number for pagination'),
limit: z.number().default(20).describe('Items per page')
})
})
Use JSDoc
ā Good: Document complex logic
/**
* Calculates shipping cost based on weight, distance, and shipping method.
* @param weight - Package weight in kg
* @param distance - Shipping distance in km
* @param method - 'standard' | 'express' | 'overnight'
* @returns Shipping cost in USD
*/
calculateShipping(weight: number, distance: number, method: string): number {
// ...
}
Configuration
Use Environment Variables
ā Good: ConfigService
constructor(private config: ConfigService) {}
const secret = this.config.get('JWT_SECRET');
ā Avoid: Hardcoded values
const secret = 'my-secret-key';