Project management backend (NestJS)
Type: ReferenceCreated: Team: Platform
draft
About this prompt
This is the exact review prompt the AI reviewer runs for the project management backend pull requests, applied on top of the shared review rules. Skim it before you raise a PR — most of what it flags is something you can catch and fix yourself first.
# PM Backend Review Guidelines
> NestJS 11 + TypeScript 5 (strict) + GraphQL (Apollo Server 5 / code-first) + TypeORM 0.3 + PostgreSQL + Redis (ioredis) + OpenSearch 3.5 + AWS S3 + SendGrid + graphql-ws + class-validator + Zod
## Architecture
GraphQL-first multi-tenant backend for the project management module. Data flows primarily through **GraphQL resolvers** (queries, mutations, subscriptions) backed by a **service → repository → TypeORM** stack. A small number of **REST controllers** exist for health monitoring, internal service triggers, and third-party OAuth callbacks. Real-time updates reach clients via **GraphQL subscriptions** over `graphql-ws`, powered by an in-process PubSub service.
### Module structure
```
src/
app.module.ts → Root module (global guards, exception handler, middleware)
graphql.config.ts → Apollo Driver config (schema auto-gen, subscriptions, error formatting)
main.ts → Bootstrap (CORS, global pipes, static assets)
board/ → Core PM domain (feature sub-modules)
entities/ → Shared/base entities
enums/ → Domain enums
errors/ → Board-specific error code enums
modules/ → Feature modules (one directory per domain concept)
ai/ → AI insights & analytics
constants/
enums/
modules/
integrations/ → External integrations (OAuth, migration, API wrappers)
planning/ → Planning module (placeholder)
retro/ → Retrospectives module (placeholder)
common/ → Shared infrastructure
config/ → Environment validation, HTTP client config
constants/ → Auth, tenant, project, database, validation constants
context/ → AsyncLocalStorage-based context holders
decorators/ → Custom resolver/parameter decorators
enums/ → Shared enums
errors/ → Common error code enums
exceptions/ → Custom exception classes and global handler
guards/ → Global guards (tenant, auth, project, API key, resource)
middleware/ → Request-scoped context initialization
modules/ → Infrastructure modules (auth, database, cache, email, notification, pubsub, search, storage, etc.)
pipe/ → Global input pipes
repository/ → Base repository abstract class
scalars/ → Custom GraphQL scalars
types/
utils/ → Shared utility functions
```
### Data layer
```
GraphQL (primary):
Resolver → Service → Repository
→ Tenant-aware execution context (read/write split)
→ Schema switching per tenant
→ Write DataSource (PostgreSQL) — mutations
→ Read DataSource (PostgreSQL) — queries
REST (internal endpoints):
Controller → Service → same repository stack
Side effects (fire-and-forget after core mutation):
Service → PubSub → WebSocket subscriptions
Service → Notifications → DB persist + real-time push
Service → Email → Transactional emails
Service → Search index → Full-text search updates
Service → Metrics → Sprint burndown snapshots
Service → Cache → Invalidation
Service → History → Audit trail writes
```
### Multi-tenancy model
- **Schema-per-tenant**: PostgreSQL `SET search_path TO "{tenantId}"` executed per query runner
- **Read/write splitting**: Separate DataSource instances (`TENANT_DATABASE_WRITE_URL`, `TENANT_DATABASE_READ_URL`)
- **Connection pooling**: Write 10 max / Read 10 max, idle 10 min timeout
- **Tenant isolation enforced at**: guards (TenantGuard), context (TenantContext), repository (BaseRepository), cache (Redis key prefix), search (OpenSearch tenantId field)
### Request lifecycle
```
Request
→ Middleware: establish request-scoped async context
→ Tenant guard: extract and validate tenant header, set tenant context
→ Auth guard: extract and validate JWT, set user context (ID, roles)
→ Project guard: extract project header, resolve from cache, set project context
→ API key guard: validate internal API key (if required by endpoint)
→ Resource guard: extract optional resource ID
→ Input pipes: trim strings, validate DTOs
→ Resolver → Service → Repository → Database
```
### State management
- **Request-scoped context** via AsyncLocalStorage — four context holders for tenant, auth, project, and resource isolation, populated by the guard chain
- **Caching** via Redis with tenant-aware key prefixing
- **Real-time** via PubSub with project-scoped channel naming
### Key conventions
- **Path alias**: `@/*` maps to `src/*`, `@shared/*` maps to `../shared/*` — never use deep relative imports
- **File naming**: `feature-name.type.ts` pattern — `.entity.ts`, `.repository.ts`, `.service.ts`, `.resolver.ts`, `.module.ts`
- **Subscription resolvers**: Separate file from the main resolver (`*-subscription.resolver.ts`)
- **DTOs**: `dtos/` subdirectory per module, split by input types and response/connection types
- **Errors**: `errors/` directory with one enum file per domain concept
- **Enums**: `enums/` directory, one enum per file with GraphQL registration at module level
- **Modules**: Explicit imports, providers, exports; global decorator for cross-cutting modules
- **Dual decorators**: Entities decorated for both TypeORM and GraphQL (code-first schema generation)
- **Constructor injection**: All dependencies via constructor — never property injection
- **Audit columns**: All business entities extend a base auditable class (timestamps + user tracking) via a TypeORM subscriber
- **Soft deletes**: Boolean flag with explicit filter in all queries — not a framework-level soft delete decorator
- **Cursor pagination**: Relay-style Connection/Edge/PageInfo pattern with Base64-encoded composite cursors
- **Fractional indexing**: String-based order indices for drag-and-drop reordering without renumbering
---
## What to flag
### GraphQL resolver issues
- Business logic in resolvers — resolvers must only delegate to service methods; filtering, validation, and data transformation belong in services
- Missing project guard decorator on project-scoped queries/mutations — all operations that access project data must have project context extracted and validated
- Missing role parameter on the project guard decorator for admin-only operations (e.g., destructive mutations)
- Mutations missing description in the GraphQL decorator — all operations must have descriptions for schema documentation
- Arguments without explicit GraphQL type declaration for non-object arguments — scalar types must declare their GraphQL type explicitly
- ResolveField methods that don't check for eager-loaded data before making service calls — always check if the parent already has the relation loaded to avoid N+1 queries
- Subscription resolvers not using the custom parameter decorators for project key / user ID extraction — subscriptions extract context from connection params, not HTTP headers
- Resolver methods that catch and swallow errors — let exceptions propagate to the global exception handler for consistent error formatting
- Missing nullable declaration on queries/mutations/field resolvers when the return type can be null — GraphQL schema must reflect nullability
### Service layer issues
- Missing validation before mutations — validate input constraints and business rules (existence checks, date range validation, uniqueness) before performing the core operation
- Missing side-effect orchestration after mutations — the established pattern is: core mutation → history → subscriptions → email → notifications → search indexing → metrics tracking → cache invalidation (order matters)
- Side effects inside a transaction — pub/sub, email, notifications, and search indexing must happen AFTER the transaction commits, not inside it
- Direct repository calls from resolvers — always go through the service layer
- Services that import other service implementations directly instead of their module — use module imports and let DI resolve the dependency
- Missing custom exception throws for error cases — use the typed error code system, never raw errors or framework HTTP exceptions
- Catching generic exceptions instead of specific types — let the global exception handler handle untyped errors
- Empty catch blocks that swallow errors silently
- Service methods that don't use the request-scoped context holders when they should — implicit context is the established pattern, avoid passing IDs as parameters when context is available
- Missing search index updates after entity mutations — every create/update/delete on indexed entities must trigger the corresponding index update
- Missing cache invalidation after entity mutations that affect cached data
- Missing notification/email dispatch for user-facing mutations
- Missing history record creation for item field changes — audit history must be created with before/after values
### Repository & database issues
- Queries without soft-delete filter — all reads must exclude soft-deleted records
- Missing transaction wrapper for multi-entity writes — operations that save to multiple tables must be atomic
- Database operations outside of the tenant-aware execution context — all queries must go through the read/write context methods for proper tenant schema switching
- Unbounded queries without pagination on potentially large tables — use cursor-based pagination for user-facing data
- Missing cursor validation — Base64 decode and JSON parse must handle malformed cursors gracefully
- QueryBuilder without parameter binding — always use parameterized placeholders, never string interpolation
- N+1 query patterns in loops — use join-and-select in the QueryBuilder or batch-fetch related entities
- Missing deterministic ordering with pagination — cursor pagination requires a composite ORDER BY (typically field + id)
- Write execution context used for read-only operations — reads must use the read context to route to read replicas
- Transactions that include non-database operations (HTTP calls, file uploads, email sends) — these should happen after the transaction
- Missing error handling for transactions — transactions auto-rollback on exception, but the calling code must handle or propagate the error
### Entity & TypeORM issues
- Entities not extending the base auditable class — all business entities must have audit fields (timestamps + user tracking)
- Missing GraphQL ObjectType decorator on entities returned in GraphQL responses — entities serve as both database models and GraphQL types
- Missing GraphQL Field decorator on properties that should be exposed in the schema
- Primary key columns without explicit bigint type — all primary keys use bigint
- Columns without explicit PostgreSQL type — always specify the database type
- Enum columns without the enum class reference — TypeORM needs it for schema mapping
- Missing JoinColumn with explicit FK column name on relationship owners — FK names must be in snake_case
- Cascade usage without justification — cascades can cause accidental deletions; only use on true parent-child compositions
- Eager fetch on collections (OneToMany, ManyToMany) — causes N+1 queries; use explicit joins in QueryBuilder
- Column name mismatch between TypeORM decorator and actual database column — must match in snake_case
- Missing GraphQL enum registration for new enums — enums used in the schema must be registered at module level
### DTO & validation issues
- Input DTOs without class-validator decorators — all input fields must be validated
- Missing non-empty validator on required fields
- Missing nested validation + type transformer on nested object inputs — the transformer needs the type hint for proper instantiation
- Array validation without per-element flag — validators must apply to each array element individually
- Missing optional decorator paired with nullable GraphQL field declaration — both must be present for optional fields
- Missing enum validator on enum-typed fields
- Response DTOs with fields that expose internal IDs, tokens, or sensitive data that shouldn't reach the client
- Pagination DTOs not following the Connection/Edge/PageInfo pattern — cursor-based pagination is the convention
### Security & auth
- Missing project guard decorator on project-scoped operations — allows access without project membership validation
- Missing role check on admin-only operations
- Guest role not excluded from features they shouldn't access — guest users should only see projects they're explicitly added to
- Public decorator on endpoints that should require authentication — only health monitoring, internal triggers, and OAuth callbacks should be public
- Tenant-excluded decorator on endpoints that should validate tenant context
- API key comparison using plain equality instead of a timing-safe comparison
- JWT tokens or signing keys logged to console — sensitive data must never appear in logs
- Missing tenant validation on subscription connection params — WebSocket connections must validate tenant context
- User-generated content returned without sanitization
- Environment secrets accessed outside of the validated config singleton — always use the Zod-validated environment object
### Context management
- Missing async context propagation — any new middleware or async operation that runs outside the request lifecycle may lose context
- Accessing auth context in code paths reachable from public endpoints — will throw unauthorized
- Accessing project context in code paths reachable without the project guard — will throw missing context error
- Accessing tenant context in code paths reachable from tenant-excluded endpoints — will throw missing header error
- Setting context values outside of guards — context should only be set in the guard chain, not in services or resolvers
### Error handling
- Throwing raw errors or framework HTTP exceptions instead of the custom module exception with typed error codes
- New error scenarios without a corresponding enum entry in the appropriate errors file
- Missing error code for new database constraint violations — add to the database error mapping
- Custom exception thrown inside a catch block without logging the original error — always log before re-throwing
- Missing error code categorization — error codes are organized by module
### Caching
- Cache reads without fallback to database — cache misses must fall through to the repository
- Missing cache invalidation after mutations that change cached data
- Cache keys without tenant prefix — all tenant-scoped cache keys must include tenant ID via the cache service (which auto-injects it)
- Missing TTL on cache entries — every cache key definition must specify a TTL
- Caching mutable data without an invalidation strategy
### Search indexing
- Missing index update after entity CRUD — every create/update/delete on indexed entities must trigger the corresponding index update
- Index operations without tenant ID injection — the document service auto-injects tenant ID, but verify this happens
- Bulk operations without batch size limits
- Missing tenant access validation on search results
- New entity types not added to the global search index mapping
### Real-time subscriptions
- Missing subscription publish calls after mutations that affect subscribed data — verify all relevant channels are notified
- Subscription channel names not following the established naming convention
- Subscription connection params missing authentication and tenant validation
- Missing resource ID injection on published events — the pub/sub service auto-injects it, but direct publish calls won't
### Notification system
- Missing notification dispatch for user-facing mutations
- Not filtering out the current user from notification recipients — the actor should not receive their own notification
- Not respecting per-user/per-project notification preferences
- Missing unread count update after notification creation
- Notification template not using the user's language preference
### Module & dependency injection
- Missing global decorator on modules imported across the entire app
- Missing module imports for injected dependencies — every provider must have its module in imports
- Circular dependencies between modules — use forward references to break cycles
- Services exported from a module but not listed in the exports array
- New modules not registered in the appropriate parent module
### Performance
- Missing parallel execution for independent async operations
- N+1 queries from field resolvers — check that parent queries join necessary relations or that field resolvers check for pre-loaded data
- Unbounded queries without pagination on tables that grow with user data
- Large search queries without result size limits
- Missing select clause on QueryBuilder — avoid fetching all columns when only a few are needed
### Testing
- New services/repositories without corresponding test files
- Tests that don't mock the tenant database service — database operations must be isolated
- Missing mocks for request-scoped context holders in tests
- E2E tests that don't set required headers (tenant, authorization, project key)
- Tests that test implementation details instead of behavior (input → output)
### Naming & conventions
- **Resolvers** not decorated with the entity type for entity-specific resolvers
- **Entities** not in PascalCase
- **Repositories** not extending the base repository class
- **Services** not using the established file naming pattern
- **Error enums** not placed in the correct errors directory
- **Enum values** not matching the string literal pattern
- **New enums** missing GraphQL registration call
- **GraphQL field names** not in camelCase
- **Database column names** not in snake_case
- **DTO files** not in the DTOs subdirectory of their module
- **Module files** missing explicit imports, providers, or exports arrays
---
## What NOT to flag (false positives)
- **Dual ORM + GraphQL decorators on entities** — entities serve as both database models and GraphQL types; this is the code-first approach
- **Global decorator on many modules** — cross-cutting infrastructure modules are intentionally global
- **Static context accessors** — AsyncLocalStorage-based context propagation is the established pattern, not a code smell
- **Fire-and-forget side effects** (no error handling on email, notification, search index calls after mutations) — this is the accepted tradeoff; index methods use safe wrappers with internal error handling
- **No explicit transactional decorator** — transactions are managed manually in repositories; the framework doesn't have a built-in transactional annotation
- **Schema synchronization disabled in TypeORM** — migrations are managed externally, not by ORM auto-sync
- **Query logging disabled in TypeORM** — intentionally disabled for performance
- **Soft deletes via boolean flag** (not a framework-level soft delete decorator) — this is the established pattern across the codebase
- **Fractional indexing** (string-based ordering for drag-and-drop) — efficient reordering without renumbering entire lists
- **Multiple subscription channels per entity** — different channels serve different UI views
- **Constructor injection pattern everywhere** — standard NestJS dependency injection
- **No dedicated mapper layer** — manual entity-to-DTO transformation is the convention
- **Bigint stored as number** — the ORM maps PostgreSQL bigint to JavaScript number; IDs don't exceed safe integer limits
- **Date fields returned as strings in GraphQL** — ISO string serialization is the convention
- **Placeholder modules** — future features with empty module registrations
- **Multiple SSL-related environment variables** — they control different aspects of SSL configuration
- **Guest role having limited access** — this is by design, not a bug
- **Automatic resource ID injection on pub/sub events** — the pub/sub service injects it for multi-client isolation
- **REST controllers alongside GraphQL** — health monitoring, internal triggers, and OAuth callbacks intentionally use REST
- **ESLint/Prettier fixable issues** (import order, formatting, trailing commas) — caught by automated tooling
- **No scheduled jobs** — the backend is stateless; scheduled operations are triggered externally
- **Redis used for both caching and pub/sub** — the client supports both use cases
---
## Checklist
Verify each item for every changed file:
### Resolvers
1. **Decorator completeness**: Project guard on project-scoped operations? Role array when admin-only? Description on every query/mutation/subscription? Nullable declared when return can be null?
2. **Argument types**: Explicit GraphQL type on scalar arguments? Nested validation on object inputs? Optional arguments have nullable + default value?
3. **ResolveField safety**: Checks for eager-loaded data on parent before making a service call? Handles null foreign keys? Returns correct type (array vs single)?
4. **Subscription correctness**: Uses custom parameter decorators for project key / user ID extraction? Returns async iterator with correct channel naming?
### Services
5. **Side-effect orchestration**: After mutations — history created? Subscriptions published? Email sent? Notifications dispatched (current user filtered out)? Search index updated? Metrics tracked? Cache invalidated?
6. **Error handling**: Custom exception with typed error code for domain errors? No raw errors or framework HTTP exceptions? No swallowed exceptions? New error scenarios have corresponding enum entries?
7. **Validation sequence**: Business rules validated BEFORE the core mutation? Existence checks throw if entity missing? Date ranges validated? Uniqueness checked?
8. **Context usage**: Uses request-scoped context holders where appropriate? No context access in code paths reachable from public endpoints?
### Repositories
9. **Tenant isolation**: All operations inside the tenant-aware read/write execution context? Mutations in write context, reads in read context? Soft-delete filter in all WHERE clauses?
10. **Transaction safety**: Multi-entity writes wrapped in a transaction? No HTTP/email/pub-sub calls inside transactions? Entity manager passed through to helper methods within the transaction?
11. **Query correctness**: Parameterized queries (no string interpolation)? Deterministic ordering with pagination? Fetch one extra for hasNextPage detection? Cursor decode/encode consistent?
12. **Performance**: Join-and-select for needed relations (not lazy-loaded in loops)? Select clause when full entity not needed? Parallel execution for independent queries?
### Entities & DTOs
13. **Entity decoration**: Extends base auditable class? Both ORM and GraphQL type decorators? Bigint primary key? Explicit database column types? Explicit FK column names in snake_case?
14. **Enum registration**: New enums registered for GraphQL schema generation? Enum values match string literal pattern? Enum class referenced in column decorator?
15. **Input validation**: Input type + field + validator decorators on all fields? Nested validation + type transformer for nested objects? Per-element flag for array validators? Optional decorator paired with nullable field?
### Security & auth
16. **Access control**: Project guard with correct role array? Public decorator only on genuinely public endpoints? API key guard on internal-only endpoints? Guest users restricted appropriately?
17. **Tenant isolation**: Tenant guard not bypassed except for cross-tenant operations? Search queries include tenant filter? Cache keys include tenant prefix? Subscription channels scoped to project?
18. **Data exposure**: No secrets in logs? No internal IDs/tokens in GraphQL response types? User-generated content sanitized?
### Infrastructure
19. **Caching**: Cache invalidation after mutations that affect cached data? TTL specified on all cache entries? Tenant-aware key generation? Fallback to DB on cache miss?
20. **Search indexing**: Index updated after entity CRUD? New entity types added to global search mapping? Bulk operations batch-limited? Tenant access verified on reads?
21. **Notifications**: All user-facing mutations trigger appropriate notifications? Current user filtered from recipients? User preferences respected? Unread count updated? Template language-aware?
22. **Module wiring**: New modules registered in parent module? Services in providers? Exports declared? Dependency modules in imports? No circular dependencies?
### Code quality
23. **Dead code**: Unused imports, unreachable branches, variables set but never read, empty catch blocks, commented-out code, unused constructor injections?
24. **Naming conventions**: Entities PascalCase, columns snake_case, enums string literal pattern, files follow established naming, error enums in correct directory?
25. **Behavioral preservation in refactors**: Same side effects triggered? Same error codes thrown? Same subscription channels published? Same cache invalidated? Same history records created? Same notification recipients?