halify-response
v1.1.3
Published
Helper utilities for HAL-style API responses in Express.js
Downloads
19
Maintainers
Readme
halify-response
📦 A comprehensive utility library for building consistent, HAL (Hypertext Application Language)-compliant JSON responses in your Express.js or REST APIs.
Brought to you by Shivank Singh.
✨ Features
- 🔗 HAL-compliant responses with
_links,_embedded, and metadata - ✅ Comprehensive response types for all API operations
- 🛡️ Advanced HTTP error handling with standardized error codes
- 🎯 HTTP success responses with semantic status codes
- 📊 Collection operations with pagination, search, and filtering
- 📦 Resource operations with CRUD support
- 🔍 Bulk operations support with detailed results
- 🏥 Health check and status endpoints
- 💬 Developer-friendly with TypeScript support
- 🚀 Lightweight and fast with zero dependencies
- 🎯 Easy integration into any Node.js project
- 🔄 Functional API with direct imports
📦 Installation
npm install halify-responseNote: This library is written in TypeScript and provides full type definitions out of the box.
🚀 Quick Start
import {
// HTTP Success Responses
ok, created, updated, deleted,
// HTTP Error Responses
badRequest, notFound, validationError,
// Resource Operations
createResponse, updateResponse, detailResponse,
// Collection Operations
listResponse, paginatedResponse, searchResponse
} from 'halify-response';
// Success response - 200 OK
const successRes = ok({ endpoint: '/api/users/123' });
// Created response - 201 Created
const createdRes = created({ endpoint: '/api/users/123' });
// Validation error - 422
const validationRes = validationError(
{ email: 'Email is required', password: 'Password too short' },
{ endpoint: '/api/users' }
);
// Resource creation
const resourceRes = createResponse(
'user',
{ id: 123, name: 'John Doe' },
'/api/users/123'
);📚 HTTP Status Code Responses
🎉 Success Responses
The library provides semantic functions for HTTP success responses:
200 - OK
import { ok } from 'halify-response';
const response = ok({
endpoint: '/api/users/123',
customDetail: 'User retrieved successfully'
});Output:
{
"_links": { "self": { "href": "/api/users/123" } },
"data": {
"status": 200,
"title": "OK",
"detail": "User retrieved successfully",
"code": "SUCCESS"
}
}200 - Updated
import { updated } from 'halify-response';
const response = updated({ endpoint: '/api/users/123' });Output:
{
"_links": { "self": { "href": "/api/users/123" } },
"data": {
"status": 200,
"title": "Updated",
"detail": "The resource has been updated successfully.",
"code": "RESOURCE_UPDATED"
}
}201 - Created
import { created } from 'halify-response';
const response = created({ endpoint: '/api/users/123' });Output:
{
"_links": {
"self": { "href": "/api/users/123" },
"location": { "href": "/api/resource", "title": "Resource Location" }
},
"data": {
"status": 201,
"title": "Created",
"detail": "The resource has been created successfully.",
"code": "RESOURCE_CREATED"
}
}202 - Accepted
import { accepted } from 'halify-response';
const response = accepted({ endpoint: '/api/jobs/123' });204 - No Content
import { noContent } from 'halify-response';
const response = noContent({ endpoint: '/api/users/123' });204 - Deleted
import { deleted } from 'halify-response';
const response = deleted({ endpoint: '/api/users/123' });Output:
{
"_links": { "self": { "href": "/api/users/123" } },
"data": {
"status": 204,
"title": "Deleted",
"detail": "The resource has been deleted successfully.",
"code": "RESOURCE_DELETED"
}
}206 - Partial Content
import { partialContent } from 'halify-response';
const response = partialContent({ endpoint: '/api/files/123' });🚨 Error Responses
The library provides comprehensive HTTP error responses:
400 - Bad Request
import { badRequest } from 'halify-response';
const response = badRequest({
endpoint: '/api/users',
customDetail: 'Missing required fields'
});Output:
{
"_links": { "self": { "href": "/api/users" } },
"error": {
"status": 400,
"title": "Bad Request",
"detail": "Missing required fields",
"code": "BAD_REQUEST"
}
}401 - Unauthorized
import { unauthorized } from 'halify-response';
const response = unauthorized({ endpoint: '/api/protected' });Output:
{
"_links": {
"self": { "href": "/api/protected" },
"login": { "href": "/auth/login", "title": "Login" }
},
"error": {
"status": 401,
"title": "Unauthorized",
"detail": "Authentication is required to access this resource.",
"code": "UNAUTHORIZED"
}
}403 - Forbidden
import { forbidden } from 'halify-response';
const response = forbidden({ endpoint: '/api/admin' });404 - Not Found
import { notFound } from 'halify-response';
const response = notFound({ endpoint: '/api/users/999' });405 - Method Not Allowed
import { methodNotAllowed } from 'halify-response';
const response = methodNotAllowed({ endpoint: '/api/users' });409 - Conflict
import { conflict } from 'halify-response';
const response = conflict({ endpoint: '/api/users' });422 - Validation Error
import { validationError } from 'halify-response';
const response = validationError(
{
email: 'Email is required',
password: 'Password must be at least 8 characters'
},
{ endpoint: '/api/users' }
);Output:
{
"_links": { "self": { "href": "/api/users" } },
"error": {
"status": 422,
"title": "Validation Error",
"detail": "One or more validation errors occurred.",
"code": "VALIDATION_ERROR",
"fields": {
"email": "Email is required",
"password": "Password must be at least 8 characters"
}
}
}429 - Too Many Requests
import { tooManyRequests } from 'halify-response';
const response = tooManyRequests({ endpoint: '/api/users' });500 - Internal Server Error
import { internalServerError } from 'halify-response';
const response = internalServerError({ endpoint: '/api/users' });Output:
{
"_links": {
"self": { "href": "/api/users" },
"support": { "href": "/support", "title": "API Support" }
},
"error": {
"status": 500,
"title": "Internal Server Error",
"detail": "An unexpected error occurred on the server. Please try again later.",
"code": "SERVER_ERROR"
}
}503 - Service Unavailable
import { serviceUnavailable } from 'halify-response';
const response = serviceUnavailable({ endpoint: '/api/users' });🏗️ Resource Operations
Generic Resource Response
import { resourceResponse } from 'halify-response';
const response = resourceResponse(
'user',
{ id: 123, name: 'John Doe' },
'/api/users/123',
{
message: 'Custom operation completed',
additionalLinks: {
profile: { href: '/api/users/123/profile', title: 'User Profile' }
}
}
);Create Response
import { createResponse } from 'halify-response';
const response = createResponse(
'user',
{ id: 123, name: 'John Doe', email: '[email protected]' },
'/api/users/123'
);Output:
{
"success": true,
"_links": { "self": { "href": "/api/users/123" } },
"name": "user",
"message": "Resource created successfully",
"attributes": {
"id": 123,
"name": "John Doe",
"email": "[email protected]"
}
}Update Response
import { updateResponse } from 'halify-response';
const response = updateResponse(
'user',
{ id: 123, name: 'John Updated', updatedAt: '2024-01-01' },
'/api/users/123'
);Detail Response
import { detailResponse } from 'halify-response';
const response = detailResponse(
'user',
{ id: 123, name: 'John Doe', profile: { bio: 'Developer' } },
'/api/users/123'
);📦 Collection Operations
Generic Collection Response
import { collectionResponse } from 'halify-response';
const response = collectionResponse(
'users',
userList,
'/api/users',
{
total: 100,
metadata: { lastUpdated: '2024-01-01' },
links: {
next: { href: '/api/users?page=2', title: 'Next Page' }
}
}
);List Response
import { listResponse } from 'halify-response';
const response = listResponse(
'users',
[
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
],
'/api/users',
{
total: 2,
metadata: {
lastUpdated: '2024-01-01'
}
}
);Output:
{
"success": true,
"_links": { "self": { "href": "/api/users" } },
"name": "users",
"_embedded": {
"data": [
{ "id": 1, "name": "John" },
{ "id": 2, "name": "Jane" }
]
},
"total": 2,
"metadata": { "lastUpdated": "2024-01-01" }
}Paginated Response
import { paginatedResponse } from 'halify-response';
const response = paginatedResponse(
'users', // Module name
userList, // Data array
'/api/users?page=2', // Current page URL
'/api/users?page=1', // First page URL
'/api/users?page=10', // Last page URL
2, // Current page
10, // Total pages
20, // Items per page
200 // Total items
);Output:
{
"success": true,
"name": "users",
"_embedded": { "data": [...] },
"_links": {
"self": { "href": "/api/users?page=2" },
"first": { "href": "/api/users?page=1" },
"last": { "href": "/api/users?page=10" }
},
"metadata": {
"page": 2,
"pageCount": 10,
"pageSize": 20,
"total": 200
}
}Search Response
import { searchResponse } from 'halify-response';
const response = searchResponse(
'users',
searchResults,
'/api/users/search',
{ query: 'john', filters: { status: 'active' } },
{
total: 15,
page: 1,
pageSize: 10,
query: 'john',
filters: ['status:active']
}
);Output:
{
"success": true,
"_links": { "self": { "href": "/api/users/search" } },
"name": "users",
"_embedded": { "results": [...] },
"metadata": {
"searchParams": { "query": "john", "filters": { "status": "active" } },
"query": "john",
"filters": ["status:active"],
"page": 1,
"pageSize": 10
}
}🔧 Specialized Operations
Bulk Response
import { bulkResponse } from 'halify-response';
const response = bulkResponse(
'users',
'create',
[
{ id: 1, status: 'success' },
{ id: 2, status: 'failed', error: 'Email exists' }
],
'/api/users/bulk',
{
successCount: 1,
failureCount: 1,
errors: [
{ id: 2, field: 'email', message: 'Email already exists' }
]
}
);Output:
{
"success": true,
"_links": { "self": { "href": "/api/users/bulk" } },
"name": "users",
"operation": "create",
"results": [
{ "id": 1, "status": "success" },
{ "id": 2, "status": "failed", "error": "Email exists" }
],
"successCount": 1,
"failureCount": 1,
"errors": [
{ "id": 2, "field": "email", "message": "Email already exists" }
]
}Status Response
import { statusResponse } from 'halify-response';
const response = statusResponse(
'user-service',
'healthy',
'/api/health',
{
version: '1.0.0',
uptime: 3600,
dependencies: {
database: 'connected',
redis: 'connected'
},
details: {
memory: '45%',
cpu: '12%'
}
}
);Output:
{
"success": true,
"_links": { "self": { "href": "/api/health" } },
"name": "user-service",
"status": "healthy",
"timestamp": "2024-01-01T12:00:00.000Z",
"version": "1.0.0",
"uptime": 3600,
"dependencies": {
"database": "connected",
"redis": "connected"
},
"details": {
"memory": "45%",
"cpu": "12%"
}
}🔧 Advanced Usage
Express.js Integration
import express from 'express';
import {
ok, created, notFound, internalServerError,
detailResponse, createResponse, paginatedResponse
} from 'halify-response';
const app = express();
// GET single user
app.get('/api/users/:id', async (req, res) => {
try {
const user = await getUserById(req.params.id);
if (!user) {
return res.status(404).json(notFound({ endpoint: req.originalUrl }));
}
const response = detailResponse('user', user, req.originalUrl);
res.json(response);
} catch (error) {
const errorRes = internalServerError({ endpoint: req.originalUrl });
res.status(500).json(errorRes);
}
});
// POST create user
app.post('/api/users', async (req, res) => {
try {
const newUser = await createUser(req.body);
const response = createResponse('user', newUser, `/api/users/${newUser.id}`);
res.status(201).json(response);
} catch (error) {
const errorRes = internalServerError({ endpoint: req.originalUrl });
res.status(500).json(errorRes);
}
});
// GET users with pagination
app.get('/api/users', async (req, res) => {
try {
const { page = 1, limit = 20 } = req.query;
const { users, total, totalPages } = await getUsers(page, limit);
const response = paginatedResponse(
'users',
users,
req.originalUrl,
`/api/users?page=1&limit=${limit}`,
`/api/users?page=${totalPages}&limit=${limit}`,
parseInt(page),
totalPages,
parseInt(limit),
total
);
res.json(response);
} catch (error) {
const errorRes = internalServerError({ endpoint: req.originalUrl });
res.status(500).json(errorRes);
}
});Error Middleware
import {
badRequest, unauthorized, forbidden, notFound,
validationError, internalServerError
} from 'halify-response';
// Global error handler
app.use((error, req, res, next) => {
let response;
let statusCode = 500;
switch (error.name) {
case 'ValidationError':
response = validationError(error.fields, { endpoint: req.originalUrl });
statusCode = 422;
break;
case 'UnauthorizedError':
response = unauthorized({ endpoint: req.originalUrl });
statusCode = 401;
break;
case 'ForbiddenError':
response = forbidden({ endpoint: req.originalUrl });
statusCode = 403;
break;
case 'NotFoundError':
response = notFound({ endpoint: req.originalUrl });
statusCode = 404;
break;
default:
response = internalServerError({
endpoint: req.originalUrl,
customDetail: error.message
});
statusCode = 500;
}
res.status(statusCode).json(response);
});Custom Response Options
import { ok, created, resourceResponse } from 'halify-response';
// Custom success response with additional links
const response = ok({
endpoint: '/api/users/123',
customDetail: 'User profile updated successfully',
additionalLinks: {
profile: { href: '/api/users/123/profile', title: 'User Profile' },
settings: { href: '/api/users/123/settings', title: 'User Settings' }
}
});
// Custom resource response with metadata
const resourceRes = resourceResponse(
'user',
userData,
'/api/users/123',
{
message: 'User data retrieved with preferences',
additionalLinks: {
preferences: { href: '/api/users/123/preferences' }
}
}
);🔍 Utility Functions
Error Code Validation
import {
isSupportedStatusCode,
getErrorCodes,
isSupportedSuccessStatusCode,
getSuccessCodes
} from 'halify-response';
// Check if error code is supported
if (isSupportedStatusCode(422)) {
console.log('422 is supported for error responses');
}
// Get all available error codes
const errorCodes = getErrorCodes();
console.log(errorCodes); // { 400: { title: "Bad Request", ... }, ... }
// Check if success code is supported
if (isSupportedSuccessStatusCode(201)) {
console.log('201 is supported for success responses');
}
// Get all available success codes
const successCodes = getSuccessCodes();
console.log(successCodes); // { 200: { title: "OK", ... }, ... }Generic Response Creation
import { createErrorResponse, createSuccessResponse } from 'halify-response';
// Create custom error response
const customError = createErrorResponse(409, {
endpoint: '/api/users',
customDetail: 'User with this email already exists',
additionalLinks: {
'forgot-password': { href: '/auth/forgot-password', title: 'Forgot Password' }
}
});
// Create custom success response
const customSuccess = createSuccessResponse(202, {
endpoint: '/api/jobs',
customDetail: 'Job queued successfully',
additionalLinks: {
status: { href: '/api/jobs/123/status', title: 'Job Status' }
}
});📝 TypeScript Support
This library is built with TypeScript and provides comprehensive type definitions:
import {
// Response interfaces
ResourceResponse, CollectionResponse,
// Option interfaces
ResourceResponseOptions, CollectionResponseOptions,
// HTTP response interfaces
SuccessResponse, ErrorResponse,
// Functions
createResponse, listResponse, ok, badRequest
} from 'halify-response';
interface User {
id: number;
name: string;
email: string;
}
// Fully typed responses
const userResponse: ResourceResponse = createResponse(
'user',
{ id: 1, name: 'John', email: '[email protected]' },
'/api/users/1'
);
const usersResponse: CollectionResponse = listResponse(
'users',
[{ id: 1, name: 'John' }],
'/api/users',
{ total: 1 }
);
const successResponse: SuccessResponse = ok({ endpoint: '/api/users' });
const errorResponse: ErrorResponse = badRequest({ endpoint: '/api/users' });Available Types
Response Interfaces
ResourceResponse- Resource operation responsesCollectionResponse- Collection operation responsesSuccessResponse- HTTP success responsesErrorResponse- HTTP error responsesBulkResponse- Bulk operation responsesStatusResponse- Status/health check responses
Option Interfaces
ResourceResponseOptions- Options for resource responsesCollectionResponseOptions- Options for collection responsesPaginatedCollectionOptions- Options for paginated collectionsSearchCollectionOptions- Options for search collectionsBulkResponseOptions- Options for bulk operationsStatusResponseOptions- Options for status responses
Validation Types
ValidationError- Validation error structureValidationErrorResponseOptions- Validation error options
🏗️ Build and Development
# Install dependencies
npm install
# Type check
npm run type-check
# Build the library
npm run build
# Publish to npm
npm publishRequirements:
- Node.js >= 12.0.0
- TypeScript >= 4.9.5
📋 API Reference
HTTP Success Functions
ok(options?)- 200 OKupdated(options?)- 200 Updatedcreated(options?)- 201 Createdaccepted(options?)- 202 AcceptednoContent(options?)- 204 No Contentdeleted(options?)- 204 DeletedpartialContent(options?)- 206 Partial Content
HTTP Error Functions
badRequest(options?)- 400 Bad Requestunauthorized(options?)- 401 Unauthorizedforbidden(options?)- 403 ForbiddennotFound(options?)- 404 Not FoundmethodNotAllowed(options?)- 405 Method Not Allowedconflict(options?)- 409 ConflictvalidationError(fields, options?)- 422 Validation ErrortooManyRequests(options?)- 429 Too Many RequestsinternalServerError(options?)- 500 Internal Server ErrorserviceUnavailable(options?)- 503 Service Unavailable
Resource Functions
resourceResponse(name, data, url, options?)- Generic resource responsecreateResponse(name, data, url, options?)- Create resourceupdateResponse(name, data, url, options?)- Update resourcedetailResponse(name, data, url, options?)- Get resource details
Collection Functions
collectionResponse(name, items, url, options?)- Generic collectionlistResponse(name, items, url, options?)- Simple listpaginatedResponse(name, items, url, first, last, page, count, size, total)- Paginated listsearchResponse(name, results, url, params, options?)- Search results
Specialized Functions
bulkResponse(name, operation, results, url, options?)- Bulk operationsstatusResponse(service, status, url, options?)- Health/status check
Utility Functions
createErrorResponse(code, options?)- Custom error responsecreateSuccessResponse(code, options?)- Custom success responsegetErrorCodes()- Get all error code definitionsgetSuccessCodes()- Get all success code definitionsisSupportedStatusCode(code)- Check if error code is supportedisSupportedSuccessStatusCode(code)- Check if success code is supported
🤝 Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- HAL Specification for the response format
- Express.js community for inspiration
- All contributors and users of this library
Made with ❤️ by Shivank Singh
