Skip to main content

Helix Platform - Development Guidelines

Essential do's and don'ts for developing on the Helix platform. Follow these guidelines to maintain code quality, consistency, and scalability.


Core Principles

1. Use Shared Libraries First

Before writing any utility, guard, pipe, decorator, or validator, check if it exists in libs/shared/*.

2. Follow TypeScript Strict Mode

No any types. Explicit return types. Type safety everywhere.

3. Write Tests

Minimum 80% coverage. Test behavior, not implementation.

4. Keep Services Focused

Each service should have a single, well-defined responsibility.

5. Security First

Never commit secrets. Always validate input. Use guards on all routes except /health.


DO ✅

Code Quality

✅ DO use TypeScript strict mode

// ✅ GOOD - Explicit types
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}

interface Item {
price: number;
name: string;
}

// ❌ BAD - Any types
function calculateTotal(items: any): any {
return items.reduce((sum: any, item: any) => sum + item.price, 0);
}

✅ DO use shared libraries

// ✅ GOOD - Use shared utilities
import { retry } from '@helix/shared/utils';
import { CurrentUser } from '@helix/shared/decorators';
import { JwtAuthGuard } from '@helix/shared/guards';
import { PaginationPipe } from '@helix/shared/pipes';

@Get()
@UseGuards(JwtAuthGuard)
async findAll(
@CurrentUser() auth: AuthContext,
@Query(PaginationPipe) pagination: PaginationParams
) {
return retry(() => this.service.findAll(pagination));
}

// ❌ BAD - Duplicate functionality
function retry() { /* ... */ } // Already exists in @helix/shared/utils
function getCurrentUser() { /* ... */ } // Already exists as decorator

✅ DO keep controllers thin

// ✅ GOOD - Controller delegates to service
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}

@Get()
async findAll(@Query(PaginationPipe) pagination: PaginationParams) {
return this.userService.findAll(pagination);
}

@Post()
async create(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
}

// ❌ BAD - Business logic in controller
@Controller('users')
export class UserController {
@Get()
async findAll(@Query() query: any) {
const page = query.page || 1;
const pageSize = query.pageSize || 10;
const skip = (page - 1) * pageSize;

const users = await this.db.user.findMany({ skip, take: pageSize });
const total = await this.db.user.count();

return {
data: users,
pagination: { page, pageSize, total },
};
}
}

✅ DO use dependency injection

// ✅ GOOD - Dependency injection
@Injectable()
export class UserService {
constructor(
private readonly db: DatabaseService,
private readonly cache: CacheService,
) {}

async findOne(id: string): Promise<User> {
const cached = await this.cache.get(`user:${id}`);
if (cached) return cached;

const user = await this.db.user.findUnique({ where: { id } });
await this.cache.set(`user:${id}`, user, 300);
return user;
}
}

// ❌ BAD - Direct instantiation
@Injectable()
export class UserService {
private db = new DatabaseService(); // Don't do this!
private cache = new CacheService(); // Don't do this!
}

✅ DO write meaningful tests

// ✅ GOOD - Test behavior
describe('UserService', () => {
describe('create', () => {
it('should create a user with valid data', async () => {
const dto = { email: 'test@example.com', name: 'Test User' };
const result = await service.create(dto);

expect(result).toHaveProperty('id');
expect(result.email).toBe(dto.email);
expect(result.name).toBe(dto.name);
});

it('should throw error for duplicate email', async () => {
const dto = { email: 'existing@example.com', name: 'Test' };

await expect(service.create(dto)).rejects.toThrow('Email already exists');
});
});
});

// ❌ BAD - Test implementation details
it('should call database.user.create', async () => {
await service.create(dto);
expect(mockDb.user.create).toHaveBeenCalled(); // Testing implementation
});

Security

✅ DO protect all routes with guards

// ✅ GOOD - Routes are protected
@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard)
export class UserController {
@Get()
@Roles('TENANT_ADMIN', 'USER')
async findAll() {
return this.service.findAll();
}

@Get('health')
@Public() // Only /health is public
async health() {
return { status: 'healthy' };
}
}

// ❌ BAD - Unprotected routes
@Controller('users')
export class UserController {
@Get() // No guards!
async findAll() {
return this.service.findAll();
}
}

