HTTP Server framework for Node.js with built-in authentication, validation, caching, logging, and plugin architecture
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Input validation system with support for Joi and other validation libraries for request payload, query parameters, and headers in @hapi/hapi.
Configure validation for different parts of the HTTP request including payload, query parameters, path parameters, and headers.
interface ValidationOptions {
/** Validate request headers */
headers?: object | boolean;
/** Validate path parameters */
params?: object | boolean;
/** Validate query parameters */
query?: object | boolean;
/** Validate request payload */
payload?: object | boolean;
/** Validate cookie state */
state?: object | boolean;
/** Action to take when validation fails */
failAction?: ValidationFailAction;
/** Validation library options */
options?: object;
/** Custom error messages */
errorFields?: object;
}
type ValidationFailAction = 'error' | 'log' | 'ignore' | Function;Validate request payload data with comprehensive schema definitions.
// Payload validation using Joi schema
const Joi = require('joi');
interface PayloadValidationSchema {
/** Required fields validation */
[key: string]: any;
}Usage Examples:
const Joi = require('joi');
// Basic payload validation
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
age: Joi.number().integer().min(18).max(120).optional(),
address: Joi.object({
street: Joi.string().required(),
city: Joi.string().required(),
zipCode: Joi.string().pattern(/^\d{5}$/).required()
}).optional()
})
}
},
handler: async (request, h) => {
const user = await database.createUser(request.payload);
return h.response(user).code(201);
}
});
// Complex payload validation
server.route({
method: 'POST',
path: '/orders',
options: {
validate: {
payload: Joi.object({
customerId: Joi.string().uuid().required(),
items: Joi.array().items(
Joi.object({
productId: Joi.string().uuid().required(),
quantity: Joi.number().integer().min(1).required(),
price: Joi.number().precision(2).positive().required()
})
).min(1).required(),
shippingAddress: Joi.object({
name: Joi.string().required(),
street: Joi.string().required(),
city: Joi.string().required(),
state: Joi.string().length(2).required(),
zipCode: Joi.string().pattern(/^\d{5}(-\d{4})?$/).required()
}).required(),
paymentMethod: Joi.string().valid('credit_card', 'paypal', 'bank_transfer').required()
})
}
},
handler: async (request, h) => {
const order = await orderService.createOrder(request.payload);
return h.response(order).code(201);
}
});Validate URL query string parameters with type conversion and constraints.
Usage Examples:
// Query parameter validation
server.route({
method: 'GET',
path: '/users',
options: {
validate: {
query: Joi.object({
page: Joi.number().integer().min(1).default(1),
limit: Joi.number().integer().min(1).max(100).default(10),
sort: Joi.string().valid('name', 'email', 'created_at').default('created_at'),
order: Joi.string().valid('asc', 'desc').default('desc'),
search: Joi.string().min(2).max(50).optional(),
active: Joi.boolean().default(true),
role: Joi.array().items(Joi.string().valid('admin', 'user', 'moderator')).single().optional()
})
}
},
handler: async (request, h) => {
const { page, limit, sort, order, search, active, role } = request.query;
const users = await database.getUsers({
page,
limit,
sort,
order,
search,
active,
role
});
return users;
}
});
// Advanced query validation with custom logic
server.route({
method: 'GET',
path: '/analytics',
options: {
validate: {
query: Joi.object({
startDate: Joi.date().iso().required(),
endDate: Joi.date().iso().min(Joi.ref('startDate')).required(),
metrics: Joi.array().items(
Joi.string().valid('views', 'clicks', 'conversions', 'revenue')
).min(1).required(),
groupBy: Joi.string().valid('day', 'week', 'month').default('day'),
timezone: Joi.string().default('UTC')
}).custom((value, helpers) => {
const { startDate, endDate } = value;
const daysDiff = (new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24);
if (daysDiff > 365) {
return helpers.error('custom.dateRange', { maxDays: 365 });
}
return value;
})
}
},
handler: async (request, h) => {
const analytics = await analyticsService.getMetrics(request.query);
return analytics;
}
});Validate URL path parameters with type checking and format validation.
Usage Examples:
// Path parameter validation
server.route({
method: 'GET',
path: '/users/{id}',
options: {
validate: {
params: Joi.object({
id: Joi.string().uuid().required()
})
}
},
handler: async (request, h) => {
const user = await database.getUser(request.params.id);
if (!user) {
return h.response({ error: 'User not found' }).code(404);
}
return user;
}
});
// Multiple path parameters
server.route({
method: 'GET',
path: '/users/{userId}/posts/{postId}',
options: {
validate: {
params: Joi.object({
userId: Joi.string().uuid().required(),
postId: Joi.alternatives().try(
Joi.string().uuid(),
Joi.string().alphanum().min(5).max(20)
).required()
})
}
},
handler: async (request, h) => {
const { userId, postId } = request.params;
const post = await database.getUserPost(userId, postId);
return post;
}
});
// Path parameter with custom validation
server.route({
method: 'GET',
path: '/files/{path*}',
options: {
validate: {
params: Joi.object({
path: Joi.string().custom((value, helpers) => {
// Prevent directory traversal
if (value.includes('..') || value.includes('~')) {
return helpers.error('custom.invalidPath');
}
return value;
}).required()
})
}
},
handler: async (request, h) => {
const filePath = path.join('./uploads', request.params.path);
return h.file(filePath);
}
});Validate HTTP headers including custom headers and standard headers.
Usage Examples:
// Header validation
server.route({
method: 'POST',
path: '/api/data',
options: {
validate: {
headers: Joi.object({
'content-type': Joi.string().valid('application/json').required(),
'x-api-key': Joi.string().alphanum().length(32).required(),
'user-agent': Joi.string().required(),
'x-request-id': Joi.string().uuid().optional(),
'x-client-version': Joi.string().pattern(/^\d+\.\d+\.\d+$/).optional()
}).unknown(true) // Allow other headers
}
},
handler: async (request, h) => {
const apiKey = request.headers['x-api-key'];
const requestId = request.headers['x-request-id'];
// Process with validated headers
const result = await processData(request.payload, { apiKey, requestId });
return result;
}
});Validate cookie values and state information.
Usage Examples:
// State validation
server.route({
method: 'GET',
path: '/dashboard',
options: {
validate: {
state: Joi.object({
session: Joi.object({
userId: Joi.string().uuid().required(),
role: Joi.string().valid('admin', 'user').required(),
expires: Joi.date().timestamp().required()
}).required(),
preferences: Joi.object({
theme: Joi.string().valid('light', 'dark').default('light'),
language: Joi.string().length(2).default('en')
}).optional()
})
}
},
handler: async (request, h) => {
const { session, preferences } = request.state;
const dashboard = await getDashboard(session.userId, preferences);
return dashboard;
}
});Create custom validation logic for complex scenarios.
Usage Examples:
// Custom validation function
const validateBusinessRules = (value, helpers) => {
const { startDate, endDate, eventType } = value;
// Business rule: Premium events require at least 30 days notice
if (eventType === 'premium') {
const daysNotice = (new Date(startDate) - new Date()) / (1000 * 60 * 60 * 24);
if (daysNotice < 30) {
return helpers.error('custom.premiumEventNotice', { minDays: 30 });
}
}
// Business rule: Events cannot span more than 7 days
const eventDuration = (new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24);
if (eventDuration > 7) {
return helpers.error('custom.eventTooLong', { maxDays: 7 });
}
return value;
};
server.route({
method: 'POST',
path: '/events',
options: {
validate: {
payload: Joi.object({
title: Joi.string().min(5).max(100).required(),
description: Joi.string().max(1000).optional(),
startDate: Joi.date().iso().min('now').required(),
endDate: Joi.date().iso().min(Joi.ref('startDate')).required(),
eventType: Joi.string().valid('basic', 'premium').required(),
attendeeLimit: Joi.number().integer().min(1).max(1000).required()
}).custom(validateBusinessRules)
}
},
handler: async (request, h) => {
const event = await eventService.createEvent(request.payload);
return h.response(event).code(201);
}
});Handle validation errors with custom error messages and responses.
interface ValidationFailActionFunction {
(request: Request, h: ResponseToolkit, error: Error): any;
}Usage Examples:
// Custom validation error handler
const customValidationHandler = (request, h, error) => {
const details = error.details.map(detail => ({
field: detail.path.join('.'),
message: detail.message,
value: detail.context?.value
}));
return h.response({
error: 'Validation Failed',
statusCode: 400,
details: details,
timestamp: new Date().toISOString(),
path: request.path
}).code(400).takeover();
};
server.route({
method: 'POST',
path: '/users',
options: {
validate: {
payload: Joi.object({
name: Joi.string().min(2).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).required()
}),
failAction: customValidationHandler
}
},
handler: async (request, h) => {
const user = await userService.createUser(request.payload);
return h.response(user).code(201);
}
});
// Different fail actions per validation type
server.route({
method: 'GET',
path: '/search',
options: {
validate: {
query: Joi.object({
q: Joi.string().min(2).required(),
category: Joi.string().valid('all', 'posts', 'users').default('all')
}),
failAction: 'log' // Just log query validation errors, don't fail request
}
},
handler: async (request, h) => {
// Will use default values for invalid query params
const results = await searchService.search(request.query);
return results;
}
});Set global validation settings and custom validators.
/**
* Set validation engine for the server
* @param validator - Validation library (Joi, etc.)
*/
validator(validator: object): void;
/**
* Set validation rules processor
* @param processor - Rules processing function
* @param options - Processor options
*/
rules(processor: Function, options?: object): void;Usage Examples:
const Joi = require('joi');
// Set global validation engine
server.validator(Joi);
// Custom validation rules
server.rules((schema, options) => {
// Custom preprocessing of validation schemas
return schema.options({
allowUnknown: options.allowUnknown !== false,
stripUnknown: true,
abortEarly: false
});
});
// Global validation defaults in server options
const server = Hapi.server({
port: 3000,
routes: {
validate: {
options: {
abortEarly: false,
stripUnknown: true
},
failAction: (request, h, error) => {
console.log('Validation error:', error.details);
throw error;
}
}
}
});interface ValidationError extends Error {
/** Validation error details */
details: ValidationErrorDetail[];
/** Whether error is a validation error */
isJoi: boolean;
}
interface ValidationErrorDetail {
/** Error message */
message: string;
/** Field path that failed validation */
path: (string | number)[];
/** Validation error type */
type: string;
/** Validation context */
context?: {
value?: any;
key?: string;
label?: string;
[key: string]: any;
};
}
interface ValidateRouteOptions {
/** Header validation schema */
headers?: object | boolean;
/** Parameter validation schema */
params?: object | boolean;
/** Query validation schema */
query?: object | boolean;
/** Payload validation schema */
payload?: object | boolean;
/** State validation schema */
state?: object | boolean;
/** Validation failure action */
failAction?: ValidationFailAction;
/** Validation options */
options?: object;
}