iamcxa nx-unified-app .cursorrules file for TypeScript

You are an expert in AWS, TypeScript, Nx, Lambda, Serverless and Terraform, focusing on modern fullstack development practices.

Key Principles:
- Write concise, readable TypeScript code with accurate examples
- Use functional programming patterns; avoid OOP when possible
- Follow SOLID principles and DRY (Don't Repeat Yourself)
- Prefer pure functions and immutability
- Use ESM/ES6 format, avoid CommonJS
- Ensure working code after each modification

Naming Conventions:
- Use PascalCase for types, interfaces (without 'I' prefix), and classes
- Use camelCase for variables, functions, and methods
- Use UPPERCASE for constants and environment variables
- Use kebab-case for file names (e.g., security.middleware.ts)
- Folder name should be included in file name (e.g., middlewares/security.middleware.ts)

TypeScript Usage:
- Always use strict type checking (strict: true)
- Avoid 'any' type; use 'unknown' when type is uncertain
- Use type inference when types are obvious
- Prefer type aliases over interfaces for better type inference
- Utilize TypeScript's utility types (e.g., Partial<Type>, Pick<Type, Keys>)
- Use const assertions for literal values (as const)
- Use branded types for type-safety (e.g., UserId, OrderId)
- Implement proper error handling with Result/Either pattern

Schema Validation:
- Use Zod for runtime type validation and complex business logic
- Define reusable schemas in separate files
- Use schema transformation for data normalization
- Common Zod usage:
  * Environment variables validation
  * API request/response validation
  * Domain model definitions
  * Complex business rules
  * Simple data structure validation
- Type Guard Best Practices with Zod:
  * Define schemas at module level for reusability
  * Use z.object() for structured data validation
  * Use z.record() for dictionary-like objects
  * Use z.unknown() instead of any for better type safety
  * Use passthrough() for allowing additional properties
  * Use safeParse() for type guards:
    ```typescript
    const eventSchema = z.object({
      headers: z.record(z.string()).optional(),
      body: z.unknown().optional(),
    });

    const isValidEvent = (event: unknown): event is Event => {
      return eventSchema.safeParse(event).success;
    };
    ```
  * Use error mapping for better error messages:
    ```typescript
    const result = schema.safeParse(data);
    if (!result.success) {
      const errors = result.error.errors.map(err => ({
        path: err.path.join('.'),
        message: err.message,
      }));
      logger.error('Validation failed', { errors });
    }
    ```
  * Combine with TypeScript type inference:
    ```typescript
    const schema = z.object({/*...*/});
    type SchemaType = z.infer<typeof schema>;
    ```
  * Use refinements for complex validations:
    ```typescript
    const schema = z.string().refine(
      (val) => isValidFormat(val),
      { message: 'Invalid format' }
    );
    ```

Code Structure:
- Each folder must have index.ts as barrel file
- One primary export per file
- Types, interfaces, and enums can have multiple exports
- Organize imports: @scope-libs, then third-party, then local, and there's a blank line between the last third-party or @scope-libs and the first local import, and sort alphabetically
- Group related functionality by feature/domain
- Place shared types in types/ directory
- Place shared utilities in utils/ directory
- Place shared constants in constants/ directory
- Keep shared code minimal and focused
- Follow Backstage.io catalog structure:
  * Define component catalog-info.yaml for each service
  * Document service dependencies and relationships
  * Maintain up-to-date API specifications
  * Group services by domains and systems

Syntax and Formatting:
- Use semicolons
- Use single quotes for strings
- Use template literals for string interpolation
- Max line length: 100 characters
- Use arrow functions by default
- Always use explicit return types for public functions

Error Handling:
- Use custom error classes extending Error
- Implement proper error boundaries
- Log errors with appropriate context
- Use try-catch with specific error types

Middleware Pattern:
- Chain middlewares using composition
- Each middleware should have single responsibility
- Use dependency injection via middleware context
- Implement proper error boundaries in middleware chain

Middy Pattern:
- Use @middy/core for middleware implementation
- Prefer official middy middlewares when available
- Custom middleware structure:
  * Keep middleware pure and focused
  * Implement proper before/after/onError handlers
  * Use proper TypeScript types for handler context
- Common middleware stack:
  * httpHeaderNormalizer
  * httpJsonBodyParser
  * validator
  * errorHandler
- Best practices:
  * Chain middlewares in logical order
  * Handle errors in each middleware layer
  * Use middleware composition for reusability
  * Implement proper types for middleware context
  * Document middleware dependencies and side effects

Lambda Specific:
- Use middy for middleware pattern implementation
- Common cross-cutting concerns should be implemented as middy middlewares:
  * Authentication/Authorization
  * Validation (using appropriate validator)
  * Logging
  * Error handling
  * Request/Response transformation
- Implement proper input validation
- Handle API Gateway events properly (v1 and v2)
- Follow serverless best practices
- Implement proper timeouts and retries
- Use proper AWS SDK clients with configuration

Testing:
- Write unit tests for business logic
- Use integration tests for Lambda functions
- Use Jest for testing
- Follow AAA pattern (Arrange-Act-Assert)
- Use factory functions for test data
- Implement proper mocks for AWS services
- Use snapshot testing for complex objects
- Test error cases explicitly
- Test schema validations thoroughly

Comments and Documentation:
- Use JSDoc for public functions and types
- Write README.md for each library
- Document breaking changes
- Comments should explain "why" not "what"
- Document schema validations and transformations
- Follow Backstage TechDocs standards:
  * Maintain mkdocs.yaml in each service
  * Document API specifications using OpenAPI
  * Keep architecture decision records (ADRs)
  * Include getting started guides
  * Document service dependencies
- Documentation structure:
  * Overview and purpose
  * Architecture diagrams
  * API documentation
  * Development setup
  * Deployment process
  * Troubleshooting guides

Service Template Standards:
- Follow Backstage Software Templates:
- Standard service structure:
  * /src
    - /handlers
    - /middlewares
    - /schemas
    - /utils
  * /tests
  * catalog-info.yaml
  * mkdocs.yaml
  * README.md
- Include required metadata:
  * Service owner
  * Team contact
  * Dependencies
  * API endpoints
- Template components:
  * Standard middleware stack
  * Basic test setup
  * CI/CD configuration
  * Documentation structure

Infrastructure as Code:
- Document infrastructure in Backstage:
  * AWS resources and permissions
  * Environment configurations
  * Network diagrams
  * Security policies

Code Review Guidelines:
- Ensure changes don't break existing functionality
- Document potential impacts on other system components
- Pause after each significant change for review
- Review checkpoints:
  * Code compiles without errors
  * All tests pass
  * No regression in existing features
  * Documentation is updated
  * Impact analysis is complete
- Consider:
  * Performance implications
  * Security considerations
  * Scalability impacts
  * Maintenance overhead

Remember to:
- Follow project's ESLint rules
- Run nx affected before commits
- Update documentation when needed
- Update Backstage catalog when adding new services
- Maintain TechDocs documentation
- Follow template standards for new services

Test desc and Code Comment and Readme should always in English.
Always check Eslint rules but ignore the rule "simple-import-sort".

# AWS Lambda Event Types Best Practices

## Event Type Design
- Extend native AWS Lambda event types whenever possible
- Use TypeScript's utility types (Pick, Omit) to refine AWS types rather than creating new ones
- Follow AWS's type patterns for consistency and maintainability

## Type Inheritance Pattern
```typescript
// ❌ Bad: Creating custom types from scratch
interface CustomEvent {
  headers: Record<string, string>;
  body: unknown;
  authorizer?: Record<string, unknown>;
}

// ✅ Good: Extending AWS native types
type CustomEvent<T> = Omit<APIGatewayProxyEventV2, 'body'> & {
  requestContext: {
    authorizer?: {
      jwt?: AuthorizerResult<T>;
      lambda?: AuthorizerResult<T>;
    };
  };
}
```

## Benefits
- Better type safety through AWS's well-defined types
- Easier integration with AWS services
- Consistent with AWS SDK updates
- Reduced maintenance overhead
- Better IDE support and documentation

## Implementation Guidelines
1. Start with the most specific AWS event type that matches your use case
2. Extend or modify only the necessary parts
3. Maintain AWS's type structure for common fields
4. Add custom fields only when absolutely necessary
5. Document any deviations from AWS's type patterns

## Common AWS Event Types to Extend
- APIGatewayProxyEventV2
- APIGatewayProxyEvent
- ALBEvent
- DynamoDBStreamEvent
- SQSEvent
- SNSEvent

# Lambda Controller Pattern Guidelines

## Controller Structure
- Use createMiddyController factory for all Lambda handlers
- Implement proper type parameters <TAuth, TBody, TResponse>
- Follow API configuration pattern with OpenAPI definitions
- Use standardized response handling through HttpResponses
- Implement proper error boundaries

## Response Standards
- Success responses:
  * Ok: 200
  * Created: 201
- Client errors:
  * BadRequest: 400
  * Unauthorized: 401
  * Forbidden: 403
  * NotFound: 404
- Server errors:
  * InternalServerError: 500

## Controller Configuration
```typescript
{
  request: { 
    body: zodSchema 
  },
  responses: { 
    200: { schema: successResponseSchema },
    400: { schema: errorResponseSchema }
  },
  security: [{ 
    [middlewareName]: ['scopes'] 
  }]
}
```

## Controller Best Practices
- One controller per business operation
- Pure function approach for handlers
- Proper type inference from schemas
- Standardized error handling
- Clear separation of concerns

## Middleware Chain
- Standard middleware order:
  1. httpHeaderNormalizer
  2. httpJsonBodyParser
  3. validator
  4. security
  5. logging
  6. errorHandler
- Custom middleware must follow single responsibility
- Implement proper before/after/onError handlers
- Use middleware composition for reusability

## Schema Validation
- Define request/response schemas using Zod
- Implement proper type inference
- Validate at runtime
- Transform data when necessary

## Error Handling Pattern
```typescript
try {
  // Business logic
  return new HttpResponses.Ok('Success').send(data);
} catch (error) {
  if (error instanceof ValidationError) {
    return new HttpResponses.BadRequest('Invalid input')
      .send(error.details);
  }
  return new HttpResponses.InternalServerError('Server error')
    .send();
}
```

## Controller File Structure
```
/controllers
  ├── domain-name/
  │   ├── create.controller.ts
  │   ├── update.controller.ts
  │   ├── delete.controller.ts
  │   └── index.ts
  └── index.ts
```

## Lambda Service Pattern
- Follow Single Responsibility Principle (SRP)
- Service layer should:
  * Handle complete business logic
  * Process data transformation
  * Format response
  * Example:
  ```typescript
  export class UserService {
    static async createUser(event: MiddyEvent<CreateUserRequest>): Promise<APIGatewayProxyResult> {
      const { name, email } = event.body;
      const user = await UserRepository.create({ name, email });
      return new HttpResponses.Created('User created').send(user);
    }
  }
  ```

- Controller layer should:
  * Only handle routing and request processing
  * Delegate business logic to service layer
  * Example:
  ```typescript
  export const userController = createControllerBuilder<CreateUserRequest>()
    .setHandler(async (event) => {
      return UserService.createUser(event);
    })
    .build();
  ```

- Benefits:
  * Clear separation of concerns
  * Easier to test
  * Better maintainability
  * Centralized error handling
  * Consistent response format

## Performance Guidelines
- Minimize middleware overhead
- Implement proper caching strategies
- Clean up resources after use
- Optimize cold starts
- Use connection pooling where applicable

## Security Considerations
- Always implement proper authentication
- Validate all inputs
- Use security middleware
- Implement proper scopes
- Follow least privilege principle

## Testing Requirements
- Unit test business logic
- Test middleware chain
- Test error scenarios
- Mock external dependencies
- Test schema validations

Remember to:
- Follow controller pattern consistently
- Use proper type safety
- Implement standardized error handling
- Document API configurations
- Test thoroughly

# Lambda + DynamoDB + Serverless Best Practices

## DynamoDB Table Design
- Use single-table design pattern
- Define GSI and LSI carefully for access patterns
- Use composite keys for efficient queries
- Implement proper TTL strategy
- Use on-demand capacity by default

## Data Access Pattern
```typescript
// Define type-safe key structure
type TableKey = {
  pk: `USER#${string}`;
  sk: `PROFILE#${string}`;
};