✅ DO validate all input

// ✅ GOOD - DTO with validation
export class CreateUserDto {
@IsEmail()
@IsNotEmpty()
email: string;

@IsString()
@MinLength(2)
@MaxLength(100)
name: string;

@IsEnum(UserRole)
role: UserRole;
}

@Post()
async create(@Body() dto: CreateUserDto) {
return this.service.create(dto);
}

// ❌ BAD - No validation
@Post()
async create(@Body() data: any) {
return this.service.create(data); // Unsafe!
}

✅ DO handle errors properly

// ✅ GOOD - Proper error handling
@Post()
async create(@Body() dto: CreateUserDto) {
try {
return await this.service.create(dto);
} catch (error) {
if (error.code === 'P2002') { // Prisma unique constraint
throw new ConflictException('Email already exists');
}
throw new InternalServerException('Failed to create user');
}
}

// ❌ BAD - Exposing internal errors
@Post()
async create(@Body() dto: CreateUserDto) {
return await this.service.create(dto); // Raw errors exposed
}

Database

✅ DO use Prisma for type-safe queries

// ✅ GOOD - Type-safe Prisma
async findAll(pagination: PaginationParams): Promise<PaginatedResponse<User>> {
const [users, total] = await Promise.all([
this.db.user.findMany({
skip: (pagination.page - 1) * pagination.pageSize,
take: pagination.pageSize,
include: {
tenant: true,
roles: true,
},
orderBy: { createdAt: 'desc' },
}),
this.db.user.count(),
]);

return {
data: users,
pagination: {
page: pagination.page,
pageSize: pagination.pageSize,
total,
totalPages: Math.ceil(total / pagination.pageSize),
},
};
}

// ❌ BAD - Raw SQL
async findAll(): Promise<User[]> {
return this.db.$queryRaw`SELECT * FROM users`; // No type safety
}

✅ DO use transactions for multi-step operations

// ✅ GOOD - Use transactions
async createUserWithRole(dto: CreateUserDto): Promise<User> {
return this.db.$transaction(async (tx) => {
const user = await tx.user.create({ data: dto });
await tx.userRole.create({
data: {
userId: user.id,
role: dto.role,
},
});
return user;
});
}

// ❌ BAD - Separate operations
async createUserWithRole(dto: CreateUserDto): Promise<User> {
const user = await this.db.user.create({ data: dto });
await this.db.userRole.create({ data: { userId: user.id, role: dto.role } });
// If second operation fails, user exists without role!
return user;
}

✅ DO index foreign keys

// ✅ GOOD - Indexed foreign keys
model User {
id String @id @default(uuid())
tenantId String

tenant Tenant @relation(fields: [tenantId], references: [id])

@@index([tenantId]) // Index for faster lookups
}

// ❌ BAD - No index on foreign key
model User {
id String @id @default(uuid())
tenantId String

tenant Tenant @relation(fields: [tenantId], references: [id])
// Missing @@index([tenantId])
}

Architecture

✅ DO follow module structure

// ✅ GOOD - Proper module structure
@Module({
imports: [
HttpModule,
CacheModule.register(),
],
controllers: [UserController],
providers: [UserService],
exports: [UserService], // Export if used by other modules
})
export class UserModule {}

// ❌ BAD - Everything in one module
@Module({
controllers: [
UserController,
TenantController,
AuthController, // Should be separate modules
],
providers: [
UserService,
TenantService,
AuthService, // Too many responsibilities
],
})
export class AppModule {}

✅ DO use environment variables for configuration

// ✅ GOOD - Environment-based config
@Injectable()
export class AppConfig {
readonly port = parseInt(process.env.PORT || '3000', 10);
readonly nodeEnv = process.env.NODE_ENV || 'development';
readonly corsOrigin = process.env.CORS_ORIGIN?.split(',') || ['*'];
}

// ❌ BAD - Hardcoded values
@Injectable()
export class AppConfig {
readonly port = 3000; // Can't change per environment
readonly corsOrigin = ['http://localhost:3001']; // Hardcoded
}

✅ DO use proper logging

