Nitrocloud LogoNitroStack
/sdk
/typescript
/best practices

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';

Next Steps