// Use branded types for keys
type UserId = Brand<string, 'UserId'>;
type ProfileId = Brand<string, 'ProfileId'>;

// Define schema with Zod
const UserSchema = z.object({
  pk: z.string().brand<'UserId'>(),
  sk: z.string().brand<'ProfileId'>(),
  // ... other fields
});
```

## Lambda Configuration
```yaml
functions:
  userApi:
    handler: src/handlers/user.handler
    memorySize: 1024
    timeout: 29
    reservedConcurrency: 10
    events:
      - http:
          path: /users/{id}
          method: get
    iamRoleStatements:
      - Effect: Allow
        Action:
          - dynamodb:GetItem
          - dynamodb:Query
        Resource: !GetAtt UserTable.Arn
```

## Performance Optimization
- Use DAX for caching when needed
- Implement batch operations
- Use parallel processing for large datasets
- Enable auto-scaling appropriately
- Optimize cold starts:
  * Use provisioned concurrency for critical paths
  * Keep dependencies minimal
  * Use layer for common dependencies

## Error Handling
- Implement retries for throttling
- Handle conditional checks properly
- Use transactions when atomic operations needed
- Implement proper backoff strategy
- Log DynamoDB consumed capacity

## Security Best Practices
- Use least privilege IAM roles
- Implement proper encryption
- Use VPC endpoints when needed
- Enable point-in-time recovery
- Implement proper backup strategy

## Monitoring Essentials
- Track DynamoDB metrics:
  * ConsumedCapacity
  * ThrottledRequests
  * SystemErrors
  * UserErrors
- Set up alerts for:
  * High throttling rates
  * Capacity consumption spikes
  * Error rate thresholds
  * Latency thresholds

Remember to:
- Follow single-table design principles
- Optimize for access patterns
- Implement proper error handling
- Monitor performance metrics
- Use proper IAM permissions
- Keep security best practices

# Testing Best Practices

## Test Framework Setup (Jest/Vitest)
```typescript
// jest.config.ts or vitest.config.ts
export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  setupFiles: ['<rootDir>/test/setup.ts'],
  collectCoverageFrom: ['src/**/*.ts'],
  coverageThreshold: {
    global: {
      statements: 80,
      branches: 80,
      functions: 80,
      lines: 80,
    },
  },
};
```

## Test File Structure
```
/src
  /feature
    feature.ts
    feature.test.ts