// ✅ GOOD - Structured logging
@Injectable()
export class UserService {
private readonly logger = new Logger(UserService.name);

async create(dto: CreateUserDto): Promise<User> {
this.logger.log(`Creating user: ${dto.email}`);

try {
const user = await this.db.user.create({ data: dto });
this.logger.log(`User created: ${user.id}`);
return user;
} catch (error) {
this.logger.error(`Failed to create user: ${error.message}`, error.stack);
throw error;
}
}
}

// ❌ BAD - Console.log
async create(dto: CreateUserDto): Promise<User> {
console.log('Creating user...'); // Lost in production
const user = await this.db.user.create({ data: dto });
console.log('Done'); // Not helpful
return user;
}

DON'T ❌

Code Quality

❌ DON'T use any types

// ❌ BAD
function processData(data: any): any {
return data.map((item: any) => item.value);
}

// ✅ GOOD
interface DataItem {
value: number;
label: string;
}

function processData(data: DataItem[]): number[] {
return data.map(item => item.value);
}

❌ DON'T skip error handling

// ❌ BAD - No error handling
@Post()
async create(@Body() dto: CreateUserDto) {
return await this.service.create(dto); // What if it fails?
}

// ✅ GOOD - Proper error handling
@Post()
async create(@Body() dto: CreateUserDto) {
try {
return await this.service.create(dto);
} catch (error) {
if (error.code === 'P2002') {
throw new ConflictException('User already exists');
}
throw new InternalServerErrorException('Failed to create user');
}
}

❌ DON'T duplicate code

// ❌ BAD - Duplicated pagination logic
class UserController {
@Get()
async findUsers(@Query() query) {
const page = query.page || 1;
const pageSize = query.pageSize || 10;
const skip = (page - 1) * pageSize;
// ... pagination logic
}
}

class TenantController {
@Get()
async findTenants(@Query() query) {
const page = query.page || 1;
const pageSize = query.pageSize || 10;
const skip = (page - 1) * pageSize;
// ... same pagination logic duplicated!
}
}

// ✅ GOOD - Use shared pipe
import { PaginationPipe } from '@helix/shared/pipes';

class UserController {
@Get()
async findUsers(@Query(PaginationPipe) pagination: PaginationParams) {
return this.service.findAll(pagination);
}
}

class TenantController {
@Get()
async findTenants(@Query(PaginationPipe) pagination: PaginationParams) {
return this.service.findAll(pagination);
}
}

❌ DON'T put business logic in controllers

// ❌ BAD - Business logic in controller
@Controller('orders')
export class OrderController {
@Post()
async create(@Body() dto: CreateOrderDto) {
// Calculating total
const total = dto.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);

// Applying discount
const discount = total > 100 ? total * 0.1 : 0;
const finalTotal = total - discount;

// Creating order
return this.db.order.create({
data: {
...dto,
total: finalTotal,
},
});
}
}

// ✅ GOOD - Business logic in service
@Controller('orders')
export class OrderController {
constructor(private readonly orderService: OrderService) {}

@Post()
async create(@Body() dto: CreateOrderDto) {
return this.orderService.create(dto);
}
}

@Injectable()
export class OrderService {
calculateTotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}

applyDiscount(total: number): number {
return total > 100 ? total * 0.1 : 0;
}

async create(dto: CreateOrderDto): Promise<Order> {
const total = this.calculateTotal(dto.items);
const discount = this.applyDiscount(total);
const finalTotal = total - discount;

return this.db.order.create({
data: { ...dto, total: finalTotal },
});
}
}

Security

❌ DON'T commit secrets

// ❌ BAD - Hardcoded secrets
const STRIPE_API_KEY = 'sk_live_abc123...'; // NEVER!

// ✅ GOOD - Environment variables
const STRIPE_API_KEY = process.env.STRIPE_API_KEY;
if (!STRIPE_API_KEY) {
throw new Error('STRIPE_API_KEY is required');
}

❌ DON'T expose internal errors

// ❌ BAD - Exposing internal errors
@Post()
async create(@Body() dto: CreateUserDto) {
return await this.db.user.create({ data: dto });
// If Prisma error occurs, exposes database structure!
}

// ✅ GOOD - Generic error messages
@Post()
async create(@Body() dto: CreateUserDto) {
try {
return await this.db.user.create({ data: dto });
} catch (error) {
this.logger.error('Database error:', error); // Log details
throw new InternalServerErrorException('Failed to create user'); // Generic message
}
}

