AgentSkillsCN

nodejs-best-practices

遵循业界最佳实践,编写整洁且易于维护的 Node.js 代码。无论是编写 Express 路由、服务层逻辑、数据库查询,还是进行错误处理与单元测试,此技能都能助您事半功倍。此外,还涵盖 PostgreSQL 连接池管理、事务处理、async/await 编程模式,以及 Jest 测试框架的高效应用。

SKILL.md
--- frontmatter
name: nodejs-best-practices
description: Clean, maintainable Node.js code following industry best practices. Use when writing Express routes, services, database queries, error handling, or tests. Covers PostgreSQL connection pooling, transactions, async/await patterns, and Jest testing.

Node.js Best Practices Skill

Overview

This skill ensures clean, maintainable, production-ready Node.js code. It covers Express application structure, PostgreSQL database patterns, error handling, testing, and security.

Project Structure

Organize code into distinct layers:

code
server/
├── index.js           # App setup, middleware mounting
├── routes/            # HTTP handling only, no business logic
├── services/          # Business logic, database operations
├── data/              # Static data, definitions, constants
├── db/                # Database connection, migrations
└── auth/              # Authentication middleware

Routes handle HTTP concerns (parsing requests, sending responses). Services contain business logic and database operations. Data modules export static definitions and pure functions.

Core Principles

1. Separation of Concerns

Routes should delegate to services, not contain business logic:

javascript
// GOOD: Route delegates to service
router.post('/:gameId/action', async (req, res) => {
  try {
    const result = await gameService.processAction(
      req.params.gameId, req.session.userId, req.body
    );
    res.json(result);
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

2. Consistent Async/Await

Use async/await consistently. Never mix with callbacks:

javascript
// GOOD: Clean async/await
async function getUser(userId) {
  const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
  return result.rows[0] || null;
}

3. Always Use Parameterized Queries

Never interpolate user input into SQL:

javascript
// GOOD: Parameterized (safe)
await pool.query('SELECT * FROM users WHERE id = $1', [userId]);

// BAD: SQL injection vulnerability
await pool.query(`SELECT * FROM users WHERE id = ${userId}`);

4. Proper Error Handling

Always handle errors - never swallow them:

javascript
router.post('/:id/action', async (req, res) => {
  try {
    const result = await processAction(req.params.id, req.body);
    res.json({ success: true, ...result });
  } catch (error) {
    console.error('Action failed:', error);
    res.status(400).json({ error: error.message });
  }
});

Database Patterns

See DATABASE.md for complete database patterns including:

  • Connection pooling configuration
  • Transaction handling with proper rollback
  • Row locking with FOR UPDATE
  • N+1 query prevention

Quick Reference

javascript
// Connection pool (singleton)
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30000
});

// Transaction pattern
const client = await pool.connect();
try {
  await client.query('BEGIN');
  // ... operations ...
  await client.query('COMMIT');
} catch (error) {
  await client.query('ROLLBACK');
  throw error;
} finally {
  client.release();  // ALWAYS release
}

Testing Patterns

See TESTING.md for complete testing patterns including:

  • Mocking the database pool
  • Testing transaction rollbacks
  • Route testing patterns

Quick Reference

javascript
// Mock database at top of test file
jest.mock('../../server/db', () => ({
  pool: { query: jest.fn(), connect: jest.fn(), on: jest.fn() }
}));

// Reset mocks between tests
beforeEach(() => {
  jest.clearAllMocks();
});

// Mock transaction client
const mockClient = { query: jest.fn(), release: jest.fn() };
pool.connect.mockResolvedValue(mockClient);

Express Patterns

Middleware Order

javascript
app.set('trust proxy', 1);        // 1. Proxy settings
app.use(express.json());          // 2. Body parsing
app.use(sessionMiddleware);       // 3. Session
app.use(express.static('public')); // 4. Static files
app.use('/api', routes);          // 5. Routes
app.use(errorHandler);            // 6. Error handler (last)

Authentication Middleware

javascript
function requireAuth(req, res, next) {
  if (!req.session?.userId) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
  next();
}

router.use(requireAuth);  // Apply to all routes in file

Input Validation

Validate early in route handlers:

javascript
router.post('/:gameId/action', async (req, res) => {
  const { actionType } = req.body;
  if (!actionType) {
    return res.status(400).json({ error: 'Action type is required' });
  }
  // Proceed with validated input...
});

Security Essentials

Environment Variables

javascript
// GOOD: From environment
const pool = new Pool({ connectionString: process.env.DATABASE_URL });

// BAD: Hardcoded credentials
const pool = new Pool({ connectionString: 'postgres://user:pass@host/db' });

Password Hashing

javascript
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 10;

await bcrypt.hash(password, SALT_ROUNDS);    // Hash
await bcrypt.compare(password, hash);         // Verify

Session Configuration

javascript
app.use(session({
  secret: process.env.SESSION_SECRET,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000
  }
}));

Performance Tips

Parallel Operations

Use Promise.all for independent operations:

javascript
const [game, players, state] = await Promise.all([
  getGameById(gameId),
  getGamePlayers(gameId),
  getGameState(gameId)
]);

Health Checks

Include database status:

javascript
app.get('/health', async (req, res) => {
  const dbHealthy = await db.healthCheck();
  res.status(dbHealthy ? 200 : 503).json({
    status: dbHealthy ? 'healthy' : 'degraded',
    database: dbHealthy ? 'connected' : 'disconnected'
  });
});

When This Skill Activates

Use this skill when:

  • Writing Express routes or middleware
  • Creating service layer functions
  • Working with PostgreSQL queries
  • Implementing database transactions
  • Writing Jest tests for Node.js code
  • Handling errors and edge cases
  • Setting up authentication or sessions