/test
  /fixtures
  /helpers
  setup.ts
```

## Test Patterns
```typescript
describe('UserService', () => {
  // Arrange - Setup
  const mockDb = createMockDb();
  const service = new UserService(mockDb);

  beforeEach(() => {
    mockDb.clear();
  });

  // Group related tests
  describe('createUser', () => {
    it('should create user with valid input', async () => {
      // Arrange
      const input = createUserInput();

      // Act
      const result = await service.createUser(input);

      // Assert
      expect(result).toMatchObject({
        id: expect.any(String),
        ...input,
      });
    });

    it('should throw on duplicate email', async () => {
      // Arrange
      const input = createUserInput();
      await service.createUser(input);

      // Act & Assert
      await expect(service.createUser(input))
        .rejects
        .toThrow('Email already exists');
    });
  });
});
```

## Best Practices
- Use factory functions for test data
- Mock external dependencies
- Test error cases explicitly
- Use snapshot testing sparingly
- Follow AAA pattern (Arrange-Act-Assert)
- Keep tests focused and isolated
- Use meaningful test descriptions
- Avoid test interdependence

## Mocking Guidelines
```typescript
// ✅ Good: Use factory functions
const createMockDb = () => ({
  query: vi.fn(),
  transaction: vi.fn(),
});

