⚡️ Exstack
A lightweight, fast, and flexible utility library for Express.js — designed to simplify development with a high-performance router, async-safe handlers, built-in validation, and clean, standardized responses.
🧭 Table of Contents
🚀 Features
- ⚡ High-Performance Router — Express-compatible router with ultra-fast matching (Trie / RegExp strategies).
- 🧠 Async-Friendly Handlers — Simplify async route logic with automatic error propagation and standardized responses.
- 🧩 Standardized Responses — Use
ApiResandHttpErrorfor clean, consistent, and typed responses. - ✅ Zod-Based Validation — Validate request body, query, and params seamlessly.
- 🧱 Essential Middleware — Includes
errorHandler,notFound, andpoweredByout of the box. - 🧾 HttpStatus Enum — Access standardized HTTP status codes and names with clear constants.
📦 Installation
npm install exstack⚡ Quick Start
import * as z from 'zod';
import express from 'express';
import {validator} from 'exstack/zod';
import {Router, handler, errorHandler, notFound, ApiRes} from 'exstack';
const app = express();
const router = new Router();
// Middleware
app.use(express.json());
// Validation schema
const schema = z.object({
name: z.string(),
});
// Define routes
router.get('/ping', () => 'pong');
router.post('/users', validator.body(schema), req => {
const user = req.valid<typeof schema>('body');
return ApiRes.created(user, 'User created successfully');
});
// Mount the router
app.use(router.dispatch);
// Error middleware
app.use(notFound('*splat'));
app.use(errorHandler(process.env.NODE_ENV === 'development'));
app.listen(3000, () => console.log('Server running on port 3000'));🧠 Core Concepts
🗺️ Router
Exstack’s Router is a high-performance, flexible routing solution for Express.js applications. It supports Trie, RegExp, or hybrid (SmartRouter) routing strategies, middleware, sub-routers, async-safe handlers, and lazy parameter parsing.
Key Features
- Smart Routing: Automatically chooses the optimal routing strategy (
Trie,RegExp, orboth) for performance. - Express-Like API: Familiar methods —
.get(),.post(),.put(),.delete(),.patch(),.head(),.options(), and.all(). - Flexible Route Registration: Supports multiple paths, multiple methods, and handler chaining via
.on(). - Middleware Support: Works like
app.use(). Path-specific or global middleware can be registered. - Sub-router Mounting: Modular design allows mounting other routers using
.route(). - Lazy Params Handling:
req.paramsandreq.param()are automatically populated for matched routes. - Async-Friendly Dispatch: Handles async/sync route handlers safely with automatic error propagation.
Creating a Router
import {Router} from 'exstack';
// Default: SmartRouter (both Trie + RegExp)
const api = new Router();
// Single router strategies
const trieRouter = new Router('trie'); // Only TrieRouter
const regexRouter = new Router('regexp'); // Only RegExpRouterRoute Registration
You can define routes using familiar methods:
api.get('/ping', (req, res) => res.send('pong'));
api.post('/login', (req, res) => res.send({token: 'abc123'}));
api.all('/health', (req, res) => res.send({status: 'ok'}));Or register multiple methods/paths at once:
api.on(['get', 'post'], ['/user', '/account'], (req, res) => {
res.send({success: true});
});Middleware (use)
Attach middleware globally or to a specific path:
// Global middleware
api.use((req, res, next) => {
console.log('Request received:', req.path);
next();
});
// Path-specific middleware
api.use('/admin', (req, res, next) => {
if (!req.user?.isAdmin) return res.status(403).send('Forbidden');
next();
});Sub-router Mounting (route)
Mount other Router instances under a specific path:
const adminRouter = new Router();
adminRouter.get('/dashboard', (req, res) => res.send('Admin Dashboard'));
api.route('/admin', adminRouter); // Mounts as /admin/dashboard⚠️ Routers must be compatible (
Trie,RegExp, orSmartRouter). Mounting incompatible router types throws an error.
Parameter Handling
Route parameters are automatically available via:
api.get('/user/:id', req => {
const userId = req.param('id'); // Single param
const allParams = req.params; // Full params object
return {userId, allParams};
});Supports inheritance of params when routers are nested.
Dispatching Requests
Use .dispatch as an Express middleware:
import express from 'express';
const app = express();
app.use(api.dispatch);- Matches requests using the selected routing strategy.
- Automatically attaches
req.paramsandreq.param(). - Optimized for single handler or composed middleware chains.
- Safely handles async and sync handlers with automatic response handling.
Example: Complete Router Usage
import express from 'express';
import {Router, ApiRes, validator, handler} from 'exstack';
import * as z from 'zod';
const app = express();
const router = new Router();
app.use(express.json());
const userSchema = z.object({name: z.string()});
// Route with validation and response
router.post(
'/users',
validator.body(userSchema),
handler(req => {
const user = req.valid('body');
return ApiRes.created(user, 'User created successfully');
}),
);
// Sub-router example
const adminRouter = new Router();
adminRouter.get('/stats', () => ApiRes.ok({users: 100}, 'Stats'));
router.route('/admin', adminRouter);
app.use(router.dispatch);
app.listen(3000, () => console.log('Server running on port 3000'));🧮 Router Benchmarks
| Test Case | Express | Router | Improvement |
|---|---|---|---|
| Route Match | 11.24ms | 1.08ms | 10.4x faster |
| Middleware | 28.43ms | 1.11ms | 25.6x faster |
| Params | 15.88ms | 2.12ms | 7.5x faster |
| Wildcard | 15.12ms | 1.29ms | 11.7x faster |
| Async | 15.14ms | 3.29ms | 4.6x faster |
Cold Start (10K routes):
- Express: 32.74 ms
- Router: 6.20 ms
Memory Footprint (10K routes):
- Express: 14.67 MB
- Router: 0.00 MB
🪄 Handler
The handler utility wraps route logic to automatically catch errors and send responses cleanly.
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.param('id'));
return ApiRes.ok(user, 'User fetched successfully');
}),
);🧩 Note: The Exstack
Routernatively supports async/sync handlers —handler()is only needed when using standalone Express routes.
📦 ApiRes
ApiRes standardizes and simplifies success response formatting.
router.get('/user', () => ApiRes.ok({name: 'John Doe'}, 'User found'));
router.post('/user', req => {
const newUser = createUser(req.body);
return ApiRes.created(newUser, 'User created');
});
// Chainable example
router.post('/user', 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 |
🚨 HttpError
The HttpError class provides a consistent and structured way to handle HTTP errors.
import {HttpError, HttpStatus} from 'exstack';
router.get('*', (req, res) =>
new HttpError(HttpStatus.NOT_FOUND, {
message: 'Not Found',
}).toJson(res),
);
router.post('/example/:id', req => {
if (!req.param('id')) throw new BadRequestError('Id is required');
});Extended Options:
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,
HttpErrorautomatically assigns one based on the status code.
Common Errors:
BadRequestErrorUnauthorizedErrorNotFoundErrorConflictErrorForbiddenErrorPaymentRequiredErrorNotImplementedErrorInternalServerErrorContentTooLargeError
HttpError.isHttpError(value)
Check whether a value is an instance of HttpError.
// 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
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
HttpStatus provides readable constants for all standard HTTP status codes.
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,
});
});Commonly Used HTTP Status Codes:
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 ....
🔍 Zod Validator
The validator middleware provides an easy way to validate incoming requests using Zod schemas. It can validate the request body, query, params and all.
Installation
# node runtime
npm install zod
# bun runtime
bun install zodExamples
import * as z from 'zod';
import {validator} from 'exstack/zod';
const createUserSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
});
router.post(
'/users',
validator.body(createUserSchema),
handler(req => {
const validatedBody = req.valid('body');
// validatedBody is guaranteed to match the schema
return ApiRes.created(validatedBody, 'User created');
}),
);router.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 the schema
const user2 = req.valid<typeof createUserSchema>('body');
// ^? z.infer<typeof createUserSchema>
// 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()}),
};
router.put(
'/users/:id',
validator.all(multiSchema),
handler(req => {
const result = req.valid('all');
return ApiRes.ok(result);
}),
);🧱 Middleware
🛠️ errorHandler
Handles HttpError and unknown exceptions with standardized JSON output.
import {errorHandler} from 'exstack';
app.use(errorHandler(process.env.NODE_ENV === 'development'));🚫 notFound
Automatically throws a 404 for unmatched routes.
app.use(notFound('*splat'));⚙️ poweredBy
Adds an X-Powered-By header to responses.
app.use(poweredBy('Exstack'));🤝 Contributing
Contributions are welcome! Please open an issue or submit a pull request to help improve Exstack.
📄 License
Licensed under the MIT License.