https://github.com/aashishpanchal/exutile
Exβ‘ lightweight utility library designed specifically for Express.js, helping developers simplify server-side logic and reduce boilerplate code.
https://github.com/aashishpanchal/exutile
asycnhandler expressjs http-errors http-status javascript try-catch typescript
Last synced: 11 months ago
JSON representation
Exβ‘ lightweight utility library designed specifically for Express.js, helping developers simplify server-side logic and reduce boilerplate code.
- Host: GitHub
- URL: https://github.com/aashishpanchal/exutile
- Owner: aashishpanchal
- License: mit
- Created: 2025-01-31T10:29:05.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2025-02-03T21:15:32.000Z (about 1 year ago)
- Last Synced: 2025-02-03T21:23:15.667Z (about 1 year ago)
- Topics: asycnhandler, expressjs, http-errors, http-status, javascript, try-catch, typescript
- Language: TypeScript
- Homepage: https://www.npmjs.com/package/exutile
- Size: 74.2 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# Exβ‘
[](https://www.npmjs.com/package/exutile)
[](https://www.npmjs.com/package/exutile)
[](https://opensource.org/licenses/MIT)
## Overview π
> `exutile` is a lightweight utility library designed specifically for Express.js, helping developers simplify server-side logic and reduce boilerplate code. It provides ready-to-use features like error handling, HTTP status utilities, and standardized API responses, enabling you to write cleaner, more maintainable code effortlessly.
## Table of Contents π
- [Features](#features-)
- [Installation](#installation-)
- [Motivation](#motivation-)
- [Quick Start](#quick-start-)
- [`globalErrorHandler`: Error Handler Middleware](#globalerrorhandler-error-handler-middleware-)
- [`serveStatic`: Serve Static Website Middleware (e.g., React, Vue)](#servestatic-serve-static-website-middleware-eg-react-vue)
- [`asyncHandler`: Simplifying Controllers](#asynchandler-simplifying-controllers-οΈ)
- [Standardized JSON Responses with ApiRes](#standardized-json-responses-with-apires-)
- [HttpError](#httperror-)
- [HttpStatus](#httpstatus-)
- [`proxyWrapper`: Class Controllers](#proxywrapper-class-controllers-οΈ)
- [Conclusion](#conclusion-)
- [Contributing](#contributing-)
- [Author](#author-)
- [License](#license-)
## Features β¨
- β
Simplified error handling with `globalErrorHandler`
- β
Simplified Serve Static Website with `serveStatic`
- β
Automatic async error handling using `asyncHandler`
- β
Standardized API responses with `ApiRes`
- β
Flexible HTTP status codes and custom error classes
- β
Class-based controllers with `proxyWrapper`
## Motivation π‘
> Building APIs often involves repetitive tasks like handling errors, managing HTTP status codes, or structuring JSON responses. `exutile` was created to eliminate this hassle, allowing developers to focus on writing business logic instead of reinventing common solutions. Whether you're a beginner or an experienced developer, `exutile` streamlines your workflow and ensures your **Express** applications are consistent and reliable.
## Installation π₯
```bash
npm install --save exutile
```
## Quick Start β‘
Hereβs a minimal setup to get you started with exutile:
```typescript
import express from 'express';
import {asyncHandler, globalErrorHandler} from 'exutile';
const app = express();
// Middleware
app.use(express.json());
// Example route using asyncHandler
const getUser = asyncHandler(async (req, res) => {
const user = await getUserById(req.params.id);
return ApiRes.ok(user); // Send user data in the response
});
// Routers
app.get('/user/:id', getUser);
// Error handling middleware
app.use(
globalErrorHandler({
isDev: process.env.NODE_ENV === 'development',
write: error => console.error(error),
}),
);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
```
## `globalErrorHandler`: Error Handler Middleware π¨
The `globalErrorHandler` middleware manages **HttpErrors** and **Unknown** errors, returning appropriate **json responses.**
**Usage:**
```typescript
import {errorHandler} from 'exutile';
// Basic usage with default options
app.use(
globalErrorHandler({
isDev: process.env.NODE_ENV === 'development',
}),
);
// Custom usage with logging in production mode
app.use(
globalErrorHandler({
isDev: process.env.NODE_ENV === 'development',
write: error => console.error(error),
}),
);
```
**Signature:**
`globalErrorHandler({ isDev: boolean, write?: (err) => void }): ErrorRequestHandler`
**Options:**
- **isDev**: Enables detailed error messages in development mode (default: **true**).
- **write**: Optional callback for logging or handling errors.
## `serveStatic`: Serve Static Website Middleware (e.g., React, Vue)
The `serveStatic` function is a middleware that serves static files from a directory and handles Single Page Application **(SPA)** routing by returning index.html for unmatched routes, excluding specified patterns **(e.g., API routes).**
#### Usage:
```typescript
import express from 'express';
import {serveStatic} from 'exutile';
const app = express();
// Serve static files and handle SPA routing
app.use(serveStatic({path: 'public', exclude: '/api{/*path}'}));
app.listen(3000, () => {
console.log('Server running on port 3000');
});
```
#### Options:
- path: The directory to serve static files from `(default: "public")`.
- exclude: Routes to exclude from SPA routing. This can be a string or an array of strings. `(default: '/api{/\*path}')`.
> _Note: The exclude option can take advantage of the [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) library to define more complex route patterns._
## `asyncHandler`: Simplifying Controllers π οΈ
Eliminates repetitive **`try-catch`** blocks by managing error handling for both async and sync functions. It also integrates seamlessly with **ApiRes** for enhanced response handling.
### Simplifying Route Handlers
```typescript
import {asyncHandler, ApiRes} from 'exutile';
// Route without asyncHandler (traditional approach with try-catch)
app.get('/user/:id', async (req, res, next) => {
try {
const user = await getUserById(req.params.id);
res.status(200).json(user);
} catch (error) {
next(error); // Pass the error to the error-handling middleware
}
});
// Route using asyncHandler (simplified with exutile)
app.get(
'/user/:id',
asyncHandler(async (req, res) => {
const user = await getUserById(req.params.id); // Fetch user from database
return ApiRes.ok(user, 'User fetched successfully'); // Send success response using ApiRes
}),
);
```
### Advanced Example: Handling Cookies and Headers
```typescript
const login = asyncHandler(async (req, res) => {
const {email, password} = req.body;
const user = await loginUser(email, password);
// Manually setting headers
res.setHeader('X-Custom-Header', 'SomeHeaderValue');
// Set multiple cookies for authentication
res.cookie('access-token', user.accessToken, {
httpOnly: true,
secure: true, // Set to true in production with HTTPS
maxAge: 3600000, // 1 hour
});
res.cookie('refresh-token', user.refreshToken, {
httpOnly: true,
secure: true,
maxAge: 7 * 24 * 3600000, // 1 week
});
// API response with token and user info
return ApiRes.ok(user, 'Logged in successfully');
});
```
### Minimal Examples
- **Simple Response:**
```typescript
const getHome = asyncHandler(() => 'Hello World!');
```
- **Custom JSON Response:**
```typescript
const getHome = asyncHandler(() => ({message: 'Hello World!'}));
```
- **Without ApiRes:**
```typescript
const login = asyncHandler(async (req, res) => {
const user = await getUserById(req.params.id);
// Manually setting headers
res.setHeader('X-Custom-Header', 'SomeHeaderValue');
// Setting cookies
res.cookie('access-token', user.accessToken, {
httpOnly: true,
secure: true, // Set to true in production with HTTPS
maxAge: 3600000, // 1 hour
});
// Sending a custom JSON response
return res.status(200).json({
status: 'success',
message: 'User fetched successfully',
data: user,
});
});
```
### Middleware Example: Role-Based Access Control
```typescript
import {Role} from './constants';
import {asyncHandler, ForbiddenError} from 'exutile';
/** Permission middleware */
export const permission = (...roles: Role[]) =>
asyncHandler(async (req, _, next) => {
const {user} = req;
if (!roles.includes(user?.role))
throw new ForbiddenError(`Access denied for ${req.originalUrl}`);
next();
});
export const onlyAdmin = permission(Role.ADMIN);
export const adminOrUser = permission(Role.ADMIN, Role.USER);
```
## Standardized JSON Responses with ApiRes π
ApiRes provides a consistent structure for API responses. It includes several static methods that handle common response patterns, such as `ok`, `created`, and `paginated`.
#### Usage:
```typescript
import {ApiRes} from 'exutile';
// With paginated
const list = asyncHandler(async req => {
const {data, meta} = await getUsers(req.query);
return ApiRes.paginated(data, meta, 'Get users list successfully');
});
// With created
const create = asyncHandler(async req => {
const user = await createUser(req.body);
return ApiRes.created(user, 'User created successfully');
});
// With ok
const get = asyncHandler(async req => {
const user = await getUser(req.params);
return ApiRes.ok(user, 'Get user successfully');
});
// Routers
app.route('/').get(list).post(create);
app.route('/:id').get(get);
```
### ApiRes Methods
- `ok(result, message)`: Returns a success response (HTTP 200).
- `created(result, message)`: Returns a resource creation response (HTTP 201).
- `paginated(data, meta, message)`: Returns a success response (HTTP 200).
## HttpError β
The HttpError class standardizes error handling by extending the native Error class. Itβs used to throw HTTP-related errors, which are then caught by the **`globalErrorHandler`** middleware.
#### Usage:
```typescript
import {HttpError, HttpStatus} from 'exutile';
// Example without asyncHandler
app.get('*', () => {
throw new HttpError('Not Found', HttpStatus.NOT_FOUND); // Throw a 404 error
});
// Example with asyncHandler
app.post(
'/example',
asyncHandler(req => {
if (!req.body.name) throw new BadRequestError('Name is required');
}),
);
```
### HttpError(msg, status, details):
- `msg`: This parameter accepts an error message, which can be a single string or an array of strings (required).
- `status`: The status code of the error, mirroring `statusCode` for general compatibility (default is 500).
- `detail`: This is an optional plain object that contains additional information about the error.
```typescript
const err = new HttpError('Validation error.', 400, {
username: 'Username is required',
password: 'Password is required',
});
```
> _Note: If only a status code is provided, the **HttpError** class will automatically generate an appropriate error name based on that status code._
### Common HTTP Errors:
- `BadRequestError`
- `UnAuthorizedError`
- `NotFoundError`
- `ConflictError`
- `ForbiddenError`
- `PaymentRequiredError`
- `NotImplementedError`
- `InternalServerError`
### `isHttpError(value)` Static Method:
The `HttpError.isHttpError(value)` method determines if a specific value is an instance of the `HttpError` class.
```typescript
// If it is an HttpError, send a JSON response with the error details
if (HttpError.isHttpError(err))
return res.status(err.status).json(err.toJson());
else {
// If it's not an HttpError, pass it to the next middleware for further handling
next(err);
}
```
### Error Properties:
- **status**: The HTTP status code associated with the error.
- **message**: A brief description of the error.
- **stack**: The stack trace of the error (available in development mode).
- **details**: Optional additional information about the error.
### Custom ErrorHandler Middleware
```typescript
export const errorHandler: ErrorRequestHandler = (err, req, res, next): any => {
// Handle known HttpError instances
if (HttpError.isHttpError(err))
return res.status(err.status).json(err.toJson());
// Log unknown errors
console.error(err);
// Create an InternalServerError for unknown errors
const error = new InternalServerError(
config.dev ? err.message : 'Something went wrong',
config.dev ? err.stack : null,
);
return res.status(error.status).json(error.toJson());
};
```
### `error.toJson()` Method:
Converts an `HttpError` instance into a structured JSON format.
```typescript
return res.status(err.status).json(err.toJson());
```
> _Note: **details** if applicable then additional information that provides context about the error._
## HttpStatus β
The `HttpStatus` provides readable constants for standard HTTP status codes **(2xx, 3xx, 4xx, 5xx)** and **Names**, improving code clarity and consistency.
#### Usage:
```typescript
import {HttpStatus} from 'exutile';
// 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,
});
});
```
### `HttpStatus.NAMES` of HTTP Status Code Name:
The `NAMES` object provides a simple lookup for the descriptive names of HTTP status codes.
```typescript
const statusName = HttpStatus.NAMES.$200; // 'OK'
```
### 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 ....
## `proxyWrapper`: Class Controllers ποΈ
`exutile` provides the utility `proxyWrapper` to make simplify working with class-based controllers in Express.
#### Usage:
```typescript
// example-controller.ts
import {Request} from 'express';
// Controller Class
class ExampleController {
constructor(private message: string) {}
async getData(req: Request) {
// Your logic here
return ApiRes.ok({}, this.message);
}
}
// example-routes.ts
import {Router} from 'express';
import {proxyWrapper} from 'exutile';
import {ExampleController} from './example-controller.ts';
const exampleRoutes = (): Router => {
const router = Router();
// Create a proxied instance of ExampleController
const example = proxyWrapper(ExampleController, 'Hello World');
// Configure routes
return router.post('/data', example.getData);
};
```
### `proxyWrapper(clsOrInstance, ...args)`:
- **Parameters**:
- `clsOrInstance`: A class constructor or an instance of a class.
- `args`: Arguments for the class constructor (if `clsOrInstance` is a constructor).
- **Returns**: A proxied instance where all methods are wrapped with `asyncHandler`.
### How It Works
- Instantiates the specified class if a constructor is provided.
- Wraps all its methods with `asyncHandler`, allowing for automatic handling of asynchronous operations.
- **Prevents method/property** overrides for safety.
### Using Dependency Injection Libraries:
You can use `proxyWrapper` with dependency injection libraries like `tsyringe` or `typedi`.
#### Example with `tsyringe`
```typescript
const exampleRoutes = (): Router => {
const router = Router();
// Create a proxied instance of ExampleController
const example = proxyWrapper(container.resolve(ExampleController));
// Configure routes
return router.post('/data', example.getData);
};
```
#### Example with `typedi`
```typescript
const exampleRoutes = (): Router => {
const router = Router();
// Create a proxied instance of ExampleController
const example = proxyWrapper(Container.get(ExampleController));
// Configure routes
return router.post('/data', example.getData);
};
```
## Conclusion π
`exutile` is a powerful tool designed to simplify and enhance Express.js applications by providing essential features out of the box. Whether youβre building a simple API or a complex web application, exutile helps you maintain clean and manageable code.
## Contributing π€
Contributions are highly appreciated! To contribute:
1. Fork the repository.
2. Create a new branch for your feature or bug fix.
3. Submit a pull request with a clear description of your changes.
## Author π€
- Created by **Aashish Panchal**.
- GitHub: [@aashishpanchal](https://github.com/aashishpanchal)
## License π
[MIT Β© Aashish Panchal](LICENSE)