Skip to main content

New Feature Checklist

When adding a new feature to UniPulse, follow this checklist to ensure consistency across all layers of the stack. This is the canonical guide for end-to-end feature development.


Overview


1. Database Layer

  • Add Prisma model(s) to apps/api/prisma/schema.prisma
  • Include workspaceId field with foreign key to Workspace
  • Add @@index([workspaceId]) for query performance
  • Add createdAt DateTime @default(now()) and updatedAt DateTime @updatedAt
  • Use onDelete: Cascade for workspace relation
  • Create migration: cd apps/api && npx prisma migrate dev --name add_feature_name
  • Review generated SQL in prisma/migrations/
  • Verify migration applies cleanly
model NewFeature {
id String @id @default(cuid())
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
name String
// ... fields
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([workspaceId])
}

2. Shared Package

  • Create type file: packages/shared/src/types/feature.types.ts
  • Define entity interfaces and input types
  • Create validator file: packages/shared/src/validators/feature.validators.ts
  • Define Zod schemas with appropriate constraints
  • Use z.infer<typeof schema> for type inference
  • Export from packages/shared/src/types/index.ts
  • Export from packages/shared/src/validators/index.ts
  • Build: npm run build --filter=shared
// types/feature.types.ts
export interface NewFeature {
id: string;
workspaceId: string;
name: string;
createdAt: string;
updatedAt: string;
}

// validators/feature.validators.ts
export const createFeatureSchema = z.object({
name: z.string().min(1).max(255),
});
export type CreateFeatureInput = z.infer<typeof createFeatureSchema>;

3. Backend

Service

  • Create service: apps/api/src/services/feature.service.ts
  • Implement CRUD operations scoped by workspaceId
  • Throw AppError for expected error cases
  • Add explicit return types to functions

Route

  • Create route: apps/api/src/routes/feature.routes.ts
  • Add authenticate middleware
  • Add requireWorkspace(minRole) middleware with appropriate role
  • Add requireFeature(featureKey) if feature-gated (optional)
  • Add requireQuota(quotaKey) if quota-limited (optional)
  • Add validate(schema) middleware for POST/PATCH/PUT
  • Register route in main router (apps/api/src/routes/index.ts)
// routes/feature.routes.ts
const router = Router();

router.get('/', authenticate, requireWorkspace('VIEWER'), async (req, res, next) => {
try {
const items = await featureService.getAll(req.workspace.id);
res.json(items);
} catch (error) { next(error); }
});

router.post('/', authenticate, requireWorkspace('EDITOR'), validate(createFeatureSchema), async (req, res, next) => {
try {
const item = await featureService.create(req.workspace.id, req.body);
res.status(201).json(item);
} catch (error) { next(error); }
});

Queue (if async processing needed)

  • Create queue: apps/api/src/queues/feature.queue.ts
  • Create worker with appropriate concurrency
  • Configure retry strategy (attempts, backoff)
  • Handle job failures gracefully

4. Frontend

Page Component

  • Create page directory: apps/web/src/pages/feature/
  • Create main page component(s)
  • Use TanStack Query for data fetching
  • Use Zod + React Hook Form for forms
  • Use shadcn/ui components for UI
  • Handle loading, error, and empty states

Routing

  • Add route to router with appropriate guard
  • Add lazy loading: const Feature = lazy(() => import('./pages/feature/Feature'))
  • Wrap in <RoleGuard minRole="..."> if needed
  • Add entry to sidebar navigation config
  • Set minRole for visibility filtering
  • Choose appropriate Lucide icon

State Management

  • Create TanStack Query hooks for API calls
  • Create Zustand store if client-only state is needed (usually not needed)

Internationalization

  • Add English translations: apps/web/src/i18n/en/feature.json
  • Add Arabic translations: apps/web/src/i18n/ar/feature.json
  • Use t('feature:key') for all user-facing strings
  • Test RTL layout with Arabic

5. Testing

  • Service tests: business logic, workspace scoping, error cases
  • Validator tests: valid input, invalid input, edge cases
  • Route tests: auth guards, validation, response format (optional but recommended)
  • Component tests: rendering, user interactions (optional)
  • All tests pass: npm run test

6. Documentation & PR

  • Update developer documentation if architecture changed
  • Create PR with clear description of changes
  • PR passes CI (build + lint + typecheck)
  • At least one team review
  • New environment variables documented in .env.example (if any)

Quick Reference: Files to Create/Modify

LayerCreateModify
Database-apps/api/prisma/schema.prisma, migration file
Sharedtypes/feature.types.ts, validators/feature.validators.tstypes/index.ts, validators/index.ts
Backendservices/feature.service.ts, routes/feature.routes.tsroutes/index.ts (register route)
Frontendpages/feature/*.tsxRouter, sidebar config
i18ni18n/en/feature.json, i18n/ar/feature.json-
Tests__tests__/feature.*.test.ts-

Cross-Reference