Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 243 additions & 0 deletions nodejs-ts/CONVENTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# Node.js TypeScript Server Development Conventions

## TypeScript Configuration
- Use strict mode: `"strict": true`, `"strictNullChecks"`, `"noImplicitAny"`
- Target ES2020+, use `"esModuleInterop": true`
- **Always provide explicit types** for function parameters and return values
- Avoid `any` - use `unknown` with type guards instead
- Prefer `interface` for object shapes, `type` for unions/intersections

## Project Structure
```
src/
├── config/ # Environment variables & configuration
├── controllers/ # Route handlers (thin, delegate to services)
├── services/ # Business logic (framework-agnostic)
├── models/ # Database schemas
├── types/ # TypeScript definitions
├── middleware/ # Express/Fastify middleware
├── routes/ # API route definitions
├── utils/ # Helper functions
├── validators/ # Input validation schemas (Zod/Joi)
└── constants/ # Application constants
```

## Code Style
- **2 spaces** indentation, **semicolons**, **single quotes**
- Max line length: **100 characters**, trailing commas
- Configure path aliases (`@/` → `src/`) in tsconfig.json
- Use absolute imports over relative when possible

## Naming Conventions
- Variables/Functions: `camelCase` (`getUserById`, `isActive`)
- Classes/Interfaces: `PascalCase` (`UserService`, `ApiResponse`)
- Constants: `UPPER_SNAKE_CASE` (`MAX_RETRY_ATTEMPTS`)
- Private properties: `_internalState`
- Booleans: prefix with `is`, `has`, `should`, `can`
- Files: `kebab-case.ts` (or `PascalCase.ts` for classes)

## Import Organization
Separate by blank lines in this order:
1. External dependencies (`express`, `zod`)
2. Internal modules with aliases (`@/services/user-service`)
3. Relative imports (`./middleware`)
4. Type-only imports (`import type { Request }`)

## Functions & Methods
- Prefer **async/await** over Promises/callbacks
- Arrow functions for inline callbacks, function declarations for module-level
- **Always specify return types explicitly**
- Use object parameters for 3+ arguments with destructuring
- Provide default values for optional parameters

## Error Handling
Create custom error classes:
```typescript
export class AppError extends Error {
constructor(
public message: string,
public statusCode: number = 500,
public code?: string,
public details?: unknown
) {
super(message);
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
}

export class ValidationError extends AppError {
constructor(message: string, details?: unknown) {
super(message, 400, 'VALIDATION_ERROR', details);
}
}
```

- Wrap async operations in try-catch
- Catch specific error types, re-throw after logging
- Use global error handler middleware (last in chain)

## Controllers
- Keep controllers thin - delegate to services
- Type Request, Response, NextFunction explicitly
- Use dependency injection
- One method per route handler

```typescript
export class UserController {
constructor(private userService: UserService) {}

getById = async (
req: Request<{ id: string }>,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const user = await this.userService.findById(req.params.id);
res.json({ data: user });
} catch (error) {
next(error);
}
};
}
```

## API Response Format
```typescript
// Success: { "data": {...}, "meta"?: {...} }
// Error: { "error": "message", "code": "ERROR_CODE", "details"?: {...} }
```

## Validation
- **Always validate** user input with Zod or Joi
- Validate at controller/route level, not in services
- Create reusable validation schemas

```typescript
export const createUserSchema = z.object({
body: z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
role: z.enum(['user', 'admin']).default('user'),
}),
});
```

## Services Layer
- Services contain business logic and orchestration
- Framework-agnostic (no Express/Fastify dependencies)
- One service per domain/entity
- Can call other services and repositories
- Validate business rules here

## Database & Repositories
- Use Prisma, TypeORM, or Drizzle for type-safe access
- Create repository classes for data access
- Keep business logic out of repositories
- **Use transactions** for multi-step operations
- Don't select sensitive fields (passwords) by default
- Always use migrations for schema changes

## Environment Configuration
```typescript
// Validate env vars at startup with Zod
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.string().transform(Number).pipe(z.number().positive()),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
});
export const env = envSchema.parse(process.env);
```
- Use dotenv for local development
- Never commit `.env` files (use `.env.example`)

## Logging
- Use Pino or Winston for structured logging (JSON)
- Include context: `logger.info({ userId, action }, 'User logged in')`
- Never log sensitive data (passwords, tokens)

## Security
- Hash passwords with bcrypt (10+ rounds)
- Use JWT for stateless auth or sessions for stateful
- Implement rate limiting on auth endpoints
- Use helmet for security headers
- Configure CORS properly with allowed origins
- Use parameterized queries (ORMs handle this)
- Create separate auth and authorization middleware

```typescript
export const authenticate = async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) throw new AppError('No token provided', 401);
const decoded = jwt.verify(token, env.JWT_SECRET);
req.user = await userService.findById(decoded.userId);
if (!req.user) throw new AppError('Invalid token', 401);
next();
};

export const authorize = (...roles: UserRole[]) =>
(req, res, next) => {
if (!req.user || !roles.includes(req.user.role)) {
throw new AppError('Insufficient permissions', 403);
}
next();
};
```

