VeztaVezta
Guides

Backend Development

NestJS modules, DTOs, migrations, and job scheduling

The Vezta backend is a NestJS application running on Fastify (not Express) with Prisma ORM, PostgreSQL, and Redis/BullMQ for background jobs. It serves the REST API on port 3001 and provides real-time data via Socket.IO.

Module Pattern

All business modules live in src/modules/<name>/. Each module follows a standard structure:

src/modules/trading/
├── trading.module.ts       # NestJS module definition
├── trading.controller.ts   # HTTP routes
├── order.service.ts        # Business logic
├── dto/                    # Request/response DTOs
│   ├── create-order.dto.ts
│   └── order-response.dto.ts
└── order.processor.ts      # BullMQ job handler (optional)

Modules are registered in app.module.ts under two groups:

  • Core -- market, trading, portfolio, merge-split, sniper, counter-trade, arbitrage
  • Platform -- leaderboard, tracker, activity, notification, rewards, events, ai-predictions, monitor, alerts, user, account, kyc, copy-trade, search, and more

Adding a New Module

Create the Module Folder

mkdir -p src/modules/your-feature/dto

Define DTOs

Use class-validator decorators for request validation and @ApiProperty for OpenAPI spec generation:

import { IsString, IsNumber, Min } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class CreateFeatureDto {
  @ApiProperty({ description: 'Feature name' })
  @IsString()
  name: string;

  @ApiProperty({ description: 'Value in USD', example: 10.50 })
  @IsNumber()
  @Min(0)
  value: number;
}

Create the Controller

Tag with @ApiTags for Swagger grouping. All routes require JWT by default -- use @Public() to exempt endpoints.

import { Controller, Post, Body } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';

@ApiTags('your-feature')
@ApiBearerAuth('access-token')
@Controller('api/v1/your-feature')
export class YourFeatureController {
  constructor(private readonly service: YourFeatureService) {}

  @Post()
  @ApiOperation({ summary: 'Create a feature' })
  create(@Body() dto: CreateFeatureDto) {
    return this.service.create(dto);
  }
}

Register in app.module.ts

Import the module in the imports array of AppModule.

Database (Prisma)

The schema is at prisma/schema.prisma with 39 models organized by domain. After modifying the schema:

pnpm prisma:migrate    # Create and apply migration
pnpm prisma:generate   # Regenerate Prisma client

Financial values must use the Decimal type in Prisma -- never JavaScript floats for money. Standard precisions: @db.Decimal(18, 2) for USD amounts, @db.Decimal(10, 6) for prices, @db.Decimal(5, 4) for rates.

The PrismaModule is @Global(), so PrismaService is available everywhere without explicit imports.

Background Jobs (BullMQ)

BullMQ queues are registered per-module via BullModule.registerQueue(). The standard pattern for scheduled jobs:

  1. Create a processor class with @Processor('queue-name') and @Process('job-name')
  2. Implement OnModuleInit to register repeatable jobs at startup
  3. Clean old repeatable jobs before registering new ones to avoid duplicates

Existing schedulers include: MarketSyncScheduler (market data every 60s), SignalIngestionScheduler (news every 5-15min), SpreadScannerScheduler (arbitrage detection), and RewardsScheduler (point calculations).

BullModule.forRoot() uses REDIS_HOST and REDIS_PORT env vars, not REDIS_URL. In Docker, REDIS_HOST must be the container name (e.g., vezta-redis).

Error Handling

All errors must use ApiException for machine-readable error codes:

import { ApiException } from '@/common/exceptions/api.exception';
import { HttpStatus } from '@nestjs/common';

throw new ApiException(
  'ORDER_FAILED',
  'Insufficient balance to place order',
  HttpStatus.BAD_REQUEST,
);

The GlobalExceptionFilter normalizes all errors to { statusCode, code, message }. See Error Codes for the full reference.

Dev Commands

pnpm start:dev          # Dev server with hot-reload (port 3001)
pnpm build              # Compile TypeScript
pnpm test               # Jest unit tests
pnpm test:e2e           # E2E tests (requires --runInBand)
pnpm prisma:migrate     # Run DB migrations
pnpm prisma:generate    # Regenerate Prisma client
pnpm prisma:studio      # Launch Prisma Studio GUI
pnpm export:openapi     # Export OpenAPI spec to openapi.json
pnpm lint               # ESLint with auto-fix

Key Conventions

  • Path alias: @/* maps to src/*
  • Config: configService.get<string>('app.jwt.secret') for runtime values, process.env for bootstrap-time values only
  • Swagger: always add @ApiTags, @ApiOperation, @ApiResponse to controllers -- the frontend generates its API client from this spec
  • Guards: global JwtAuthGuard applied via APP_GUARD. Use @Public() to exempt endpoints

On this page