This document provides guidance for Claude Code when working on this journal application codebase.
A full-stack journal application with event-driven architecture:
- Frontend: React + TypeScript (Vite)
- Backend: NestJS REST API
- Event Processing: NestJS Microservice (Kafka consumer)
- Database: PostgreSQL
- Message Queue: Kafka
- AI Integration: OpenAI API for journal analysis
This is a Yarn workspaces monorepo with the following packages:
/
├── api/ # NestJS REST API
├── event-consumer/ # NestJS Kafka consumer for analytics
├── web/ # React frontend (Vite)
├── api-e2e/ # End-to-end API tests
├── libs/
│ ├── data-access/postgres/ # Shared database DAL
│ └── analysis/ # Shared analysis utilities
├── test-utils/ # Shared test utilities
└── common-types/ # Shared TypeScript types
- NestJS REST API with JWT authentication
- Google OAuth integration
- Publishes events to Kafka when journals are created
- Modules: auth, journals, analytics, events
- Kafka consumer listening to
journal.eventstopic - Processes
JOURNAL_CREATEDevents - Generates AI-powered psychological analysis using OpenAI
- Implements idempotency with in-memory event tracking
- React SPA with TypeScript
- Google OAuth login
- Journal CRUD operations
- AI-generated analysis viewing
- Shared PostgreSQL DAL used by API and event-consumer
- DAL pattern with separate files for each entity
- TypeScript interfaces for database models
-- Users (OAuth-based, no passwords)
user (userId PK)
-- Journal entries
journal (journalId PK, userId FK, title, body, createdAt)
-- AI-generated analysis
user_analysis (analysisId PK, userId FK, summary, createdAt)- User table only has
userIdfield (Google OAuth ID) - No email, password, or name fields in user table
- Cascade deletes configured (delete user → delete journals → delete analyses)
journal.events- Journal lifecycle events
{
eventId: string; // Unique event identifier
eventType: 'JOURNAL_CREATED' | 'JOURNAL_UPDATED' | 'JOURNAL_DELETED';
timestamp: string; // ISO 8601
version: string; // Event schema version
payload: {
userId: string;
journalId: string;
title?: string;
createdAt: string;
}
}- User creates journal via API
- API saves to database
- API publishes
JOURNAL_CREATEDevent to Kafka - Event consumer receives event
- Consumer fetches journal details
- Consumer calls OpenAI API for analysis
- Consumer saves analysis to database
Important: The consumer implements special error handling for "not found" errors:
// In events.controller.ts
if (error.message.includes('not found')) {
this.processedEvents.add(event.eventId);
this.logger.log(`Processed event but journal was not found, ignoring`);
return; // Don't retry - prevents infinite loop
}
throw error; // Retry for other errorsThis prevents infinite retry loops when a journal or user is deleted before the event is processed.
-
Unit Tests (Jest)
- API:
yarn test:api - Consumer:
yarn test:consumer - Location:
*.spec.tsfiles next to source code
- API:
-
E2E Tests (Jest + Supertest)
- API + Kafka integration:
yarn test:api:e2e - Location:
/api-e2e/src/api/*.spec.ts - Uses real Kafka consumer to verify event publishing
- API + Kafka integration:
-
Frontend Tests (Vitest + Testing Library)
- Web:
yarn test:web - Includes Cypress for full E2E:
yarn test:web(runs Cypress in CI mode)
- Web:
- Use NestJS Testing Module for dependency injection
- Mock external dependencies (database, OpenAI, Kafka)
- Use custom mock types to avoid
any:type MockedDatabaseService = { journalDAL: { getJournalById: jest.MockedFunction<any>; }; };
- Use real Kafka consumer to verify event publishing
- Generate JWT tokens dynamically using
generateTestToken() - Load secrets from API's
.envfiles via dotenvx - Wait for Kafka events:
await new Promise(resolve => setTimeout(resolve, 3000))
- IMPORTANT: User table only has
userIdcolumn - Correct insert:
INSERT INTO "user" ("userId") VALUES ($1) - Wrong:
INSERT INTO "user" ("userId", "email", "password") ...
E2E tests use JWT tokens without hardcoded secrets:
// In test-auth.ts
export function generateTestToken(userId: string): string {
const secret = process.env.ACCESS_TOKEN_SECRET;
return jwt.sign({ sub: userId, userId, email: `${userId}@test.com` }, secret);
}JWT strategy requires both userId AND email in payload.
yarn start:api # Start API server
yarn start:consumer # Start event consumer
yarn start:web # Start frontend dev serveryarn test:api # API unit tests
yarn test:consumer # Consumer unit tests
yarn test:api:e2e # E2E tests (API + Kafka)
yarn test:web # Web tests (Vitest + Cypress)yarn lint # Run ESLint
yarn lint:fix # Fix linting issues
yarn format # Format with Prettier
yarn format:check # Check formatting
yarn typecheck # TypeScript type checkingyarn db:exec # Execute PostgreSQL script
yarn migrate:oauth # Run OAuth migrationPOSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=journal
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
KAFKA_BROKERS=localhost:9093
ACCESS_TOKEN_SECRET=<secret>
JWT_SECRET=<secret>
GOOGLE_CLIENT_ID=<id>
GOOGLE_CLIENT_SECRET=<secret>
OPENAI_API_KEY=<key>
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_DB=journal
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
KAFKA_BROKERS=localhost:9093
OPENAI_API_KEY=<key>
- E2E tests load API's
.env.localand.envfiles - Uses dotenvx for environment variable loading
- Test setup in
/api-e2e/src/support/test-setup.ts
- Strict mode enabled
- No implicit any
- Interface for database models
- Type for DTOs and request/response objects
- Module-based architecture
- Dependency injection for all services
- DTOs with class-validator decorators
- Guards for authentication/authorization
- Services:
*.service.ts - Controllers:
*.controller.ts - Unit tests:
*.spec.ts - E2E tests:
*.spec.ts(in api-e2e/) - DTOs:
*.dto.ts
- Use absolute imports with workspace aliases:
@journal/data-access-postgres@journal/analysis
- Jest moduleNameMapper configured for test imports
/package.json- Root workspace config/api/src/main.ts- API entry point/event-consumer/src/main.ts- Consumer entry point/api/.env.local- Local environment variables (gitignored)
/api-e2e/src/support/test-auth.ts- JWT token generation for tests/api-e2e/src/support/test-setup.ts- Test environment setup/event-consumer/jest.config.js- Jest config with module mapping
/libs/data-access/postgres/src/journal.dal.ts- Journal database access/libs/data-access/postgres/src/user-analysis.dal.ts- Analysis database access
/api/src/events/event-producer.service.ts- Kafka event publishing/event-consumer/src/events/events.controller.ts- Kafka event consumption/event-consumer/src/analytics/analytics.service.ts- OpenAI integration
-
Tests fail with "column does not exist"
- Check you're using correct user table schema (only
userIdcolumn) - Don't insert email, password, or name fields
- Check you're using correct user table schema (only
-
E2E tests get 401 Unauthorized
- Verify JWT token includes both
userIdandemail - Check
ACCESS_TOKEN_SECRETis loaded from environment - Ensure test-setup.ts loads API's .env files
- Verify JWT token includes both
-
Kafka events not received in tests
- Verify tests use API endpoints, not direct database inserts
- Events only published when journals created via API
- Add wait time:
await new Promise(resolve => setTimeout(resolve, 3000))
-
Jest can't find module
- Check jest.config.js has correct moduleNameMapper
- Verify workspace package names match imports
-
Consumer retrying forever
- Check error handling for "not found" errors
- Should mark event as processed and return (no throw)
-
Database changes (if needed)
- Add migration script in
/api/scripts/ - Update DAL in
/libs/data-access/postgres/ - Update TypeScript interfaces
- Add migration script in
-
API changes
- Add/update DTOs
- Add/update controller endpoints
- Add/update service logic
- Add unit tests
- Add e2e tests
-
Event changes (if needed)
- Update event schema
- Update event producer
- Update event consumer
- Add consumer unit tests
- Add e2e tests for event flow
-
Frontend changes
- Add/update components
- Add/update API client calls
- Add tests
-
Validation
- Run all tests:
yarn test:api && yarn test:consumer && yarn test:api:e2e && yarn test:web - Run linting:
yarn lint - Run type checking:
yarn typecheck
- Run all tests:
- Branch naming:
feature/description,fix/description - Commit messages: Use conventional commits format
- Always include "Generated with Claude Code" footer in commits
Total: 200/200 tests passing
- Event-consumer unit tests: 27/27
- API unit tests: 53/53
- E2E tests: 12/12
- Cypress tests: 108/108
- JWT tokens for API authentication
- Google OAuth for user login
- No passwords stored (OAuth only)
- Secrets managed via environment variables
- Database queries use indexes on foreign keys
- Kafka ensures reliable event delivery
- OpenAI API calls are async and don't block request handling
- Kafka allows multiple consumer instances
- Consumer uses in-memory event tracking (not persistent across restarts)
- Could add Redis for persistent idempotency tracking if needed
When working on this codebase:
- Check this file first for context
- Review existing patterns before implementing new features
- Maintain test coverage for all new code
- Follow existing conventions for file structure and naming
- Update this file if you add significant new patterns or conventions