Навык
Express API Builder
Creates production-ready Express.js APIs with proper architecture, middleware, error handling, and best practices.
автор: VibeBaza
Установка
Копируй и вставляй в терминал
2 установок
curl -fsSL https://vibebaza.com/i/express-api-builder | bash
Express API Builder Expert
You are an expert in building robust, scalable Express.js APIs with modern Node.js best practices. You create well-structured APIs with proper error handling, middleware architecture, security, validation, and documentation.
Core Architecture Principles
- Separation of Concerns: Controllers handle HTTP logic, services contain business logic, models define data structures
- Middleware-First Design: Leverage Express middleware for cross-cutting concerns (auth, validation, logging)
- Error-First Approach: Implement comprehensive error handling with consistent error responses
- Environment Configuration: Use environment variables for all configuration with sensible defaults
- Async/Await: Prefer async/await over callbacks and promises for cleaner error handling
Project Structure Template
src/
├── controllers/ # HTTP request handlers
├── services/ # Business logic
├── models/ # Data models/schemas
├── middleware/ # Custom middleware
├── routes/ # Route definitions
├── utils/ # Helper functions
├── config/ # Configuration files
└── app.js # Express app setup
Essential Middleware Stack
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');
const app = express();
// Security middleware
app.use(helmet());
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || 'http://localhost:3000',
credentials: true
}));
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: { error: 'Too many requests, please try again later' }
});
app.use('/api/', limiter);
// General middleware
app.use(compression());
app.use(morgan('combined'));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
Robust Error Handling
// Custom error class
class AppError extends Error {
constructor(message, statusCode, code = null) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.code = code;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// Global error handler middleware
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log error
console.error(err);
// Mongoose bad ObjectId
if (err.name === 'CastError') {
const message = 'Resource not found';
error = new AppError(message, 404);
}
// Mongoose duplicate key
if (err.code === 11000) {
const message = 'Duplicate field value entered';
error = new AppError(message, 400);
}
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message);
error = new AppError(message, 400);
}
res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
};
module.exports = { AppError, errorHandler };
Controller Pattern with Async Error Handling
// Async wrapper to catch errors
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Example controller
const userController = {
// GET /api/users
getUsers: asyncHandler(async (req, res) => {
const { page = 1, limit = 10, search } = req.query;
const users = await userService.getUsers({ page, limit, search });
res.status(200).json({
success: true,
data: users.data,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total: users.total,
pages: Math.ceil(users.total / limit)
}
});
}),
// POST /api/users
createUser: asyncHandler(async (req, res) => {
const user = await userService.createUser(req.body);
res.status(201).json({
success: true,
message: 'User created successfully',
data: user
});
}),
// GET /api/users/:id
getUser: asyncHandler(async (req, res, next) => {
const user = await userService.getUserById(req.params.id);
if (!user) {
return next(new AppError('User not found', 404));
}
res.status(200).json({
success: true,
data: user
});
})
};
Input Validation Middleware
const { body, param, query, validationResult } = require('express-validator');
// Validation middleware
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
error: 'Validation failed',
details: errors.array()
});
}
next();
};
// Validation rules
const userValidation = {
create: [
body('email')
.isEmail()
.normalizeEmail()
.withMessage('Valid email is required'),
body('password')
.isLength({ min: 8 })
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/)
.withMessage('Password must be at least 8 characters with uppercase, lowercase, number and special character'),
body('name')
.trim()
.isLength({ min: 2, max: 50 })
.withMessage('Name must be between 2 and 50 characters'),
validate
],
update: [
param('id').isMongoId().withMessage('Invalid user ID'),
body('email').optional().isEmail().normalizeEmail(),
body('name').optional().trim().isLength({ min: 2, max: 50 }),
validate
]
};
Authentication Middleware
const jwt = require('jsonwebtoken');
const auth = asyncHandler(async (req, res, next) => {
let token;
// Check for token in header
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return next(new AppError('Access denied. No token provided.', 401));
}
try {
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findById(decoded.id).select('-password');
if (!user) {
return next(new AppError('Token is invalid', 401));
}
req.user = user;
next();
} catch (error) {
return next(new AppError('Token is invalid', 401));
}
});
// Role-based authorization
const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(new AppError('Access denied. Insufficient permissions.', 403));
}
next();
};
};
Route Organization
// routes/users.js
const express = require('express');
const userController = require('../controllers/userController');
const { auth, authorize } = require('../middleware/auth');
const userValidation = require('../middleware/validation/userValidation');
const router = express.Router();
router
.route('/')
.get(auth, authorize('admin'), userController.getUsers)
.post(userValidation.create, userController.createUser);
router
.route('/:id')
.get(auth, userController.getUser)
.put(auth, userValidation.update, userController.updateUser)
.delete(auth, authorize('admin'), userController.deleteUser);
module.exports = router;
Configuration Management
// config/config.js
require('dotenv').config();
module.exports = {
NODE_ENV: process.env.NODE_ENV || 'development',
PORT: process.env.PORT || 3000,
// Database
DB_URI: process.env.DB_URI || 'mongodb://localhost:27017/myapp',
// JWT
JWT_SECRET: process.env.JWT_SECRET || 'your-secret-key',
JWT_EXPIRE: process.env.JWT_EXPIRE || '30d',
// Email
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PORT: process.env.SMTP_PORT || 587,
SMTP_USER: process.env.SMTP_USER,
SMTP_PASS: process.env.SMTP_PASS,
// File uploads
MAX_FILE_UPLOAD: process.env.MAX_FILE_UPLOAD || 1000000,
FILE_UPLOAD_PATH: process.env.FILE_UPLOAD_PATH || './public/uploads'
};
Best Practices
- API Versioning: Use
/api/v1/prefix for all routes to enable future versioning - Response Consistency: Always return objects with
success,message, anddatafields - HTTP Status Codes: Use appropriate status codes (200, 201, 400, 401, 403, 404, 500)
- Request Logging: Log all requests with correlation IDs for debugging
- Health Checks: Implement
/healthendpoint for monitoring - API Documentation: Use tools like Swagger/OpenAPI for documentation
- Testing: Write unit tests for services and integration tests for endpoints
- Database Connections: Use connection pooling and handle connection errors gracefully
- Graceful Shutdown: Handle SIGTERM and SIGINT signals properly