// ✅ Good: Type-safe mocks
const mockAwsService = vi.mocked(AwsService);

// ❌ Bad: Magic values
const user = { id: '123', name: 'test' };
```

## Integration Tests
- Test complete features
- Use test containers when needed
- Clean up resources after tests
- Mock external services
- Use proper test environment

## Coverage Requirements
- Maintain minimum 80% coverage
- Cover all business logic paths
- Test error handling
- Test edge cases
- Test input validation

Remember to:
- Write tests before fixing bugs
- Keep tests maintainable
- Use proper assertions
- Document test requirements
- Run tests before commits

# Auto-configured Middleware Pattern Best Practices

## Design Principles
- Auto-read configuration from controller config
- Support OpenAPI-style configuration
- Keep middleware pure and stateless
- Follow single responsibility principle
- Implement proper error handling

## Configuration Pattern
```typescript
// ✅ Good: Declarative configuration
export const userController = createController({
  config: {
    method: HttpMethod.POST,
    path: '/users',
    request: {
      body: userRequestSchema,
    },
    responses: {
      201: {
        description: 'User created',
        schema: userResponseSchema
      }
    },
    security: [{ jwt: ['user'] }]
  },
  handler: async (event) => {
    return UserService.createUser(event);
  }
});

// ❌ Bad: Imperative configuration
export const userController = createController()
  .setMethod(HttpMethod.POST)
  .setPath('/users')
  .setRequestSchema(userRequestSchema)
  .setResponseSchema(userResponseSchema)
  .setSecurity('jwt', ['user'])
  .setHandler(handler);
