# Recipes Step-by-step instructions for common development tasks. For module structure conventions and shared service patterns, see the [[02 - Backend]] architecture reference. --- ## Recipe: Add a New Backend Module 1. Create the module directory: `backend/src/modules/my-feature/` 2. Create `my-feature.module.ts`: ```typescript import { Module } from '@nestjs/common'; import { PrismaModule } from '../../shared/prisma/prisma.module'; import { AuditModule } from '../../shared/audit/audit.module'; import { MyFeatureController } from './my-feature.controller'; import { MyFeatureService } from './my-feature.service'; @Module({ imports: [PrismaModule, AuditModule], controllers: [MyFeatureController], providers: [MyFeatureService], }) export class MyFeatureModule {} ``` 3. Create `my-feature.controller.ts` with the required decorators: ```typescript import { Controller, UseGuards, Request } from '@nestjs/common'; import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; @ApiTags('my-feature') @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Controller('my-feature') export class MyFeatureController { constructor(private readonly myFeatureService: MyFeatureService) {} } ``` 4. Create `my-feature.service.ts` with `PrismaService` injection. Always scope DB queries to `req.user.teamId`: ```typescript import { Injectable } from '@nestjs/common'; import { PrismaService } from '../../shared/prisma/prisma.service'; @Injectable() export class MyFeatureService { constructor(private readonly prisma: PrismaService) {} } ``` 5. Register the module in `AppModule` imports array at `backend/src/app.module.ts` 6. If the worker also needs access to this module's services (e.g. for use in a queue processor), register it in `WorkerModule` at `backend/src/worker.module.ts` as well > **Audit logging:** If the module performs user-facing mutations (create/update/delete), it must import `AuditModule` and inject `AuditService`. Call `AuditService.log(actorId, entityType, entityId, action, before, after)` in every mutation. See [[04 - Backend Architecture Patterns]] for the full audit pattern. --- ## Recipe: Add a New Lint Rule 1. Create `backend/src/modules/linting/rules/my-rule.rule.ts`: ```typescript import { LintRule, LintIssue } from './base.rule'; import { LintSeverity } from '@prisma/client'; export class MyRule implements LintRule { code = 'MY_RULE_CODE'; severity = LintSeverity.WARNING; check(video: any): LintIssue | null { if (/* condition */) { return { targetField: 'title', message: 'Describe the problem clearly', fixSuggestion: 'Explain how to fix it', }; } return null; } } ``` 2. Open `backend/src/modules/linting/linting.service.ts`, import the new rule class, and add an instance to the `rules[]` array: ```typescript private readonly rules: LintRule[] = [ new ExistingRule(), new MyRule(), // add here ]; ``` 3. The rule runs automatically on all subsequent lint jobs. It can be disabled per-team in the Settings page (lint rule disabling is stored on the `Team` model). See [[03 - Metadata Linting]] for the full lint system overview. --- ## Recipe: Add a New API Endpoint to the Frontend 1. If the endpoint returns a new response shape, add a TypeScript interface to `frontend/src/lib/api.ts`: ```typescript export interface MyResponse { id: string; name: string; // ... } ``` 2. Add the API function in the same file: ```typescript export const myNewAction = (id: string, data: MyData): Promise => apiClient.post(`/my-feature/${id}/action`, data).then(r => r.data); ``` 3. Use it in a component with TanStack Query: ```typescript const qc = useQueryClient(); // must be called at component level, not inside a callback const mut = useMutation({ mutationFn: () => myNewAction(id, data), onSuccess: () => { qc.invalidateQueries({ queryKey: ['affected-key'] }); }, }); ``` > See [[03 - Component Patterns]] for TanStack Query conventions, including `placeholderData` usage and multi-key invalidation. --- ## Recipe: Add a New Sidebar Navigation Item 1. Open `frontend/src/components/shared/Sidebar.tsx` 2. Import the icon from `lucide-react`: ```typescript import { MyIcon } from 'lucide-react'; ``` 3. Add an entry to the `navItems` array: ```typescript { label: 'My Page', href: '/my-page', icon: MyIcon, group: 'WORKSPACE' }, ``` Available groups and their current members: | Group | Used for | |---|---| | `WORKSPACE` | Overview, Videos, Calendar | | `OPERATIONS` | Bulk Jobs, Import/Export | | `PEOPLE` | Collaborators, Teams | | `LOGGING` | Audit Log, Quota | 4. Create the corresponding route at `frontend/src/app/(dashboard)/my-page/page.tsx` --- ## Recipe: Add a New Prisma Field 1. Add the field to the appropriate model in `backend/prisma/schema.prisma` 2. Stop the backend (and worker if running) 3. Create and apply the migration: ```bash npx prisma migrate dev --name add-my-field ``` 4. Regenerate the Prisma client: ```bash npx prisma generate ``` 5. Restart the backend and worker > **Never remove enum values** from the Prisma schema. PostgreSQL enum removal requires a raw SQL migration and risks data loss if any rows reference the removed value. Mark deprecated values in the UI instead. See [[04 - Gotchas]] for detail. --- ## Recipe: Add a New System Token (e.g. `{video.myField}`) System tokens are built-in substitution tokens like `{video.title}` or `{collab.youtube}` that are handled by dedicated resolvers rather than team variable lookup. 1. Add the new token definition to `SYSTEM_VARIABLES[]` in: `backend/src/shared/system-variables/system-variables.registry.ts` 2. Add the token string to the `SYSTEM_VARIABLE_TOKENS` Set in the same file: ```typescript export const SYSTEM_VARIABLE_TOKENS = new Set([ // existing tokens... '{video.myField}', ]); ``` 3. Implement the resolution logic in `RenderEngineService` (`backend/src/shared/render-engine/render-engine.service.ts`) in the appropriate resolver method > **Critical:** Without step 2, `resolveVariables()` will treat the token as a team variable key, find nothing, and silently produce an empty string in the rendered description. This is a common source of invisible rendering bugs. See [[07 - Render Engine]] for the full token resolution pipeline.