❌ DON'T skip input validation

// ❌ BAD - No validation
@Post('users/:id/role')
async updateRole(@Param('id') id: string, @Body() body: any) {
return this.service.updateRole(id, body.role);
// What if id is not a UUID?
// What if role is invalid?
}

// ✅ GOOD - Full validation
@Post('users/:id/role')
async updateRole(
@Param('id', UuidValidationPipe) id: string,
@Body() dto: UpdateRoleDto
) {
return this.service.updateRole(id, dto.role);
}

export class UpdateRoleDto {
@IsEnum(UserRole)
role: UserRole;
}

Database

❌ DON'T use raw SQL unless absolutely necessary

// ❌ BAD - Raw SQL
async findUsers(): Promise<User[]> {
return this.db.$queryRaw`SELECT * FROM users WHERE active = true`;
}

// ✅ GOOD - Prisma query
async findUsers(): Promise<User[]> {
return this.db.user.findMany({
where: { active: true },
});
}

❌ DON'T forget to include relations

// ❌ BAD - N+1 query problem
async getTenantsWithUsers(): Promise<Tenant[]> {
const tenants = await this.db.tenant.findMany();

for (const tenant of tenants) {
tenant.users = await this.db.user.findMany({
where: { tenantId: tenant.id },
}); // Separate query for each tenant!
}

return tenants;
}

// ✅ GOOD - Include relations
async getTenantsWithUsers(): Promise<Tenant[]> {
return this.db.tenant.findMany({
include: {
users: true, // Single query with JOIN
},
});
}

❌ DON'T mutate Prisma results directly

// ❌ BAD - Mutating Prisma object
const user = await this.db.user.findUnique({ where: { id } });
user.password = undefined; // Mutation can cause issues
return user;

// ✅ GOOD - Return new object
const user = await this.db.user.findUnique({ where: { id } });
const { password, ...userWithoutPassword } = user;
return userWithoutPassword;

// ✅ BETTER - Use Prisma select
const user = await this.db.user.findUnique({
where: { id },
select: {
id: true,
email: true,
name: true,
// Don't select password
},
});
return user;

Performance

❌ DON'T make unnecessary database calls

// ❌ BAD - Multiple queries
async getUserWithTenant(id: string) {
const user = await this.db.user.findUnique({ where: { id } });
const tenant = await this.db.tenant.findUnique({
where: { id: user.tenantId }
});
return { user, tenant };
}

// ✅ GOOD - Single query with include
async getUserWithTenant(id: string) {
return this.db.user.findUnique({
where: { id },
include: { tenant: true },
});
}

❌ DON'T forget to implement caching

// ❌ BAD - No caching for expensive operations
async getStats(): Promise<Stats> {
// Expensive aggregation query
return this.db.user.aggregate({
_count: true,
_avg: { age: true },
// ... complex calculations
});
}

// ✅ GOOD - Cache expensive operations
async getStats(): Promise<Stats> {
const cached = await this.cache.get('stats');
if (cached) return cached;

const stats = await this.db.user.aggregate({
_count: true,
_avg: { age: true },
});

await this.cache.set('stats', stats, 300); // 5 minutes
return stats;
}

Testing

❌ DON'T skip edge cases

// ❌ BAD - Only happy path
describe('divide', () => {
it('should divide two numbers', () => {
expect(divide(10, 2)).toBe(5);
});
});

// ✅ GOOD - Test edge cases
describe('divide', () => {
it('should divide two numbers', () => {
expect(divide(10, 2)).toBe(5);
});

it('should throw error for division by zero', () => {
expect(() => divide(10, 0)).toThrow('Cannot divide by zero');
});

it('should handle negative numbers', () => {
expect(divide(-10, 2)).toBe(-5);
});

it('should handle decimal results', () => {
expect(divide(10, 3)).toBeCloseTo(3.33, 2);
});
});

❌ DON'T write tests that depend on external services

// ❌ BAD - Depends on real API
describe('StripeService', () => {
it('should create customer', async () => {
const result = await service.createCustomer('test@example.com');
expect(result).toHaveProperty('id'); // Calls real Stripe API!
});
});

