Based on Planner and Memory Manager analysis: New Skills (8): - nodejs-express-patterns: App structure, routing, middleware - nodejs-security-owasp: OWASP Top 10 security practices - nodejs-testing-jest: Unit/integration tests, mocking - nodejs-auth-jwt: JWT authentication, OAuth, sessions - nodejs-error-handling: Error classes, middleware, async handlers - nodejs-middleware-patterns: Auth, validation, rate limiting - nodejs-db-patterns: SQLite, PostgreSQL, MongoDB patterns - nodejs-npm-management: package.json, scripts, dependencies New Rules: - nodejs.md: Code style, security, best practices Updated: - backend-developer.md: Added skills reference table Milestone: #48 NodeJS Development Coverage Related: Planner & Memory Manager analysis results
5.5 KiB
5.5 KiB
NodeJS Development Rules
Essential rules for Node.js backend development.
Code Style
- Use
constandlet, nevervar - Use arrow functions for callbacks
- Use async/await instead of callbacks
- Use template literals for string interpolation
- Use object destructuring
- Use spread operator for objects/arrays
// ✅ Good
const { id, name } = req.body;
const user = { ...req.body, createdAt: new Date() };
const users = await User.findAll();
// ❌ Bad
var id = req.body.id;
const user = Object.assign({}, req.body, { createdAt: new Date() });
User.findAll().then(users => {});
Error Handling
- Always use try/catch with async/await
- Use centralized error handling middleware
- Never catch and swallow errors
- Use custom AppError classes
- Log errors with context
// ✅ Good
try {
const user = await User.findById(id);
if (!user) throw new NotFoundError('User');
res.json({ user });
} catch (error) {
next(error);
}
// ❌ Bad
User.findById(id).then(user => {
if (!user) return res.status(404).json({ error: 'Not found' });
res.json({ user });
}).catch(err => {}); // Swallowing error
Async Code
- Always use async/await
- Never mix callbacks and promises
- Use Promise.all for parallel operations
- Use async middleware wrapper
// ✅ Good
const [users, posts] = await Promise.all([
User.findAll(),
Post.findAll()
]);
// ❌ Bad
let users;
User.findAll().then(u => { users = u; });
console.log(users); // undefined
Security
- Always validate and sanitize input
- Use parameterized queries
- Never expose sensitive data
- Use HTTPS in production
- Set security headers with helmet
- Rate limit public endpoints
// ✅ Good
const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
app.use(helmet());
// ❌ Bad
const user = await db.query(`SELECT * FROM users WHERE id = ${id}`);
// SQL injection vulnerable
Authentication
- Never store passwords in plain text
- Use bcrypt for password hashing
- Use short-lived access tokens
- Use refresh tokens
- Use httpOnly cookies
- Never put secrets in JWT payload
// ✅ Good
const hashedPassword = await bcrypt.hash(password, 12);
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
// ❌ Bad
const hashedPassword = password; // No hash
const token = jwt.sign({ password: user.password }, 'secret'); // Secret in payload
Express Best Practices
- Use express.Router() for route organization
- Keep route handlers thin
- Validate at route level
- Put error handlers last
- Use middleware for cross-cutting concerns
// ✅ Good
// routes/users.js
const router = express.Router();
router.get('/', authenticate, validate, controller.list);
// app.js
app.use('/api/users', routes.users);
app.use(errorHandler); // Last middleware
// ❌ Bad
app.get('/api/users', async (req, res) => {
// All logic in route
});
Database
- Use connection pooling
- Close connections gracefully
- Use transactions for writes
- Index frequently queried fields
- Use migrations for schema changes
// ✅ Good
await db.transaction(async (trx) => {
await trx('users').insert(user);
await trx('profiles').insert(profile);
});
// ❌ Bad
async function createUser(data) {
const user = await db('users').insert(data);
// No transaction, partial data on error
await Profile.create({ userId: user.id });
}
Logging
- Use structured logging (pino, winston)
- Log levels: error, warn, info, debug
- Include request ID for tracing
- Log errors with stack traces
- Don't log sensitive data
// ✅ Good
logger.info({ userId, action: 'login', ip: req.ip });
// ❌ Bad
console.log('User logged in:', user); // Logs entire user including password
Testing
- Write tests for critical paths
- Use Jest or Mocha
- Mock external dependencies
- Aim for 80%+ coverage
- Test edge cases
// ✅ Good
describe('UserService', () => {
it('should create user with hashed password', async () => {
const user = await service.create({ email, password });
expect(user.password).not.toBe(password);
});
});
Environment
- Use .env for secrets
- Never commit secrets
- Use different configs for environments
- Validate required env vars
// ✅ Good
const config = {
db: {
url: process.env.DATABASE_URL
}
};
if (!config.db.url) {
throw new Error('DATABASE_URL is required');
}
// ❌ Bad
const config = {
db: {
url: 'postgres://user:pass@localhost/db' // Hardcoded
}
};
Package Management
- Use exact versions in production
- Run npm audit regularly
- Update dependencies regularly
- Remove unused dependencies
# ✅ Good
npm audit
npx depcheck
# ❌ Bad
# Never running security audit
# Many unused dependencies
Performance
- Use streaming for large files
- Cache frequently accessed data
- Use connection pooling
- Implement pagination
- Compress responses
// ✅ Good
app.use(compression());
app.get('/users', paginated, controller.list);
// ❌ Bad
app.get('/users', async (req, res) => {
const users = await User.findAll(); // All users at once
res.json(users);
});
Clean Code
- No magic numbers, use constants
- Meaningful variable names
- One function, one responsibility
- Comments only for "why", not "what"
- DRY principle
// ✅ Good
const MAX_LOGIN_ATTEMPTS = 5;
const isLocked = user.loginAttempts >= MAX_LOGIN_ATTEMPTS;
// ❌ Bad
if (user.loginAttempts >= 5) { // Magic number
// ...
}