feat: add comprehensive NodeJS development skills and rules
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
This commit is contained in:
377
.kilo/skills/nodejs-middleware-patterns/SKILL.md
Normal file
377
.kilo/skills/nodejs-middleware-patterns/SKILL.md
Normal file
@@ -0,0 +1,377 @@
|
||||
# NodeJS Middleware Patterns
|
||||
|
||||
Comprehensive middleware patterns for Express.js applications.
|
||||
|
||||
## Middleware Types
|
||||
|
||||
### 1. Application-level Middleware
|
||||
|
||||
```javascript
|
||||
// Global middleware
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(helmet());
|
||||
app.use(cors());
|
||||
|
||||
// Conditional middleware
|
||||
app.use((req, res, next) => {
|
||||
if (req.path.startsWith('/api')) {
|
||||
return apiLimiter(req, res, next);
|
||||
}
|
||||
next();
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Router-level Middleware
|
||||
|
||||
```javascript
|
||||
const router = require('express').Router();
|
||||
|
||||
// Router-specific middleware
|
||||
router.use(authenticate);
|
||||
router.use(validateRequest);
|
||||
|
||||
// Route-specific middleware
|
||||
router.post('/', validateUser, controller.create);
|
||||
router.put('/:id', validateUser, authenticate, controller.update);
|
||||
```
|
||||
|
||||
### 3. Error-handling Middleware
|
||||
|
||||
```javascript
|
||||
// Must have 4 parameters
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(err.statusCode || 500).json({
|
||||
status: 'error',
|
||||
message: err.message
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 4. Built-in Middleware
|
||||
|
||||
```javascript
|
||||
app.use(express.static('public'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Authentication Middleware
|
||||
|
||||
```javascript
|
||||
function authenticate(req, res, next) {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
try {
|
||||
const decoded = verifyToken(token);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
return res.status(401).json({ message: 'Invalid token' });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Authorization Middleware
|
||||
|
||||
```javascript
|
||||
function authorize(...roles) {
|
||||
return (req, res, next) => {
|
||||
if (!roles.includes(req.user.role)) {
|
||||
return res.status(403).json({ message: 'Forbidden' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
// Usage
|
||||
router.delete('/:id', authenticate, authorize('admin'), controller.delete);
|
||||
```
|
||||
|
||||
### Rate Limiting Middleware
|
||||
|
||||
```javascript
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 100,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { message: 'Too many requests' }
|
||||
});
|
||||
|
||||
app.use('/api', limiter);
|
||||
```
|
||||
|
||||
### Validation Middleware
|
||||
|
||||
```javascript
|
||||
const { validationResult, body } = require('express-validator');
|
||||
|
||||
function validate(validations) {
|
||||
return async (req, res, next) => {
|
||||
await Promise.all(validations.map(v => v.run(req)));
|
||||
|
||||
const errors = validationResult(req);
|
||||
if (errors.isEmpty()) return next();
|
||||
|
||||
res.status(400).json({
|
||||
status: 'fail',
|
||||
errors: errors.array().map(err => ({
|
||||
field: err.path,
|
||||
message: err.msg
|
||||
}))
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Usage
|
||||
router.post('/users',
|
||||
validate([
|
||||
body('email').isEmail(),
|
||||
body('password').isLength({ min: 8 })
|
||||
]),
|
||||
controller.create
|
||||
);
|
||||
```
|
||||
|
||||
### Request Logging Middleware
|
||||
|
||||
```javascript
|
||||
const pino = require('pino');
|
||||
const logger = pino();
|
||||
|
||||
function requestLogger(req, res, next) {
|
||||
const start = Date.now();
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
logger.info({
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
status: res.statusCode,
|
||||
duration,
|
||||
user: req.user?.id
|
||||
});
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
```
|
||||
|
||||
### Context Middleware
|
||||
|
||||
```javascript
|
||||
// Request-scoped context
|
||||
const asyncLocalStorage = new (require('async_hooks').AsyncLocalStorage)();
|
||||
|
||||
function contextMiddleware(req, res, next) {
|
||||
const context = {
|
||||
requestId: require('uuid').v4(),
|
||||
userId: req.user?.id,
|
||||
startTime: Date.now()
|
||||
};
|
||||
|
||||
asyncLocalStorage.run(context, next);
|
||||
}
|
||||
|
||||
// Access context anywhere in the request lifecycle
|
||||
function getContext() {
|
||||
return asyncLocalStorage.getStore();
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handler Middleware
|
||||
|
||||
```javascript
|
||||
function errorHandler(err, req, res, next) {
|
||||
const statusCode = err.statusCode || 500;
|
||||
const status = err.status || 'error';
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return res.status(statusCode).json({
|
||||
status,
|
||||
message: err.message,
|
||||
stack: err.stack
|
||||
});
|
||||
}
|
||||
|
||||
if (err.isOperational) {
|
||||
return res.status(statusCode).json({
|
||||
status,
|
||||
message: err.message
|
||||
});
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Third-party Middleware
|
||||
|
||||
### Security (Helmet)
|
||||
|
||||
```javascript
|
||||
const helmet = require('helmet');
|
||||
|
||||
app.use(helmet());
|
||||
app.use(helmet.contentSecurityPolicy({
|
||||
directives: {
|
||||
defaultSrc: ["'self'"],
|
||||
scriptSrc: ["'self'"]
|
||||
}
|
||||
}));
|
||||
```
|
||||
|
||||
### Compression
|
||||
|
||||
```javascript
|
||||
const compression = require('compression');
|
||||
|
||||
app.use(compression({
|
||||
threshold: 1024, // Only compress >1KB
|
||||
filter: (req, res) => {
|
||||
if (req.headers['x-no-compression']) return false;
|
||||
return compression.filter(req, res);
|
||||
}
|
||||
}));
|
||||
```
|
||||
|
||||
### CORS
|
||||
|
||||
```javascript
|
||||
const cors = require('cors');
|
||||
|
||||
app.use(cors({
|
||||
origin: process.env.CORS_ORIGIN?.split(',') || '*',
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization'],
|
||||
credentials: true
|
||||
}));
|
||||
```
|
||||
|
||||
### Morgan (Logging)
|
||||
|
||||
```javascript
|
||||
const morgan = require('morgan');
|
||||
|
||||
app.use(morgan('dev')); // Development
|
||||
app.use(morgan('combined')); // Production
|
||||
```
|
||||
|
||||
## Custom Middleware Factory
|
||||
|
||||
```javascript
|
||||
// Custom middleware factory
|
||||
function createMiddleware(options) {
|
||||
return (req, res, next) => {
|
||||
// Pre-processing
|
||||
if (options.preCondition && !options.preCondition(req)) {
|
||||
return res.status(400).json({ message: 'Precondition failed' });
|
||||
}
|
||||
|
||||
// Modify request
|
||||
req.custom = options.transform?.(req) || req.custom;
|
||||
|
||||
// Post-processing (on response)
|
||||
res.on('finish', () => {
|
||||
options.postProcess?.(req, res);
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
// Usage
|
||||
app.use(createMiddleware({
|
||||
preCondition: (req) => req.headers['x-api-key'],
|
||||
transform: (req) => ({ ip: req.ip }),
|
||||
postProcess: (req, res) => console.log(`Request completed: ${req.path}`)
|
||||
}));
|
||||
```
|
||||
|
||||
## Async Middleware Wrapper
|
||||
|
||||
```javascript
|
||||
function asyncHandler(fn) {
|
||||
return (req, res, next) =>
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
}
|
||||
|
||||
// Wrap middleware
|
||||
app.get('/users/:id', asyncHandler(async (req, res) => {
|
||||
const user = await User.findById(req.params.id);
|
||||
res.json({ user });
|
||||
}));
|
||||
```
|
||||
|
||||
## Middleware Composition
|
||||
|
||||
```javascript
|
||||
// Compose multiple middleware
|
||||
function compose(...middlewares) {
|
||||
return (req, res, next) => {
|
||||
let index = -1;
|
||||
|
||||
function dispatch(i) {
|
||||
if (i <= index) return next(new Error('next() called multiple times'));
|
||||
index = i;
|
||||
|
||||
const middleware = middlewares[i];
|
||||
if (!middleware) return next();
|
||||
|
||||
return middleware(req, res, (err) => {
|
||||
if (err) return next(err);
|
||||
return dispatch(i + 1);
|
||||
});
|
||||
}
|
||||
|
||||
return dispatch(0);
|
||||
};
|
||||
}
|
||||
|
||||
// Usage
|
||||
app.use(compose(
|
||||
authenticate,
|
||||
authorize('user'),
|
||||
validateRequest
|
||||
));
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Order matters** - Put error handlers last
|
||||
2. **Use router middleware** - For route groups
|
||||
3. **Use async handlers** - Wrap async functions
|
||||
4. **Keep it simple** - One responsibility per middleware
|
||||
5. **Use third-party** - Don't reinvent the wheel
|
||||
6. **Document dependencies** - Comment required fields
|
||||
|
||||
## Middleware Order
|
||||
|
||||
```javascript
|
||||
// Correct order
|
||||
app.use(helmet()); // Security headers
|
||||
app.use(cors()); // CORS
|
||||
app.use(compression()); // Compression
|
||||
app.use(express.json()); // Body parsing
|
||||
app.use(express.urlencoded()); // URL encoding
|
||||
app.use(morgan('dev')); // Logging
|
||||
app.use('/api', apiLimiter); // Rate limiting
|
||||
app.use('/api', routes); // Routes
|
||||
app.use(notFoundHandler); // 404 handler
|
||||
app.use(errorHandler); // Error handler
|
||||
```
|
||||
Reference in New Issue
Block a user