// ✅ GOOD - Mock external services
describe('StripeService', () => {
let mockStripe: jest.Mocked<Stripe>;

beforeEach(() => {
mockStripe = {
customers: {
create: jest.fn().mockResolvedValue({ id: 'cus_123' }),
},
} as any;
});

it('should create customer', async () => {
const result = await service.createCustomer('test@example.com');
expect(result).toEqual({ id: 'cus_123' });
expect(mockStripe.customers.create).toHaveBeenCalledWith({
email: 'test@example.com',
});
});
});

Coding Standards

TypeScript

// ✅ GOOD
interface User {
id: string;
email: string;
createdAt: Date;
}

function getUser(id: string): Promise<User | null> {
return db.user.findUnique({ where: { id } });
}

// ❌ BAD
function getUser(id) {
return db.user.findUnique({ where: { id } });
}

Naming Conventions

// ✅ GOOD - Clear, descriptive names
class UserManagementService { }
interface CreateUserDto { }
enum UserRole { }
const MAX_LOGIN_ATTEMPTS = 5;

// ❌ BAD - Unclear names
class UMS { }
interface DTO { }
enum Roles { }
const MAX = 5;

File Organization

✅ GOOD Structure:
apps/user-service/
├── src/
│ ├── main.ts
│ ├── app/
│ │ ├── app.module.ts
│ │ ├── app.controller.ts
│ │ └── app.service.ts
│ ├── users/
│ │ ├── users.module.ts
│ │ ├── users.controller.ts
│ │ ├── users.service.ts
│ │ ├── dto/
│ │ │ ├── create-user.dto.ts
│ │ │ └── update-user.dto.ts
│ │ └── entities/
│ │ └── user.entity.ts
│ ├── health/
│ │ ├── health.module.ts
│ │ └── health.controller.ts
│ └── config/
│ └── app.config.ts

❌ BAD Structure:
apps/user-service/
├── src/
│ ├── main.ts
│ ├── users.ts # Everything in one file
│ ├── stuff.ts # Unclear purpose
│ └── helpers.ts # Dumping ground

Imports

// ✅ GOOD - Organized imports
// Node modules
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

// Shared libraries
import { DatabaseService } from '@helix/shared/database';
import { CurrentUser } from '@helix/shared/decorators';
import { JwtAuthGuard } from '@helix/shared/guards';

// Local imports
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';

// ❌ BAD - Random order, no grouping
import { CreateUserDto } from './dto/create-user.dto';
import { Injectable } from '@nestjs/common';
import { UserService } from './user.service';
import { DatabaseService } from '@helix/shared/database';

Best Practices by Service Type

API Gateway

  • ✅ DO implement request ID tracking
  • ✅ DO implement rate limiting
  • ✅ DO implement timeout handling
  • ❌ DON'T add business logic (just routing)
  • ❌ DON'T connect directly to databases

Backend Services

  • ✅ DO implement health checks
  • ✅ DO use CLS for context propagation
  • ✅ DO implement proper error handling
  • ✅ DO use guards on all routes
  • ❌ DON'T expose internal service to public
  • ❌ DON'T skip authorization checks

Database Migrations

  • ✅ DO test migrations locally first
  • ✅ DO make migrations reversible when possible
  • ✅ DO backup before running in production
  • ❌ DON'T modify existing migrations
  • ❌ DON'T skip migration testing

Code Review Checklist

Before submitting PR, verify:

  • All tests passing (make test)
  • Code formatted (make format)
  • No linting errors (make lint)
  • TypeScript strict mode (no any)
  • All routes have guards (except /health)
  • Input validation on all endpoints
  • Error handling implemented
  • Shared libraries used where applicable
  • Documentation updated
  • Environment variables in .env.example
  • No secrets committed
  • Service builds successfully (make build-service)
  • Docker image builds (make docker-build-service)
  • Health endpoint works
  • Migrations tested (if applicable)

Performance Guidelines

DO ✅

  1. Use connection pooling - Prisma handles this automatically
  2. Implement caching - For expensive operations
  3. Use pagination - For large datasets
  4. Include only needed fields - Use Prisma select
  5. Batch operations - Use createMany, updateMany
  6. Index foreign keys - Add @@index in Prisma schema
  7. Use async/await - Don't block event loop