## Testing
- Write unit tests for services/utils
- Write integration tests for API endpoints
- Aim for 80%+ coverage
- Use test databases or mocks
- Mock dependencies in unit tests

## Async Patterns
- Always use async/await, not `.then()/.catch()`
- Use `Promise.all()` for concurrent operations
- Use `Promise.allSettled()` when some can fail
- Handle rejections properly

## Middleware Order
```typescript
app.use(helmet()); // Security headers
app.use(cors(corsOptions)); // CORS
app.use(express.json()); // Body parsing
app.use(requestLogger); // Logging
app.use('/api', rateLimiter); // Rate limiting
app.use('/api', authenticate); // Authentication
app.use('/api', routes); // Routes
app.use(errorHandler); // Error handler (LAST)
```

## Performance
- Use compression middleware
- Implement caching (Redis) for frequent reads
- Use connection pooling for databases
- Use streaming for large data transfers
- Avoid blocking the event loop

## Documentation
- Add JSDoc for public APIs and complex functions
- Document **why**, not what (code should be self-documenting)
- Keep docs updated with code changes

## Dependencies
Essential packages:
- express/fastify, zod/joi, prisma/typeorm, bcrypt, jsonwebtoken
- dotenv, pino/winston, helmet, cors, jest/vitest

Dev tools:
- ESLint + @typescript-eslint, Prettier, Husky, lint-staged, tsx/nodemon

## Git Practices
- Conventional Commits: `type(scope): subject`
- Types: feat, fix, docs, style, refactor, test, chore
- Feature branches, PR reviews, stable main branch

## Key Principles
- **Type safety**: Explicit types everywhere, no `any`
- **Separation of concerns**: Controllers → Services → Repositories
- **Security first**: Validate input, hash passwords, use middleware
- **Error handling**: Custom errors, try-catch, global handler
- **Consistency**: Apply these conventions uniformly
83 changes: 83 additions & 0 deletions nodejs-ts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Node.js TypeScript Server Conventions

This conventions file provides comprehensive guidelines for building professional, scalable Node.js backend servers using TypeScript. These conventions are designed to work with [aider](https://aider.chat) to ensure consistent, maintainable, and production-ready server code.

## Purpose

These conventions ensure that your Node.js TypeScript server code is:
- **Type-safe** - Leveraging TypeScript's full potential for compile-time safety
- **Maintainable** - Following consistent patterns and clear structure
- **Scalable** - Architected for growth and easy extension
- **Production-ready** - Including error handling, logging, and security best practices
- **Testable** - Designed with testing in mind from the start

## Use Cases

This convention file is ideal for:
- Building REST APIs with Express or Fastify
- Creating GraphQL servers
- Developing microservices
- Building real-time applications with WebSockets
- Server-side applications requiring robust architecture
- Projects that prioritize type safety and code quality

## What's Included

- **TypeScript Configuration** - Strict type checking and modern ES features
- **Project Structure** - Organized, scalable folder hierarchy
- **Code Style** - Consistent formatting and naming conventions
- **Error Handling** - Comprehensive error management patterns
- **API Design** - RESTful and async/await best practices
- **Security** - Input validation, authentication, and common vulnerabilities
- **Testing** - Unit and integration testing guidelines
- **Logging & Monitoring** - Structured logging patterns
- **Database** - ORM/query builder patterns and migrations
- **Environment Configuration** - Managing config across environments

## Usage

To use these conventions with aider:

```bash
# Add the conventions file to your aider session
aider --read CONVENTIONS.md

# Or add to your .aider.conf.yml
read: CONVENTIONS.md
```

For more information about using conventions with aider, see the [conventions documentation](https://aider.chat/docs/usage/conventions.html).

## Example Projects

These conventions work well with:
- Express.js + TypeScript servers
- Fastify + TypeScript APIs
- NestJS applications
- Next.js API routes
- Apollo Server (GraphQL)
- Socket.io real-time applications
- Serverless functions (AWS Lambda, Vercel, etc.)

## Technology Stack

The conventions assume or recommend:
- **Runtime**: Node.js 18+ or 20+ (LTS versions)
- **Language**: TypeScript 5.x
- **Package Manager**: npm, yarn, or pnpm
- **Testing**: Jest or Vitest
- **Linting**: ESLint with TypeScript support
- **Formatting**: Prettier
- **Validation**: Zod or Joi
- **ORM/Query Builder**: Prisma, TypeORM, or Drizzle

## Contributing

Found an improvement or have a suggestion? These conventions can be customized to fit your team's specific needs. Feel free to fork and adapt them to your requirements.

## Related Resources

- [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices)
- [TypeScript Deep Dive](https://basarat.gitbook.io/typescript/)
- [Express.js Best Practices](https://expressjs.com/en/advanced/best-practice-performance.html)
- [Twelve-Factor App](https://12factor.net/)