```

## Validator Middleware
- Use Zod for schema validation
- Support i18n error messages
- Validate both request and response
- Provide clear error messages
- Example:
```typescript
export const createValidatorMiddleware = () => ({
  before: async (request) => {
    const { request: reqConfig } = request.event.internal;
    if (reqConfig?.body?.schema) {
      request.event.body = await reqConfig.body.schema
        .parseAsync(request.event.body);
    }
  },
  after: async (request) => {
    const { responses } = request.event.internal;
    const schema = responses?.[request.response.statusCode]?.schema;
    if (schema) {
      await schema.parseAsync(JSON.parse(request.response.body));
    }
  }
});
```

## Auth Middleware
- Support multiple auth strategies
- Use strategy registration mechanism
- Support flexible combinations (OR/AND)
- Follow fail-fast principle
- Example:
```typescript
export const createAuthMiddleware = () => ({
  before: async (request) => {
    const { security } = request.event.internal;
    for (const requirement of security) {
      const [strategyName, scopes] = Object.entries(requirement)[0];
      const strategy = getAuthStrategy(strategyName);
      const result = await strategy.verify(request.event);
      if (result.authorized) {
        setEventAuthorizer(request.event, strategyName, result);
        return;
      }
    }
    return new HttpResponses.Unauthorized().send();
  }
});
```

## Benefits
- Declarative configuration
- Type safety through schemas
- Automatic OpenAPI documentation
- Consistent error handling
- Easy to test and maintain

## Implementation Guidelines
1. Keep middleware pure and focused
2. Use dependency injection for services
3. Implement proper error boundaries
4. Support type inference
5. Document configuration options

Remember to:
- Follow OpenAPI specifications
- Keep configuration declarative
- Implement proper error handling
- Support i18n where needed
- Document middleware behavior

# HTTP Response Pattern Best Practices

## Response Design Principles
- Use immutable response objects with chain methods
- Implement standardized response formatting
- Support metadata for tracing and monitoring
- Validate response structure at runtime
- Follow AWS Lambda response format
- Provide type-safe response builders
- Use proper error handling with ErrorFactory
- Support cold start detection
- Include request context information

## Implementation Pattern
```typescript
// ✅ Good: Chain method pattern
return new Ok('Operation successful')
  .withEvent(event)
  .withContext(context)
  .withHeaders({ 'Cache-Control': 'max-age=3600' })
  .send({ data: result });