DON'T ❌

  1. Don't load entire tables - Always paginate
  2. Don't make N+1 queries - Use include or select
  3. Don't forget to close connections - Use try/finally
  4. Don't use SELECT * - Select only needed fields
  5. Don't skip indexes - Slow queries without them
  6. Don't block the event loop - Use async operations
  7. Don't ignore query performance - Monitor slow queries

Git Commit Guidelines

Commit Message Format

<type>(<scope>): <subject>

<body>

<footer>

Types:

  • feat - New feature
  • fix - Bug fix
  • docs - Documentation only
  • style - Code style (formatting, no logic change)
  • refactor - Code refactoring
  • test - Adding or updating tests
  • chore - Maintenance tasks

Examples:

# Good commits
git commit -m "feat(auth): add password reset functionality"
git commit -m "fix(kira): resolve OpenAI timeout issues"
git commit -m "docs(api-gateway): update routing documentation"
git commit -m "refactor(tenant): extract validation logic to service"

# Bad commits
git commit -m "stuff"
git commit -m "fixes"
git commit -m "wip"
git commit -m "asdf"

When to Create New Shared Library Code

Create in libs/shared/* when:

✅ Code is generic and usable by multiple services ✅ It's a common NestJS pattern (guard, pipe, decorator, filter) ✅ It's a utility function with no service-specific logic ✅ You find yourself copying code between services

Examples:

  • Email validation pipe → libs/shared/pipes/
  • Date formatting utility → libs/shared/utils/
  • Custom decorator → libs/shared/decorators/
  • Common interface → libs/shared/interfaces/

Keep in service when:

❌ Code is service-specific or domain-specific ❌ Code depends on service-specific models ❌ It's a one-off implementation ❌ It tightly couples services

Examples:

  • Stripe payment processing → Keep in service
  • User registration business logic → Keep in service
  • Service-specific DTOs → Keep in service
  • Domain models → Keep in service

Common Pitfalls

Pitfall 1: Circular Dependencies

// ❌ BAD - Circular dependency
// user.service.ts
import { TenantService } from '../tenant/tenant.service';

@Injectable()
export class UserService {
constructor(private tenantService: TenantService) {}
}

// tenant.service.ts
import { UserService } from '../user/user.service';

@Injectable()
export class TenantService {
constructor(private userService: UserService) {} // Circular!
}

// ✅ GOOD - Use forwardRef or event-driven
@Injectable()
export class UserService {
constructor(
@Inject(forwardRef(() => TenantService))
private tenantService: TenantService
) {}
}

Pitfall 2: Not Cleaning Up Subscriptions

// ❌ BAD - Memory leak
@Injectable()
export class SomeService implements OnModuleInit {
onModuleInit() {
this.eventEmitter.on('user.created', (user) => {
this.handleUserCreated(user);
}); // Never removed!
}
}

// ✅ GOOD - Clean up
@Injectable()
export class SomeService implements OnModuleInit, OnModuleDestroy {
private subscription: any;

onModuleInit() {
this.subscription = this.eventEmitter.on('user.created', (user) => {
this.handleUserCreated(user);
});
}

onModuleDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}

Pitfall 3: Not Handling Async Properly

// ❌ BAD - Unhandled promise
@Get()
findAll() {
this.service.findAll(); // Promise not awaited!
return { status: 'ok' };
}

// ✅ GOOD - Await async operations
@Get()
async findAll() {
const data = await this.service.findAll();
return { status: 'ok', data };
}

Documentation Standards

DO ✅

  1. Document public APIs - JSDoc for all public methods
  2. Update README - Keep service docs current
  3. Add code comments - For complex logic only
  4. Maintain examples - In README files
  5. Document breaking changes - In commit messages

Example:

/**
* Creates a new user in the system.
*
* @param dto - User creation data
* @returns Created user object
* @throws ConflictException if email already exists
* @throws ValidationException if data is invalid
*/
@Post()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('TENANT_ADMIN')
async create(@Body() dto: CreateUserDto): Promise<User> {
return this.userService.create(dto);
}

Need More Help?


Remember: These guidelines exist to maintain code quality, consistency, and scalability. When in doubt, look at existing code in the platform for patterns to follow.