express-utility-kit
v1.1.0
Published
Production-ready utilities for Express applications to reduce boilerplate
Maintainers
Readme
express-utility-kit
Production-ready utilities for Express applications to reduce boilerplate and standardize error handling, async operations, and API responses.
Installation
npm install express-utility-kitFeatures
- asyncHandler: Wrap async route handlers to automatically catch errors
- AppError: Custom error class with status codes and operational flags
- errorMiddleware: Global error handler with standardized JSON responses
- notFoundMiddleware: Handle undefined routes with 404 errors
- responseFormatter: Helper methods for consistent API responses with pagination support
- authenticate: JWT authentication middleware
- JWT Helpers: Generate, verify, and decode JWT tokens
- Password Helpers: Hash and compare passwords using bcrypt
- Pagination Helpers: Calculate pagination metadata and parse query parameters
- Validation Helpers: Email validation, password strength validation, and input sanitization
- OTP Helpers: Generate numeric/alphanumeric OTPs with expiry, secure tokens
Usage
Basic Setup
import express from 'express';
import {
asyncHandler,
errorMiddleware,
notFoundMiddleware,
AppError,
responseFormatter
} from 'express-utility-kit';
const app = express();
// Apply response formatter middleware globally
app.use(responseFormatter);
// Your routes here
app.get('/api/users', asyncHandler(async (req, res) => {
const users = await getUsersFromDB();
res.success(users, 'Users fetched successfully');
}));
app.post('/api/users', asyncHandler(async (req, res) => {
const user = await createUser(req.body);
res.success(user, 'User created successfully', 201);
}));
// Handle undefined routes (must be after all routes)
app.use(notFoundMiddleware);
// Global error handler (must be last)
app.use(errorMiddleware);
app.listen(3000, () => {
console.log('Server running on port 3000');
});Using asyncHandler
Wrap async route handlers to automatically catch errors and pass them to error middleware:
import { asyncHandler } from 'express-utility-kit';
app.get('/api/posts/:id', asyncHandler(async (req, res) => {
const post = await Post.findById(req.params.id);
if (!post) {
throw new AppError('Post not found', 404);
}
res.success(post, 'Post retrieved successfully');
}));Using AppError
Throw custom errors with specific status codes:
import { AppError, asyncHandler } from 'express-utility-kit';
app.delete('/api/posts/:id', asyncHandler(async (req, res) => {
const post = await Post.findById(req.params.id);
if (!post) {
throw new AppError('Post not found', 404);
}
if (post.userId !== req.user.id) {
throw new AppError('Not authorized to delete this post', 403);
}
await post.remove();
res.success(null, 'Post deleted successfully');
}));Using responseFormatter
The response formatter adds helper methods to the response object:
// Success response
res.success(data, message, statusCode);
// Example:
res.success({ id: 1, name: 'John' }, 'User created', 201);
// Returns:
// {
// "success": true,
// "message": "User created",
// "data": { "id": 1, "name": "John" }
// }
// Error response
res.error(message, statusCode);
// Example:
res.error('Invalid credentials', 401);
// Returns:
// {
// "success": false,
// "message": "Invalid credentials",
// "data": null
// }
// Paginated response
res.paginated(data, pagination, message, statusCode);
// Example:
res.paginated(
users,
{ page: 1, limit: 10, total: 50 },
'Users retrieved successfully'
);
// Returns:
// {
// "success": true,
// "message": "Users retrieved successfully",
// "data": [...users],
// "pagination": {
// "page": 1,
// "limit": 10,
// "total": 50,
// "totalPages": 5,
// "hasNextPage": true,
// "hasPrevPage": false
// }
// }Using JWT Authentication
import { generateToken, verifyToken, authenticate } from 'express-utility-kit';
// Generate token on login
app.post('/api/login', asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Verify user credentials (your logic here)
const user = await User.findOne({ email });
if (!user || !(await comparePassword(password, user.password))) {
throw new AppError('Invalid credentials', 401);
}
// Generate JWT token
const token = generateToken(
{ id: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.success({ token, user }, 'Login successful');
}));
// Protect routes with authentication middleware
const authMiddleware = authenticate(process.env.JWT_SECRET);
app.get('/api/profile', authMiddleware, asyncHandler(async (req, res) => {
// req.user contains decoded token payload
const user = await User.findById(req.user.id);
res.success(user, 'Profile retrieved');
}));Using Password Helpers
import { hashPassword, comparePassword } from 'express-utility-kit';
// Register user with hashed password
app.post('/api/register', asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Hash password before saving
const hashedPassword = await hashPassword(password);
const user = await User.create({
email,
password: hashedPassword
});
res.success(user, 'User registered successfully', 201);
}));
// Login with password comparison
app.post('/api/login', asyncHandler(async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await comparePassword(password, user.password))) {
throw new AppError('Invalid credentials', 401);
}
res.success({ user }, 'Login successful');
}));Using Pagination Helpers
import { parsePaginationQuery, getPaginationMeta } from 'express-utility-kit';
app.get('/api/users', asyncHandler(async (req, res) => {
// Parse pagination from query params (?page=1&limit=10)
const { page, limit, skip } = parsePaginationQuery(req.query);
// Fetch data with pagination
const users = await User.find().skip(skip).limit(limit);
const total = await User.countDocuments();
// Send paginated response
res.paginated(users, { page, limit, total }, 'Users retrieved');
}));Using Validation Helpers
import { isValidEmail, validatePassword, sanitizeString } from 'express-utility-kit';
app.post('/api/register', asyncHandler(async (req, res) => {
const { email, password, name } = req.body;
// Validate email
if (!isValidEmail(email)) {
throw new AppError('Invalid email format', 400);
}
// Validate password strength
const passwordValidation = validatePassword(password, {
minLength: 8,
requireUppercase: true,
requireNumbers: true
});
if (!passwordValidation.isValid) {
throw new AppError(passwordValidation.errors.join(', '), 400);
}
// Sanitize user input
const sanitizedName = sanitizeString(name);
// Create user...
res.success({ email, name: sanitizedName }, 'User registered', 201);
}));Using OTP Helpers
import {
generateOTP,
generateAlphanumericOTP,
generateOTPWithExpiry,
isOTPExpired,
generateSecureToken
} from 'express-utility-kit';
// Generate numeric OTP
app.post('/api/send-otp', asyncHandler(async (req, res) => {
const { email } = req.body;
// Generate 6-digit OTP
const otp = generateOTP(6);
// Or generate with expiry
const { otp: otpCode, expiresAt } = generateOTPWithExpiry(6, 5); // 5 minutes
// Save OTP to database
await OTP.create({
email,
otp: otpCode,
expiresAt
});
// Send OTP via email/SMS (your logic)
await sendOTPEmail(email, otpCode);
res.success(null, 'OTP sent successfully');
}));
// Verify OTP
app.post('/api/verify-otp', asyncHandler(async (req, res) => {
const { email, otp } = req.body;
const otpRecord = await OTP.findOne({ email, otp });
if (!otpRecord) {
throw new AppError('Invalid OTP', 400);
}
if (isOTPExpired(otpRecord.expiresAt)) {
throw new AppError('OTP has expired', 400);
}
// OTP is valid
await OTP.deleteOne({ _id: otpRecord._id });
res.success(null, 'OTP verified successfully');
}));
// Generate alphanumeric OTP
const alphaOTP = generateAlphanumericOTP(8, {
includeUppercase: true,
includeLowercase: true,
includeNumbers: true,
excludeSimilar: true // Excludes 0, O, I, l, 1
});
// Generate secure token for password reset
app.post('/api/forgot-password', asyncHandler(async (req, res) => {
const { email } = req.body;
const user = await User.findOne({ email });
if (!user) {
throw new AppError('User not found', 404);
}
// Generate secure token
const resetToken = generateSecureToken(32);
const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
// Save token to database
user.resetToken = resetToken;
user.resetTokenExpiry = expiresAt;
await user.save();
// Send reset link via email
const resetLink = `https://yourapp.com/reset-password?token=${resetToken}`;
await sendPasswordResetEmail(email, resetLink);
res.success(null, 'Password reset link sent');
}));Complete Example
import express from 'express';
import {
asyncHandler,
errorMiddleware,
notFoundMiddleware,
AppError,
responseFormatter
} from 'express-utility-kit';
const app = express();
app.use(express.json());
app.use(responseFormatter);
// Mock database
const users = [
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' }
];
// Get all users
app.get('/api/users', asyncHandler(async (req, res) => {
res.success(users, 'Users retrieved successfully');
}));
// Get user by ID
app.get('/api/users/:id', asyncHandler(async (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
throw new AppError('User not found', 404);
}
res.success(user, 'User found');
}));
// Create user
app.post('/api/users', asyncHandler(async (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
throw new AppError('Name and email are required', 400);
}
const newUser = {
id: users.length + 1,
name,
email
};
users.push(newUser);
res.success(newUser, 'User created successfully', 201);
}));
// Handle 404
app.use(notFoundMiddleware);
// Global error handler
app.use(errorMiddleware);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});API Reference
Middleware
asyncHandler(fn)
Wraps an async function to catch errors automatically.
- Parameters:
fn(Function) - Async route handler - Returns: Express middleware function
authenticate(secret, options)
JWT authentication middleware factory.
- Parameters:
secret(String) - JWT secret keyoptions(Object) - Optional configurationheaderKey(String) - Header to read token from (default: 'authorization')tokenPrefix(String) - Token prefix (default: 'Bearer')attachTo(String) - Property name to attach decoded token (default: 'user')
- Returns: Express middleware function
errorMiddleware(err, req, res, next)
Global error handling middleware.
- Sends standardized JSON error responses
- Includes stack trace in development mode (NODE_ENV=development)
notFoundMiddleware(req, res, next)
Handles undefined routes with 404 errors.
- Should be placed after all route definitions
- Creates an AppError with 404 status
responseFormatter(req, res, next)
Adds helper methods to response object:
res.success(data, message, statusCode)- Send success responseres.error(message, statusCode)- Send error responseres.paginated(data, pagination, message, statusCode)- Send paginated response
Error Classes
AppError(message, statusCode)
Custom error class for operational errors.
- Parameters:
message(String) - Error messagestatusCode(Number) - HTTP status code
- Properties:
status- "fail" for 4xx, "error" for 5xxisOperational- Always true for AppError instances
JWT Utilities
generateToken(payload, secret, options)
Generate JWT token.
- Parameters:
payload(Object) - Data to encodesecret(String) - JWT secret keyoptions(Object) - JWT options (expiresIn, etc.)
- Returns: JWT token string
verifyToken(token, secret)
Verify JWT token.
- Parameters:
token(String) - JWT tokensecret(String) - JWT secret key
- Returns: Decoded token payload
- Throws: Error if invalid or expired
decodeToken(token)
Decode JWT without verification.
- Parameters:
token(String) - JWT token - Returns: Decoded token payload
Password Utilities
hashPassword(password, saltRounds)
Hash password using bcrypt.
- Parameters:
password(String) - Plain text passwordsaltRounds(Number) - Salt rounds (default: 10)
- Returns: Promise - Hashed password
comparePassword(password, hash)
Compare password with hash.
- Parameters:
password(String) - Plain text passwordhash(String) - Hashed password
- Returns: Promise - True if matches
Pagination Utilities
parsePaginationQuery(query, defaults)
Parse pagination from query parameters.
- Parameters:
query(Object) - Request query objectdefaults(Object) - Default values
- Returns: Object with page, limit, skip
getPaginationMeta(page, limit, total)
Calculate pagination metadata.
- Parameters:
page(Number) - Current pagelimit(Number) - Items per pagetotal(Number) - Total items
- Returns: Pagination metadata object
Validation Utilities
isValidEmail(email)
Validate email format.
- Parameters:
email(String) - Returns: Boolean
validatePassword(password, options)
Validate password strength.
- Parameters:
password(String)options(Object) - Validation rules
- Returns: Object with isValid and errors array
sanitizeString(input)
Sanitize string input.
- Parameters:
input(String) - Returns: Sanitized string
OTP Utilities
generateOTP(length)
Generate numeric OTP.
- Parameters:
length(Number) - OTP length 4-10 (default: 6) - Returns: String - Numeric OTP
generateAlphanumericOTP(length, options)
Generate alphanumeric OTP.
- Parameters:
length(Number) - OTP length 4-20 (default: 6)options(Object) - Generation optionsincludeUppercase(Boolean) - Include uppercase letters (default: true)includeLowercase(Boolean) - Include lowercase letters (default: true)includeNumbers(Boolean) - Include numbers (default: true)excludeSimilar(Boolean) - Exclude similar characters like 0,O,I,l,1 (default: true)
- Returns: String - Alphanumeric OTP
generateOTPWithExpiry(length, expiryMinutes)
Generate OTP with expiry time.
- Parameters:
length(Number) - OTP length (default: 6)expiryMinutes(Number) - Expiry time in minutes (default: 5)
- Returns: Object with otp, expiresAt, expiryMinutes
isOTPExpired(expiresAt)
Check if OTP is expired.
- Parameters:
expiresAt(Date|String) - Expiry timestamp - Returns: Boolean - True if expired
generateSecureToken(bytes)
Generate secure random token for email verification, password reset, etc.
- Parameters:
bytes(Number) - Number of bytes (default: 32) - Returns: String - Hex string token
Environment Variables
NODE_ENV- Set to "development" to include stack traces in error responses
License
MIT
