A utility library designed to simplify and enhance express.js applications.
npm install exstack


> A lightweight, fast, and flexible utility library for Express.js โ designed to simplify development with async-safe handlers, built-in validation, standardized responses, and clean error handling.
- ๐ Features
- ๐ฆ Installation
- โก Quick Start
- ๐ง Core Concepts
- ๐ช Handler
- ๐ฆ ApiRes
- ๐จ HttpError
- โ
HttpStatus
- ๐ Zod Validator
- ๐งฑ Middleware
- ๐ Serve
- ๐ค Contributing
- ๐ License
- ๐ง Async-Friendly Handlers โ Simplify async route logic with automatic error propagation and standardized responses.
- ๐งฉ Standardized Responses โ Use ApiRes and HttpError for clean, consistent, and typed responses.
- โ
Zod-Based Validation โ Validate request body, query, and params seamlessly.
- ๐งฑ Essential Middleware โ Includes errorHandler, notFound, and poweredBy out of the box.
- ๐งพ HttpStatus Enum โ Access standardized HTTP status codes and names with clear constants.
- ๐ Graceful Shutdown โ Built-in server with graceful shutdown support for production deployments.
``bash`
npm install exstack
`typescript
import * as z from 'zod';
import express from 'express';
import {validator} from 'exstack/zod';
import {handler, errorHandler, notFound, ApiRes} from 'exstack';
import {serve} from 'exstack/serve';
const app = express();
// Middleware
app.use(express.json());
// Validation schema
const schema = z.object({
name: z.string(),
});
// Define routes with handler
app.get(
'/ping',
handler(() => 'pong'),
);
app.post(
'/users',
validator.body(schema),
handler(req => {
const user = req.valid
return ApiRes.created(user, 'User created successfully');
}),
);
// Error middleware
app.use(notFound('*splat'));
app.use(errorHandler(process.env.NODE_ENV === 'development'));
// Start server with graceful shutdown
serve(app, {port: 3000});
`
The handler utility wraps route logic to automatically catch errors and send responses cleanly.
`typescript
import {handler, ApiRes} from 'exstack';
// Without handler (classic)
app.get('/user/:id', async (req, res, next) => {
try {
const user = await getUserById(req.params.id);
res.status(200).json(user);
} catch (err) {
next(err);
}
});
// With handler (cleaner)
app.get(
'/user/:id',
handler(async req => {
const user = await getUserById(req.params.id);
return ApiRes.ok(user, 'User fetched successfully');
}),
);
`
ApiRes standardizes and simplifies success response formatting.
`typescript
app.get(
'/user',
handler(() => ApiRes.ok({name: 'John Doe'}, 'User found')),
);
app.post(
'/user',
handler(req => {
const newUser = createUser(req.body);
return ApiRes.created(newUser, 'User created');
}),
);
// Chainable example
app.post(
'/user',
handler(req => {
const newUser = createUser(req.body);
return ApiRes.status(200).msg('User created').data(newUser);
}),
);
`
Available Methods:
| Method | Description |
| --------------------------------------- | -------------------------- |
| ApiRes.ok(data, message) | 200 OK response |ApiRes.created(data, message)
| | 201 Created response |ApiRes.paginated(data, meta, message)
| | Paginated success response |.status(code)
| | Chainable status setter |.msg(message)
| | Chainable message setter |.data(data)
| | Chainable data setter |
The HttpError class provides a consistent and structured way to handle HTTP errors.
`typescript
import {HttpError, HttpStatus} from 'exstack';
app.get(
'*',
handler((req, res) =>
new HttpError(HttpStatus.NOT_FOUND, {
message: 'Not Found',
}).toJson(res),
),
);
app.post(
'/example/:id',
handler(req => {
if (!req.params.id) throw new BadRequestError('Id is required');
}),
);
`
Extended Options:
`typescript`
const err = new HttpError(400, {
message: 'Validation Error',
data: {
username: 'Username is required',
password: 'Password is required',
},
cause: new Error('Invalid input'),
});
> _If no custom name is provided, HttpError automatically assigns one based on the status code._
#### Common Errors:
- BadRequestErrorUnauthorizedError
- NotFoundError
- ConflictError
- ForbiddenError
- PaymentRequiredError
- NotImplementedError
- InternalServerError
- ContentTooLargeError
-
#### HttpError.isHttpError(value)
Check whether a value is an instance of HttpError.
`typescript`
// If it is an HttpError, send a JSON response with the error details
if (HttpError.isHttpError(err)) return err.toJson(res);
else
// If it's not an HttpError, pass it to the next middleware for further handling
next(err);
#### Custom Error Handler Example
`typescript`
export const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
// Handle known HttpError instances
if (HttpError.isHttpError(err)) {
// Log the cause if it exists
if (err.options.cause) console.error('HttpError Cause:', err.options.cause);
return err.toJson(res);
}
// Write unknown errors if a write function is provided
console.error('Unknown Error:', err);
// Standardized error response for unknown exceptions
const unknown = {
status: HttpStatus.INTERNAL_SERVER_ERROR,
error: 'InternalServerError',
message: isDev ? err.message || 'Unexpected error' : 'Something went wrong',
stack: isDev ? err.stack : undefined,
};
res.status(unknown.status).json(unknown);
};
HttpStatus provides readable constants for all standard HTTP status codes.
`typescript
import {HttpStatus} from 'exstack';
// Example: Basic usage in a route
app.get('/status-example', (req, res) => {
res.status(HttpStatus.OK).json({message: 'All good!'});
});
// Example: Custom error handling middleware
app.use((req, res) => {
res.status(HttpStatus.NOT_FOUND).json({
error: 'Resource not found',
});
});
// Example: Response with a 201 Created status
app.post('/create', (req, res) => {
const resource = createResource(req.body);
res.status(HttpStatus.CREATED).json({
message: 'Resource created successfully',
data: resource,
});
});
`
- 2xx: Success
- HttpStatus.OK: 200 โ Request succeeded.HttpStatus.CREATED
- : 201 โ Resource created.HttpStatus.ACCEPTED
- : 202 โ Request accepted for processing.HttpStatus.NO_CONTENT
- : 204 โ No content to send.
- and more ....
- 3xx: Redirection
- HttpStatus.MOVED_PERMANENTLY: 301 โ Resource moved permanently.HttpStatus.FOUND
- : 302 โ Resource found at another URI.HttpStatus.NOT_MODIFIED
- : 304 โ Resource not modified.
- and more ....
- 4xx: Client Error
- HttpStatus.BAD_REQUEST: 400 โ Bad request.HttpStatus.UNAUTHORIZED
- : 401 โ Authentication required.HttpStatus.FORBIDDEN
- : 403 โ Access forbidden.HttpStatus.NOT_FOUND
- : 404 โ Resource not found.
- and more ....
- 5xx: Server Error
- HttpStatus.INTERNAL_SERVER_ERROR: 500 โ Internal server error.HttpStatus.NOT_IMPLEMENTED
- : 501 โ Not implemented.HttpStatus.SERVICE_UNAVAILABLE
- : 503 โ Service unavailable.
- and more ....
The validator middleware provides an easy way to validate incoming requests using Zod schemas. It can validate the request body, query, params and all.
`bash`node runtime
npm install zodbun runtime
bun install zod
`typescript
import * as z from 'zod';
import {validator} from 'exstack/zod';
const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
app.post(
'/users',
validator.body(createUserSchema),
handler(req => {
const body = req.valid('body');
// body is guaranteed to match the schema
return ApiRes.created(body, 'User created');
}),
);
`
`typescript
app.post(
'/users',
validator.body(createUserSchema),
handler(req => {
// Option 1: Automatically inferred from schema
const user = req.valid('body');
// ^? { name: string; email: string }
// Option 2: Explicitly infer from schema
const user2 = req.valid
// ^? z.infer
// Option 3: Manually provide a type if needed
const user3 = req.valid<{name: string; email: string}>('body');
// ^? { name: string; email: string }
return ApiRes.created(user, 'User created successfully');
}),
);
// Multi-part Validation Example
const multiSchema = {
body: z.object({name: z.string()}),
query: z.object({page: z.string().optional()}),
params: z.object({id: z.string().uuid()}),
};
app.put(
'/users/:id',
validator.all(multiSchema),
handler(req => {
const result = req.valid('all');
return ApiRes.ok(result);
}),
);
`
#### ๐ ๏ธ errorHandler
Handles HttpError and unknown exceptions with standardized JSON output.
`typescript
import {errorHandler} from 'exstack';
app.use(errorHandler(process.env.NODE_ENV === 'development'));
`
#### ๐ซ notFound
Automatically throws a 404 for unmatched routes.
`typescript`
app.use(notFound('*splat'));
#### โ๏ธ poweredBy
Adds an X-Powered-By header to responses.
`typescript`
app.use(poweredBy('Exstack'));
Start your Express app or HTTP/HTTPS server with built-in graceful shutdown support.
`typescript
import express from 'express';
import {serve} from 'exstack/serve';
const app = express();
// ... configure routes
serve(app, {port: 3000});
`
Features:
- โ
Graceful shutdown on SIGINT/SIGTERM
- โ
Countdown timer with force close option
- โ
Support for HTTP, HTTPS, and HTTP2 servers
- โ
Auto-disabled in CI/TEST environments
Options:
`typescript
serve(app, {
port: 3000, // Default: 3000 or PORT env
hostname: 'localhost', // Default: 'localhost' or HOST env
silent: false, // Suppress startup logs
gracefulShutdown: true, // true | false | number (timeout in seconds)
});
// Examples:
serve(app, { gracefulShutdown: 10 }); // 10 second timeout
serve(app, { gracefulShutdown: 0 }); // Disable (same as false)
serve(app, { gracefulShutdown: false }); // Disable
`
HTTPS Example:
`typescript
import express from 'express';
import https from 'node:https';
import fs from 'node:fs';
import {serve} from 'exstack/serve';
const app = express();
const httpsServer = https.createServer(
{
cert: fs.readFileSync('./cert.pem'),
key: fs.readFileSync('./key.pem'),
},
app,
);
serve(httpsServer, {port: 443});
`
Graceful Shutdown Behavior:
- Press Ctrl+C โ Server stops accepting new connections and waits for active requestsCtrl+C` again โ Force close immediately
- Shows countdown timer (default 5 seconds)
- Press
- After timeout โ Automatically force closes all connections
Contributions are welcome!
Please open an issue or submit a pull request to help improve Exstack.
Licensed under the MIT License.