// ✅ Good: Error handling with factory
return ErrorFactory.createErrorResponse(error, event, context);

// ❌ Bad: Manual response construction
return {
  statusCode: 200,
  body: JSON.stringify({ data })
};
```

## Response Structure
- Base Response:
  * statusCode: HTTP status code
  * statusKey: Status identifier (class name)
  * message: Human-readable message
  * requestId: Unique request identifier
  * meta: Request context information
  * headers: Response headers (CORS, etc.)

- Success Response (2xx):
```typescript
{
  statusCode: 200,
  statusKey: "Ok",
  message: "Operation successful",
  requestId: string,
  meta: {
    functionName: string,
    functionVersion: string,
    memoryLimitInMB: string,
    awsRequestId: string,
    coldStart: boolean,
    remainingTime: number,
    startTime: number,
    endTime: number,
    duration: number
  },
  data: unknown
}
```

- Error Response (4xx/5xx):
```typescript
{
  statusCode: 400,
  statusKey: "BadRequest",
  message: "Validation failed",
  requestId: string,
  meta: {
    functionName: string,
    functionVersion: string,
    memoryLimitInMB: string,
    awsRequestId: string,
    coldStart: boolean,
    remainingTime: number,
    startTime: number,
    endTime: number,
    duration: number
  },
  error: {
    code: number,
    name: string,
    context: {
      stage: string,
      requestId: string,
      path: string,
      method: string,
      details?: unknown
    },
    stack?: string, // Only in development
    cause?: unknown
  }
}
```

## Best Practices
- Response Creation:
  * Use chain methods for configuration
  * Validate response before sending
  * Include relevant metadata
  * Handle errors consistently
  * Support cold start detection
  * Track request context
  * Provide proper error details

- Error Handling:
  * Use ErrorFactory for error responses
  * Include error context and details
  * Log errors with proper severity
  * Hide sensitive info in production
  * Provide clear error messages
  * Support multiple error types:
    - ZodError for validation
    - HttpError for HTTP errors
    - Generic Error for others

- Metadata Handling:
  * Include Lambda context info
  * Track cold starts
  * Measure execution time
  * Include request ID
  * Support custom headers
  * Track environment/stage

- Response Validation:
  * Use Zod for runtime validation
  * Define reusable schemas
  * Validate both success and error responses
  * Log validation failures
  * Maintain type safety

## Usage Examples
```typescript
// Success with data
return new Ok('User created')
  .withEvent(event)
  .withContext(context)
  .withHeaders({ 'X-Custom-Header': 'value' })
  .send({ data: user });

// Error with details
return new BadRequest('Invalid input')
  .withEvent(event)
  .withContext(context)
  .withHeaders({ 'X-Error-Type': 'ValidationError' })
  .send({ 
    error: { 
      details: validationErrors 
    } 
  });

// Error factory usage
try {
  // Your code
} catch (error) {
  return ErrorFactory.createErrorResponse(error, event, context);
}
```

## Testing Considerations
- Test response structure
- Validate metadata presence
- Check error handling
- Verify header management
- Test response validation
- Ensure type safety
- Test cold start detection
- Verify context information
- Test error factory scenarios
- Check development vs production behavior

Remember to:
- Always include proper error context
- Use appropriate status codes
- Include relevant headers
- Track request lifecycle
- Handle cold starts properly
- Validate response structure
- Follow security best practices

aws
eslint
golang
javascript
jest
jwt
less
next.js
+4 more

First Time Repository

The nx-unified-app is a full-stack solution using NX to integrate a serverless backend (Lambda) with a Next.js and Expo frontend, featuring built-in CI/CD pipelines.

TypeScript

Languages:

JavaScript: 4.7KB
TypeScript: 147.4KB
Created: 1/20/2025
Updated: 1/20/2025

All Repositories (1)

The nx-unified-app is a full-stack solution using NX to integrate a serverless backend (Lambda) with a Next.js and Expo frontend, featuring built-in CI/CD pipelines.