Table of Contents
- Project Overview
- Code Style and Formatting
- Architecture and Design Patterns
- Code Quality Standards
- Git and Version Control
- API and Interface Guidelines
- Database and Data Management
- Performance and Optimization
- AI Agent-Specific Instructions
- Common Pitfalls and Anti-patterns
- Auto-Detected Configuration Summary
Wallet is a personal finance management application built with a modern full-stack architecture. The application enables users to track expenses, manage budgets, set financial goals, and monitor income across multiple accounts.
Frontend (Client)
- Framework: React 18.2.0 with TypeScript 5.2.2
- Build Tool: Vite 5.4.21
- UI Library: Radix UI (@radix-ui/themes 2.0.3)
- State Management: TanStack Query 5.15.0 (React Query)
- GraphQL: gql.tada 1.8.10 with graphql-request 5.2.0
- Routing: React Router DOM 6.11.2
- Internationalization: i18next 23.8.0 with react-i18next 14.0.1
Backend (API)
- Runtime: Node.js 18
- Framework: Express 4.20.0
- GraphQL: Type-GraphQL 1.1.1 with graphql-http 1.22.0
- Database: SQLite (via Prisma)
- ORM: Prisma 5.7.1 with typegraphql-prisma 0.27.1
- Authentication: express-session 1.18.0 with bcrypt 5.1.1
- Security: helmet 7.1.0, cors 2.8.5
- Logging: Winston 3.11.0
Monorepo Management
- Build System: Nx 17.1.3
- Package Manager: pnpm 8
- Shared Libraries:
@wallet/common- Shared utilities, GraphQL client, i18n@wallet/ui- Reusable UI components@wallet/shared-gql- Shared GraphQL types
Prerequisites
- Node.js 18.14.2 or higher
- pnpm 8.x
- SQLite (for local development)
Environment Variables
DATABASE_URL- SQLite database connection string (default: file:./dev.db)HOST- API server host (default: localhost)PORT- API server port (default: 4000)NODE_ENV- Environment mode (development/production)
Setup Commands
# Install dependencies
pnpm install
# Generate Prisma client and TypeGraphQL types
pnpm run db:generate
# Run database migrations
pnpm run db:migrate
# Start development servers (client + API)
pnpm run dev
# Or start individually
nx serve client # Frontend on http://localhost:4200
nx serve api # Backend on http://localhost:4000The project uses Prettier 2.6.2 for code formatting with the following rules:
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"jsxSingleQuote": true,
"bracketSpacing": false,
"arrowParens": "avoid",
"printWidth": 80
}charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = trueFiles and Directories
- React components: PascalCase (e.g.,
Button.tsx,UserProfile.tsx) - Utilities/helpers: camelCase (e.g.,
logger.ts,client.ts) - Test files:
*.spec.tsxor*.spec.ts - Type definitions:
*.d.ts - Configuration files: kebab-case or standard names
Code Naming
-
Variables: camelCase
const userName = 'John' const isAuthenticated = true
-
Functions: camelCase
const getUserById = (id: number) => {...} async function registerUser() {...}
-
Classes: PascalCase
class UserService {...} class CustomCreateOneUserResolver {...}
-
Interfaces/Types: PascalCase with 'T' prefix for types
interface UserData {...} type TSignupFormData = {...}
-
Constants: UPPER_SNAKE_CASE or camelCase
const API_URL = 'http://localhost:4000' const levels = { error: 0, warn: 1 }
-
GraphQL Resolvers: PascalCase ending with 'Resolver'
class CustomCreateOneUserResolver {...}
-
React Hooks: camelCase starting with 'use'
const useUserQuery = () => {...} const useRegisterUserMutation = () => {...}
Do's ✅
// Use explicit types for function parameters
async function registerUser(data: UserRegisterInput): Promise<User> {...}
// Use type imports when importing only types
import type {ButtonProps} from '@radix-ui/themes'
// Use path aliases defined in tsconfig
import {graphql} from '@wallet/common'
// Use decorators for Type-GraphQL
@Resolver()
class CustomResolver {...}Don'ts ❌
// Don't use 'any' - use proper typing
const data: any = {...} // ❌
// Don't use default exports for named entities (prefer named exports)
// Exception: React components can use default exports// 1. External dependencies
import express from 'express'
import {createHandler} from 'graphql-http/lib/use/express'
// 2. Internal workspace packages
import {graphql, client} from '@wallet/common'
// 3. Relative imports
import Logger from './libs/logger'
import {sessionRequestHandler} from './middlewares/session'
// 4. Type imports (if separate)
import type {Context} from './context'wallet/
├── apps/
│ ├── api/ # Backend GraphQL API
│ │ ├── src/
│ │ │ ├── graphql/ # GraphQL schema, resolvers, context
│ │ │ ├── middlewares/ # Express middlewares
│ │ │ ├── libs/ # Utility libraries (logger, error)
│ │ │ ├── data/ # Configuration and constants
│ │ │ ├── db.ts # Prisma client singleton
│ │ │ └── main.ts # Application entry point
│ │ └── project.json
│ └── client/ # Frontend React app
│ ├── src/
│ │ ├── app/ # Main app component
│ │ ├── components/ # Page-specific components
│ │ ├── graphql/ # GraphQL queries/mutations
│ │ ├── routes/ # Route configurations
│ │ ├── types/ # TypeScript types
│ │ └── main.tsx # Application entry point
│ └── project.json
├── libs/
│ ├── common/ # Shared utilities
│ │ ├── config/ # Environment config
│ │ ├── hooks/ # React hooks
│ │ ├── libs/ # GraphQL client, i18n
│ │ └── styles/ # Global CSS
│ └── ui/ # Shared UI components
│ └── components/ # Reusable React components
├── prisma/
│ ├── migrations/ # Database migrations
│ └── schema.prisma # Database schema
└── schema.gql # Generated GraphQL schema
Backend Patterns
- Singleton Pattern (Prisma Client)
// db.ts - Single instance of Prisma client
import {PrismaClient} from '@prisma/client'
const prisma = new PrismaClient()
export {prisma}- Middleware Pattern (Express)
// Chainable middleware for cross-cutting concerns
app.use(cors({...}))
app.use(helmet())
app.use(sessionRequestHandler)
app.use(loggerRequestHandler)- Resolver Pattern (Type-GraphQL)
@Resolver()
class CustomCreateOneUserResolver {
@Mutation(() => User)
async registerUserSession(@Arg("data") data: UserRegisterInput) {...}
}- Dependency Injection (GraphQL Context)
// Context provides dependencies to all resolvers
createHandler({
schema,
context: req => ({
session: req.raw.session,
prisma
})
})Frontend Patterns
- Custom Hooks Pattern (Data Fetching)
const useUserQuery = (variables: {id: number}) => {
const UserQuery = graphql(`query GetUser($where: UserWhereUniqueInput!) {...}`)
return useQuery({
queryKey: ['user', UserQuery, variables],
queryFn: async () => await client(UserQuery, [variables])
})
}- Component Composition (Radix UI Wrapper)
// Wrap and extend third-party components
export default (props: ButtonProps) => {
return <Button style={{textTransform: 'uppercase'}} {...props} />
}- Provider Pattern (Context Providers)
// main.tsx - Nest providers for global state
<QueryClientProvider client={client}>
<Theme>
<App />
</Theme>
</QueryClientProvider>Dependency Rules (enforced by Nx)
- Apps can depend on libs
- Libs can depend on other libs
- Circular dependencies are forbidden
- Shared code goes in libs, app-specific code stays in apps
Library Purposes
@wallet/common- Cross-cutting concerns (GraphQL client, i18n, env config)@wallet/ui- Presentational components (no business logic)@wallet/shared-gql- GraphQL type definitions shared between client and server
Frontend Testing
- Framework: Vitest 0.34.6
- Testing Library: @testing-library/react 14.0.0
- Coverage: @vitest/coverage-v8 0.34.6
Test Structure
import {render} from '@testing-library/react'
import {Accordion} from '.'
describe('Accordion', () => {
it('should render successfully', () => {
const {baseElement} = render(<Accordion />)
expect(baseElement).toBeTruthy()
})
})Backend Testing
- Framework: Jest 29.4.1
- Environment: jest-environment-node 29.4.1
When to Write Tests
- All new UI components in
libs/ui - Custom hooks for data fetching
- Utility functions with complex logic
- Critical business logic resolvers
Test Naming Convention
describe('ComponentName or FunctionName', () => {
it('should describe expected behavior', () => {...})
})CI Configuration (from nx.json)
{
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
}
}Run tests with coverage:
nx affected -t test --configuration=ciESLint Configuration
- Version: 8.46.0
- Plugins:
- @nx/eslint-plugin
- @typescript-eslint/eslint-plugin 6.9.1
- @tanstack/eslint-plugin-query 5.14.6
- eslint-plugin-react 7.32.2
- eslint-plugin-react-hooks 4.6.0
- eslint-plugin-jsx-a11y 6.7.1
- eslint-config-prettier 9.0.0
Running Linters
# Lint all projects
pnpm run lint
# Lint only affected projects
pnpm run lint:pr
nx affected -t lintNx Module Boundary Enforcement
{
"rules": {
"@nx/enforce-module-boundaries": ["error", {
"enforceBuildableLibDependency": true
}]
}
}Backend Error Patterns
// Use Winston logger for errors
Logger.error("Error message")
// Throw appropriate GraphQL errors
throw new UnauthorizedError()
// Centralized error handler
process.on("uncaughtException", (error: Error) => {
errorHandler.handleError(error)
})Frontend Error Patterns
// Handle errors in mutations/queries
const {mutate, error} = useMutation({...})
if (error) {
// Display user-friendly error message
}Backend Logging Levels
Logger.error() // Critical errors requiring immediate attention
Logger.warn() // Warning conditions
Logger.info() // Informational messages
Logger.http() // HTTP request logging
Logger.debug() // Detailed debug informationEnvironment-Based Logging
- Development: Show all levels (debug and above)
- Production: Show warnings and errors only
Log Output
- Console: All log levels (colored)
logs/error.log: Error level onlylogs/all.log: All levels
Primary Branches
main- Production-ready codedevelopment- Integration branch for features
Feature Branches
- Format:
feature/descriptionorcopilot/description - Example:
feature/add-expense-tracking
Convention: Conventional Commits (@commitlint/config-conventional)
Format
<type>(<scope>): <subject>
<body>
<footer>
Types
feat: New featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, semicolons, etc.)refactor: Code refactoringtest: Adding or updating testschore: Build process or auxiliary tool changesci: CI/CD configuration changes
Examples
feat(auth): add user registration endpoint
fix(budget): correct calculation for monthly budget
docs(readme): update installation instructions
ci: add permissions for GitHub Actions workflowCI Checks (from .github/workflows/ci.yml)
- Install dependencies with pnpm
- Generate Prisma client (
pnpm run db:generate) - Format code (
nx format:write) - Run affected linting (
nx affected -t lint) - Run affected tests (
nx affected -t test) - Run affected builds (
nx affected -t build)
PR Process
- Target branch:
mainordevelopment - Ensure all CI checks pass
- Code must be formatted with Prettier
- All affected tests must pass
- No linting errors
Husky Configuration
- Location:
.husky/ - Commit message validation with commitlint
- Automatic code formatting (prepare script)
Schema Location
- Defined using Type-GraphQL decorators in resolvers
- Generated schema:
schema.gql(auto-generated, don't edit manually)
Resolver Structure
@Resolver()
class CustomResolverName {
@Query(() => ReturnType, {nullable: false})
async queryName(
@Arg("argName") argName: ArgType,
@Ctx() {prisma, session}: Context
): Promise<ReturnType> {
// Implementation
}
@Mutation(() => ReturnType, {nullable: false})
async mutationName(
@Arg("data") data: InputType,
@Ctx() {prisma, session}: Context
): Promise<ReturnType> {
// Implementation
}
}Input Types
@InputType({
description: "Description of input model"
})
class UserRegisterInput {
@Field(() => String, {
nullable: false,
description: "Field description"
})
email!: string
}Authentication Pattern
// Use custom auth checker with Type-GraphQL
const customAuthChecker: AuthChecker<Context> = async ({context}) => {
return context.session.user != null
}
// Apply to protected resolvers
@Authorized()
@Query(() => User)
async me(@Ctx() {session, prisma}: Context) {...}Type-Safe Queries with gql.tada
const UserQuery = graphql(`
query GetUser($where: UserWhereUniqueInput!) {
user(where: $where) {
id
email
name
accounts {
id
name
}
}
}
`)React Query Integration
const useUserQuery = (variables: {id: number}) => {
return useQuery({
queryKey: ['user', UserQuery, variables],
queryFn: async () => await client(UserQuery, [variables])
})
}Base URL
- Development:
http://localhost:4000/graphql - Production: TBD
CORS Configuration
cors({
origin: 'http://localhost:4200',
credentials: true
})Schema Location: prisma/schema.prisma
Generators
generator client {
provider = "prisma-client-js"
}
generator typegraphql {
provider = "typegraphql-prisma"
simpleResolvers = true
}Database Provider: SQLite (development)
Model Naming
- Singular, PascalCase (e.g.,
User,Account,Expense)
Field Naming
- camelCase (e.g.,
createdAt,userId,creditDate)
Standard Fields
model Example {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
}Relationships
model User {
id Int @id @default(autoincrement())
accounts Account[] // One-to-many
password Password? // One-to-one (optional)
}
model Account {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id])
}Create Migration
pnpm run db:migrate
# This runs: prisma migrate devReset Database (development only)
pnpm run db:reset
# This runs: prisma db push --force-resetGenerate Client
pnpm run db:generate
# This runs: prisma generateMigration Best Practices
- Always create named migrations with descriptive names
- Test migrations locally before committing
- Never modify existing migrations
- Use
prisma db pushfor prototyping,prisma migratefor production
Prisma-Level Validation
model User {
email String @unique // Enforces uniqueness
name String // Required by default
}Application-Level Validation
- Use
class-validatordecorators in GraphQL input types - Use
zodfor runtime validation where needed
Using Prisma Client
// Simple query
const user = await prisma.user.findUnique({where: {id}})
// With relations
const user = await prisma.user.findUnique({
where: {id},
include: {accounts: true, password: true}
})
// Create with nested relations
const user = await prisma.user.create({
data: {
name,
email,
password: {create: {hash}}
}
})Nx Build Cache
- Enabled for build, lint, test, and e2e targets
- Configuration in
nx.jsonundertargetDefaults
Frontend Query Caching
- TanStack Query handles automatic caching
- Query keys:
['entityName', query, variables]
Build Optimization
{
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
}
}
}N+1 Query Prevention
- Use Prisma's
includeorselectfor eager loading - Use
graphql-fieldsto request only needed fields
// Good - Single query with relations
const users = await prisma.user.findMany({
include: {accounts: true}
})
// Avoid - N+1 queries
const users = await prisma.user.findMany()
for (const user of users) {
const accounts = await prisma.account.findMany({where: {userId: user.id}})
}Nx Parallel Execution
nx affected -t lint,test,build --parallel=3Development Server Ports
- Client: 4200
- API: 4000
Logging Infrastructure
- Winston logger with file output
- HTTP request logging with Morgan
- Session debugging in development
Error Tracking
- Centralized error handler in API
- Uncaught exception and unhandled rejection handlers
When Requirements Are Unclear
- Review existing similar implementations in the codebase
- Check
docs/domains.mdfor domain-specific context - Look at Prisma schema for data model relationships
- Examine existing GraphQL resolvers for patterns
- If still unclear, ask for clarification rather than guessing
Making Reasonable Assumptions
- Follow established patterns in similar features
- Maintain consistency with existing code style
- Prefer TypeScript-first solutions
- Use existing libraries before adding new dependencies
When Encountering Legacy Code
- Don't refactor unless directly related to your task
- Maintain backward compatibility
- Add type safety incrementally
- Document any workarounds with comments
For New Implementations
- Use latest TypeScript features (5.2.2)
- Prefer functional components over class components
- Use Type-GraphQL decorators for schema
- Implement proper error handling from the start
Prioritization
- New features take priority unless refactoring blocks implementation
- Small refactorings that improve immediate code are acceptable
- Large refactorings require separate tasks
- Always maintain test coverage during refactoring
Safe Refactoring Practices
- Run tests before and after refactoring
- Use TypeScript's type system to catch breaking changes
- Leverage Nx affected commands to test impact
- Keep refactoring commits separate from feature commits
Checklist Before Marking Work Complete
- Code follows project style guide (Prettier + ESLint)
- TypeScript types are properly defined (no
any) - Tests are written for new functionality
- All tests pass (
nx affected -t test) - Linting passes (
nx affected -t lint) - Build succeeds (
nx affected -t build) - Prisma schema is generated if models changed
- GraphQL schema is valid (server starts)
- Manual testing performed for UI changes
- No console errors in browser/server
- Changes are committed with proper commit message format
❌ Don't: Forget to generate Prisma client after schema changes
# After editing prisma/schema.prisma, always run:
pnpm run db:generate❌ Don't: Use raw SQL queries
// Avoid
await prisma.$executeRaw`SELECT * FROM User`
// Prefer
await prisma.user.findMany()❌ Don't: Expose password hashes in GraphQL
// Use separate Password model, not on User
model User {
id Int @id
password Password? // Relation, not direct hash
}❌ Don't: Return sensitive data in queries
// Bad
@Query(() => User)
async user(@Arg("id") id: number) {
return prisma.user.findUnique({
where: {id},
include: {password: true} // ❌ Exposes password hash
})
}
// Good
@Query(() => User)
async user(@Arg("id") id: number) {
return prisma.user.findUnique({where: {id}}) // ✅ No password
}❌ Don't: Skip input validation
// Add validation to input types
@InputType()
class UserRegisterInput {
@Field(() => String)
@IsEmail() // Use class-validator decorators
email!: string
}❌ Don't: Make direct fetch calls
// Bad
const response = await fetch('/graphql', {...})
// Good - Use the GraphQL client
const data = await client(UserQuery, variables)❌ Don't: Forget to handle loading and error states
// Complete pattern
const {data, isLoading, error} = useUserQuery({id})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <div>{data.user.name}</div>❌ Don't: Forget to save session after modification
// Always save session after changes
session.user = user.id
session.save((err) => {
if (err) throw new UnauthorizedError()
})❌ Don't: Use implicit any
// Bad - Parameters need types
function process(data) {...} // ❌
// Good
function process(data: UserData) {...} // ✅❌ Don't: Ignore TypeScript errors
// Bad
// @ts-ignore
const result = riskyOperation() // ❌
// Good - Fix the root cause or use proper type assertion
const result = riskyOperation() as ExpectedType // ✅❌ Don't: Install dependencies with npm or yarn
# Wrong
npm install package-name # ❌
yarn add package-name # ❌
# Correct
pnpm add package-name # ✅❌ Don't: Import from dist or node_modules directly
// Bad
import {Button} from '../../dist/libs/ui' // ❌
// Good - Use path aliases
import {Button} from '@wallet/ui' // ✅- Session Storage - Currently using in-memory sessions (not production-ready)
- Database - SQLite is for development; production needs PostgreSQL/MySQL
- Error Messages - Some error messages are too technical for end users
- Type Safety - Some generated GraphQL types need manual refinement
- Test Coverage - Frontend tests are minimal, need expansion
| Category | Details |
|---|---|
| Languages | TypeScript 5.2.2, JavaScript (ES2022) |
| Frontend Framework | React 18.2.0 |
| Backend Framework | Express 4.20.0, Type-GraphQL 1.1.1 |
| Build System | Nx 17.1.3 |
| Build Tools | Vite 5.4.21 (frontend), esbuild 0.19.12 (backend) |
| Package Manager | pnpm 8.x |
| Testing Frameworks | Vitest 0.34.6 (frontend), Jest 29.4.1 (backend) |
| E2E Testing | Playwright 1.36.0, Cypress 13.0.0 |
| Linting | ESLint 8.46.0 with TypeScript, React, TanStack Query plugins |
| Formatting | Prettier 2.6.2 |
| Database | SQLite (via Prisma 5.7.1) |
| ORM | Prisma Client + TypeGraphQL-Prisma |
| GraphQL Client | gql.tada 1.8.10, graphql-request 5.2.0 |
| State Management | TanStack Query 5.15.0 |
| UI Library | Radix UI 2.0.3 |
| Logging | Winston 3.11.0 |
| Security | Helmet 7.1.0, bcrypt 5.1.1, CORS 2.8.5 |
| Session Management | express-session 1.18.0 |
| Internationalization | i18next 23.8.0, react-i18next 14.0.1 |
| CI/CD | GitHub Actions (Node 18, pnpm cache) |
| Git Hooks | Husky 8.0.0 |
| Commit Linting | @commitlint/config-conventional 18.4.3 |
| Node Version | 18.14.2+ |
# Development
pnpm run dev # Start all apps
nx serve client # Start frontend only
nx serve api # Start backend only
# Building
pnpm run build # Build all apps
nx build client # Build frontend
nx build api # Build backend
# Testing
pnpm run test # Test all
pnpm run test:pr # Test affected only
nx test client # Test client
# Linting
pnpm run lint # Lint all
pnpm run lint:pr # Lint affected only
nx lint api # Lint API
# Database
pnpm run db:generate # Generate Prisma client
pnpm run db:migrate # Run migrations
pnpm run db:reset # Reset database (dev only)
pnpm run db:studio # Open Prisma Studio
# Nx Utilities
nx graph # View project graph
nx affected:graph # View affected projects
nx list # List available plugins
nx format:write # Format all files- Nx Documentation: https://nx.dev
- Prisma Documentation: https://www.prisma.io/docs
- Type-GraphQL Documentation: https://typegraphql.com
- TanStack Query Documentation: https://tanstack.com/query
- Radix UI Documentation: https://www.radix-ui.com/themes/docs
- React Router Documentation: https://reactrouter.com
Last Updated: Auto-generated from project analysis Project Version: 0.0.0 Nx Version: 17.1.3