Adding a backend module
A new feature module — say, referrals — follows this shape.
Layout
src/referrals/
referrals.module.ts
referrals.controller.ts
referrals.service.ts
dto/
create-referral.dto.ts
referral.dto.ts
Module
// referrals.module.ts
import { Module } from "@nestjs/common";
import { ReferralsController } from "./referrals.controller";
import { ReferralsService } from "./referrals.service";
@Module({
controllers: [ReferralsController],
providers: [ReferralsService],
exports: [ReferralsService],
})
export class ReferralsModule {}
Register in app.module.ts:
imports: [
// ...
ReferralsModule,
],
DTOs
// dto/create-referral.dto.ts
import { IsEmail, IsOptional, IsString } from "class-validator";
import { ApiProperty } from "@nestjs/swagger";
export class CreateReferralDto {
@ApiProperty()
@IsEmail()
invitedEmail: string;
@ApiProperty({ required: false })
@IsOptional()
@IsString()
message?: string;
}
@ApiProperty is what surfaces the field in the generated openapi.json. Don't skip it — the docs site won't see your endpoint otherwise.
Service
Inject DRIZZLE:
// referrals.service.ts
import { Inject, Injectable } from "@nestjs/common";
import { DRIZZLE, Drizzle } from "../db/drizzle.module";
import * as schema from "../db/schema";
@Injectable()
export class ReferralsService {
constructor(@Inject(DRIZZLE) private readonly db: Drizzle) {}
async create(userId: string, dto: CreateReferralDto) {
const [row] = await this.db
.insert(schema.referrals)
.values({ inviterId: userId, ...dto })
.returning();
return row;
}
}
Controller
// referrals.controller.ts
import { Body, Controller, Get, Post, Req, UseGuards } from "@nestjs/common";
import { ApiTags, ApiBearerAuth } from "@nestjs/swagger";
@ApiTags("referrals") // groups in OpenAPI / docs sidebar
@ApiBearerAuth()
@Controller("referrals")
export class ReferralsController {
constructor(private readonly svc: ReferralsService) {}
@Post()
create(@Req() req, @Body() dto: CreateReferralDto) {
return this.svc.create(req.user.id, dto);
}
}
@ApiTags("referrals") is what makes endpoints group under "Referrals" in the generated reference.
Schema
In src/db/schema/business.ts:
export const referrals = pgTable("referrals", {
id: uuid("id").primaryKey().defaultRandom(),
inviterId: uuid("inviter_id").notNull().references(() => user.id, { onDelete: "cascade" }),
invitedEmail: text("invited_email").notNull(),
message: text("message"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
}, (t) => ({
uniqueInvitee: uniqueIndex("referrals_unique").on(t.inviterId, t.invitedEmail),
}));
Then:
pnpm db:generate --name add_referrals
# review drizzle/00NN_add_referrals.sql
git add . && git commit -m "feat(referrals): add table + module"
pnpm db:migrate
Audit
Audit logs are automatic — every request goes through AuditLogMiddleware. If you want business-level events:
await this.auditLogger.log({
event: "referral_created",
properties: { inviterId, invitedEmail },
});
Inject AUDIT_LOGGER via DI.
Auth
If your endpoint needs platform_admin:
import { PlatformAdminGuard } from "../auth/guards/platform-admin.guard";
@UseGuards(PlatformAdminGuard)
@Post()
adminOnly(...) {}
Bearer auth (any authenticated user) is the default if AuthMiddleware sets req.user and your controller uses it.
Test
Add a unit test next to the service file (referrals.service.spec.ts) using NestJS's testing module. Integration tests live in test/.
Update the OpenAPI spec
pnpm start:dev # NestJS Swagger emits openapi.json on the fly
Then in marketplace-docs:
pnpm refresh-api
The new endpoints appear in the sidebar.
Update permissions if needed
If you added a new resource that should be gated, update src/auth/permissions.ts AND the frontend mirror.