Copilot Instructions for Node.js/Fastify Projects
Important Process Requirements
- Always use TypeScript with strict mode enabled
- Enable
experimentalDecorators and emitDecoratorMetadata in tsconfig.json for dependency injection
- Import
reflect-metadata at the top of main.ts
- Use Zod for schema validation and type inference
- Implement proper dependency injection using
fastify-decorators
- Always validate request/response data with schemas
- Use correlation IDs for request tracing (generate with uuid)
- Configure environment-specific settings using dedicated config files
- ALWAYS present a detailed plan and wait for explicit approval before implementing any code changes
- Do not proceed with implementation until receiving confirmation from the user
- When presenting the plan, provide a step-by-step breakdown of all files to be created or modified
- Ask directly: "Do you approve this plan before I proceed with implementation?"
Naming Conventions
Files and Directories
- Use kebab-case for file names:
posts.example.controller.ts
- Add descriptive suffixes:
.controller.ts, .service.ts, .middleware.ts, .plugin.ts
- Test files:
*.spec.ts or *.test.ts
- Config files:
app.config.{env}.ts format
- Place tests in
__tests__ directories within their respective modules
Classes and Interfaces
- Use PascalCase for classes:
PostController, BaseHttpService
- Use PascalCase for interfaces:
AppConfig, ValidationErrorDetail
- Controllers: End with
Controller (e.g., HealthController)
- Services: End with
Service (e.g., PostService)
- Errors: End with
Error (e.g., ValidationError)
Variables and Methods
- Use camelCase for variables and methods:
getAllPosts, baseUrl
- Use descriptive names:
createPost instead of create
- Boolean variables: Use
is, has, can prefixes when appropriate
- Constants: Use UPPER_SNAKE_CASE for module-level constants
Schema and Type Names
- Zod schemas: End with
Schema (e.g., PostSchema, CreatePostSchema)
- TypeScript types: Use inferred types from Zod schemas with
z.infer<typeof Schema>
- Response schemas: Include HTTP method context (e.g.,
postListResponseSchema)
Code Style
- Use 2 spaces for indentation
- Use semicolons at the end of statements
- Use single quotes for strings
- Trailing commas in objects and arrays
- Line length: Maximum 85 characters
- Use template literals for string interpolation
Import Organization
- Group imports: external libraries first, then internal modules
- Use simple-import-sort ESLint plugin for automatic sorting
- Relative imports for local modules:
'../cross-cutting/config'
- Absolute imports from node_modules:
'fastify'
Code Structure
- Use async/await instead of promises with .then()
- Destructure objects when accessing multiple properties
- Use optional chaining (
?.) when appropriate
- Prefer const over let, avoid var
- Use arrow functions for short callbacks
Project Structure
src/
├── main.ts # Application entry point
├── server.ts # Server configuration
├── routes.ts # Route registration
├── controllers/ # HTTP request handlers
│ ├── __tests__/ # Controller tests
│ └── *.controller.ts
├── service/ # Business logic services
│ └── *.service.ts
├── middlewares/ # Custom middlewares
│ └── *.middleware.ts
├── plugins/ # Fastify plugins
│ ├── index.ts # Plugin registration
│ └── *.plugin.ts
├── schemas/ # Request/response schemas
│ └── *.schemas.ts
├── types/ # Type definitions
│ └── *.types.ts
├── interfaces/ # Interface definitions
│ └── *.ts
└── cross-cutting/ # Shared concerns
├── config/ # Configuration management
├── error-handling/ # Error classes and handling
├── logging/ # Logging configuration
├── communication/ # HTTP client services
├── caching/ # Cache strategies
└── context/ # Request context management
Organization Principles
- Keep cross-cutting concerns separated
- Place tests close to the code they test
- Use index.ts files for clean imports
Documentation
- Use JSDoc for public APIs and complex functions
- Explain "why" not "what" in comments
- Document configuration options and environment variables
- Include examples for complex schemas or services
Repository Documentation
- Maintain a comprehensive README.md with setup instructions
- Document API endpoints using Fastify Swagger integration
- Keep CHANGELOG.md updated using conventional commits
- Include environment variable documentation
- Provide Docker setup instructions
API Documentation
- Use Fastify Swagger plugin for automatic API docs
- Define comprehensive schemas for all endpoints
- Include response examples and error codes
Testing
Test Structure
- Use Jest as the testing framework
- Use
@swc/jest for fast TypeScript compilation
- Place tests in
__tests__ directories
- Use descriptive test names:
should respond with 200 OK
Test Patterns
describe('FeatureName', () => {
let app: FastifyInstance;
beforeAll(async () => {
app = await createServer();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('should perform expected behavior', async () => {
// Arrange, Act, Assert pattern
});
});
Testing Guidelines
- Test controllers using
app.inject() for HTTP testing
- Mock external dependencies using Jest mocks
- Use factory functions for test data creation
- Test both success and error scenarios
- Maintain test coverage thresholds (minimum 80% for branches, functions, lines, statements)
Dependencies & Setup
Package Management
- Use yarn as package manager
- Pin dependency versions in package.json
- Separate devDependencies from dependencies
- Use
peerDependencies for plugin development
Key Dependencies
- Runtime:
fastify, fastify-decorators, zod, axios
- Development:
typescript, jest, eslint, prettier
- Utilities:
uuid, pino, dotenv, qs
Import/Export Patterns
- Use ES6 imports/exports
- Export classes and functions as named exports
- Use default exports for main module exports
- Re-export from index.ts files for clean imports
Error Handling
Error Architecture
- Extend
BaseError for all custom errors
- Use specific error classes:
ValidationError, CommunicationError, ApiError
- Include error codes and HTTP status codes
- Provide detailed error information for debugging
Error Types
// Custom error classes
export class ValidationError extends BaseError
export class CommunicationError extends BaseError
export class ApiError extends BaseError
// Error structure
interface Error {
message: string;
code: string;
statusCode: StatusCodes;
validation?: any;
}
Error Handling Patterns
- Use global error handler middleware
- Transform external errors (Axios, Zod) to internal error types
- Log errors with correlation IDs
- Return consistent error response format
- Hide internal error details in production
Monitoring
Logging Strategy
- Use Pino logger for structured logging
- Include correlation IDs in all log entries
- Log request/response data selectively
- Use different log levels: debug, info, warn, error
- Configure log serializers for request/response objects
Log Structure
- Include timestamp, level, message, and correlation_id
- Add trace_id from incoming requests
- Log HTTP method, URL, and status codes
- Use structured logging with JSON format
Observability
- Integrate New Relic for application monitoring
- Enable New Relic only in dev/staging/production environments
- Configure proper error capturing and reporting
- Include performance metrics and traces
HTTP Client Optimization
- Use keep-alive agents for HTTP connections
- Configure connection pooling with
agentkeepalive
- Implement request/response interceptors for common headers
- Use appropriate timeouts for external calls
Caching Strategy
- Implement cache abstraction with multiple strategies (Redis, LRU)
- Use middleware for route-level caching
- Cache frequently accessed data
- Implement cache invalidation strategies
- Use async/await for non-blocking operations
- Implement connection pooling for databases
- Use streaming for large data processing
- Enable gzip compression for responses
Design Patterns
Dependency Injection
- Mark services with
@Service() decorator
- Inject dependencies using
@Inject() decorator
- Use constructor injection for external dependencies
Repository/Service Pattern
- Separate business logic (services) from data access
- Use base classes for common functionality (
BaseHttpService)
- Implement interfaces for external service contracts
- Use factory pattern for cache strategy selection
Plugin Architecture
- Create reusable plugins for cross-cutting concerns
- Use
fastify-plugin for plugin registration
- Implement plugin lifecycle hooks properly
- Register plugins before route registration
Schema-First Development
- Define Zod schemas for all data structures
- Use
z.infer for type generation
- Validate all input/output with schemas
- Transform schemas to JSON Schema for Swagger documentation
Node.js/Fastify-Specific Guidance
Fastify Best Practices
- Register plugins before routes
- Use plugin encapsulation for feature isolation
- Implement proper request lifecycle hooks
- Use Fastify's built-in validation with schemas
Request/Response Handling
- Use typed request/response interfaces
- Implement proper HTTP status codes
- Use reply decorators for response helpers
- Handle request context with
@fastify/request-context
Middleware Patterns
- Implement middleware as Fastify hooks or plugins
- Use
onRequest hooks for request preprocessing
- Use
preHandler for route-specific middleware
- Implement global error handling middleware
Configuration Management
- Use environment-specific configuration files
- Validate configuration with Zod schemas
- Use
@fastify/env for environment variable management
- Separate configuration from application logic
Security Considerations
- Enable CORS with
@fastify/cors
- Use helmet for security headers
- Implement rate limiting
- Validate and sanitize all inputs
- Use secure cookie settings with
@fastify/cookie