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
workspaceIdfield with foreign key toWorkspace - Add
@@index([workspaceId])for query performance - Add
createdAt DateTime @default(now())andupdatedAt DateTime @updatedAt - Use
onDelete: Cascadefor 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
AppErrorfor expected error cases - Add explicit return types to functions
Route
- Create route:
apps/api/src/routes/feature.routes.ts - Add
authenticatemiddleware - 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
Navigation
- Add entry to sidebar navigation config
- Set
minRolefor 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
| Layer | Create | Modify |
|---|---|---|
| Database | - | apps/api/prisma/schema.prisma, migration file |
| Shared | types/feature.types.ts, validators/feature.validators.ts | types/index.ts, validators/index.ts |
| Backend | services/feature.service.ts, routes/feature.routes.ts | routes/index.ts (register route) |
| Frontend | pages/feature/*.tsx | Router, sidebar config |
| i18n | i18n/en/feature.json, i18n/ar/feature.json | - |
| Tests | __tests__/feature.*.test.ts | - |
Cross-Reference
- Code Style -- naming and pattern conventions
- PR Process -- submitting changes
- Testing -- what and how to test
- Adding New Types -- shared package guide
- Migrations -- database migration workflow
- Middleware -- available middleware stack