{"id":25067154,"url":"https://github.com/aashishpanchal/exutile","last_synced_at":"2025-04-14T19:42:32.544Z","repository":{"id":275554029,"uuid":"925151182","full_name":"aashishpanchal/exutile","owner":"aashishpanchal","description":"Ex⚡ lightweight utility library designed specifically for Express.js, helping developers simplify server-side logic and reduce boilerplate code.","archived":false,"fork":false,"pushed_at":"2025-02-03T21:15:32.000Z","size":76,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-03T21:23:15.667Z","etag":null,"topics":["asycnhandler","expressjs","http-errors","http-status","javascript","try-catch","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/exutile","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aashishpanchal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-31T10:29:05.000Z","updated_at":"2025-02-03T21:16:46.000Z","dependencies_parsed_at":"2025-02-03T21:23:17.175Z","dependency_job_id":null,"html_url":"https://github.com/aashishpanchal/exutile","commit_stats":null,"previous_names":["aashishpanchal/exutils","aashishpanchal/exutile"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aashishpanchal%2Fexutile","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aashishpanchal%2Fexutile/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aashishpanchal%2Fexutile/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aashishpanchal%2Fexutile/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aashishpanchal","download_url":"https://codeload.github.com/aashishpanchal/exutile/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237471924,"owners_count":19315609,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asycnhandler","expressjs","http-errors","http-status","javascript","try-catch","typescript"],"created_at":"2025-02-06T20:36:44.956Z","updated_at":"2025-02-06T20:36:45.621Z","avatar_url":"https://github.com/aashishpanchal.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ex⚡\n\n[![npm downloads](https://img.shields.io/npm/dm/exutile.svg)](https://www.npmjs.com/package/exutile)\n[![npm version](https://img.shields.io/npm/v/exutile.svg)](https://www.npmjs.com/package/exutile)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## Overview 🌟\n\n\u003e `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.\n\n## Table of Contents 📚\n\n- [Features](#features-)\n- [Installation](#installation-)\n- [Motivation](#motivation-)\n- [Quick Start](#quick-start-)\n- [`globalErrorHandler`: Error Handler Middleware](#globalerrorhandler-error-handler-middleware-)\n- [`serveStatic`: Serve Static Website Middleware (e.g., React, Vue)](#servestatic-serve-static-website-middleware-eg-react-vue)\n- [`asyncHandler`: Simplifying Controllers](#asynchandler-simplifying-controllers-️)\n- [Standardized JSON Responses with ApiRes](#standardized-json-responses-with-apires-)\n- [HttpError](#httperror-)\n- [HttpStatus](#httpstatus-)\n- [`proxyWrapper`: Class Controllers](#proxywrapper-class-controllers-️)\n- [Conclusion](#conclusion-)\n- [Contributing](#contributing-)\n- [Author](#author-)\n- [License](#license-)\n\n## Features ✨\n\n- ✅ Simplified error handling with `globalErrorHandler`\n- ✅ Simplified Serve Static Website with `serveStatic`\n- ✅ Automatic async error handling using `asyncHandler`\n- ✅ Standardized API responses with `ApiRes`\n- ✅ Flexible HTTP status codes and custom error classes\n- ✅ Class-based controllers with `proxyWrapper`\n\n## Motivation 💡\n\n\u003e 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.\n\n## Installation 📥\n\n```bash\nnpm install --save exutile\n```\n\n## Quick Start ⚡\n\nHere’s a minimal setup to get you started with exutile:\n\n```typescript\nimport express from 'express';\nimport {asyncHandler, globalErrorHandler} from 'exutile';\n\nconst app = express();\n\n// Middleware\napp.use(express.json());\n\n// Example route using asyncHandler\nconst getUser = asyncHandler(async (req, res) =\u003e {\n  const user = await getUserById(req.params.id);\n  return ApiRes.ok(user); // Send user data in the response\n});\n\n// Routers\napp.get('/user/:id', getUser);\n\n// Error handling middleware\napp.use(\n  globalErrorHandler({\n    isDev: process.env.NODE_ENV === 'development',\n    write: error =\u003e console.error(error),\n  }),\n);\n\napp.listen(3000, () =\u003e {\n  console.log('Server running on port 3000');\n});\n```\n\n## `globalErrorHandler`: Error Handler Middleware 🚨\n\nThe `globalErrorHandler` middleware manages **HttpErrors** and **Unknown** errors, returning appropriate **json responses.**\n\n**Usage:**\n\n```typescript\nimport {errorHandler} from 'exutile';\n\n// Basic usage with default options\napp.use(\n  globalErrorHandler({\n    isDev: process.env.NODE_ENV === 'development',\n  }),\n);\n\n// Custom usage with logging in production mode\napp.use(\n  globalErrorHandler({\n    isDev: process.env.NODE_ENV === 'development',\n    write: error =\u003e console.error(error),\n  }),\n);\n```\n\n**Signature:**  \n`globalErrorHandler({ isDev: boolean, write?: (err) =\u003e void }): ErrorRequestHandler`\n\n**Options:**\n\n- **isDev**: Enables detailed error messages in development mode (default: **true**).\n- **write**: Optional callback for logging or handling errors.\n\n## `serveStatic`: Serve Static Website Middleware (e.g., React, Vue)\n\nThe `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).**\n\n#### Usage:\n\n```typescript\nimport express from 'express';\nimport {serveStatic} from 'exutile';\n\nconst app = express();\n\n// Serve static files and handle SPA routing\napp.use(serveStatic({path: 'public', exclude: '/api{/*path}'}));\n\napp.listen(3000, () =\u003e {\n  console.log('Server running on port 3000');\n});\n```\n\n#### Options:\n\n- path: The directory to serve static files from `(default: \"public\")`.\n- exclude: Routes to exclude from SPA routing. This can be a string or an array of strings. `(default: '/api{/\\*path}')`.\n\n\u003e _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._\n\n## `asyncHandler`: Simplifying Controllers 🛠️\n\nEliminates repetitive **`try-catch`** blocks by managing error handling for both async and sync functions. It also integrates seamlessly with **ApiRes** for enhanced response handling.\n\n### Simplifying Route Handlers\n\n```typescript\nimport {asyncHandler, ApiRes} from 'exutile';\n\n// Route without asyncHandler (traditional approach with try-catch)\napp.get('/user/:id', async (req, res, next) =\u003e {\n  try {\n    const user = await getUserById(req.params.id);\n    res.status(200).json(user);\n  } catch (error) {\n    next(error); // Pass the error to the error-handling middleware\n  }\n});\n\n// Route using asyncHandler (simplified with exutile)\napp.get(\n  '/user/:id',\n  asyncHandler(async (req, res) =\u003e {\n    const user = await getUserById(req.params.id); // Fetch user from database\n    return ApiRes.ok(user, 'User fetched successfully'); // Send success response using ApiRes\n  }),\n);\n```\n\n### Advanced Example: Handling Cookies and Headers\n\n```typescript\nconst login = asyncHandler(async (req, res) =\u003e {\n  const {email, password} = req.body;\n  const user = await loginUser(email, password);\n\n  // Manually setting headers\n  res.setHeader('X-Custom-Header', 'SomeHeaderValue');\n\n  // Set multiple cookies for authentication\n  res.cookie('access-token', user.accessToken, {\n    httpOnly: true,\n    secure: true, // Set to true in production with HTTPS\n    maxAge: 3600000, // 1 hour\n  });\n\n  res.cookie('refresh-token', user.refreshToken, {\n    httpOnly: true,\n    secure: true,\n    maxAge: 7 * 24 * 3600000, // 1 week\n  });\n\n  // API response with token and user info\n  return ApiRes.ok(user, 'Logged in successfully');\n});\n```\n\n### Minimal Examples\n\n- **Simple Response:**\n\n```typescript\nconst getHome = asyncHandler(() =\u003e 'Hello World!');\n```\n\n- **Custom JSON Response:**\n\n```typescript\nconst getHome = asyncHandler(() =\u003e ({message: 'Hello World!'}));\n```\n\n- **Without ApiRes:**\n\n```typescript\nconst login = asyncHandler(async (req, res) =\u003e {\n  const user = await getUserById(req.params.id);\n  // Manually setting headers\n  res.setHeader('X-Custom-Header', 'SomeHeaderValue');\n  // Setting cookies\n  res.cookie('access-token', user.accessToken, {\n    httpOnly: true,\n    secure: true, // Set to true in production with HTTPS\n    maxAge: 3600000, // 1 hour\n  });\n  // Sending a custom JSON response\n  return res.status(200).json({\n    status: 'success',\n    message: 'User fetched successfully',\n    data: user,\n  });\n});\n```\n\n### Middleware Example: Role-Based Access Control\n\n```typescript\nimport {Role} from './constants';\nimport {asyncHandler, ForbiddenError} from 'exutile';\n\n/** Permission middleware */\nexport const permission = (...roles: Role[]) =\u003e\n  asyncHandler(async (req, _, next) =\u003e {\n    const {user} = req;\n\n    if (!roles.includes(user?.role))\n      throw new ForbiddenError(`Access denied for ${req.originalUrl}`);\n\n    next();\n  });\n\nexport const onlyAdmin = permission(Role.ADMIN);\nexport const adminOrUser = permission(Role.ADMIN, Role.USER);\n```\n\n## Standardized JSON Responses with ApiRes 📊\n\nApiRes provides a consistent structure for API responses. It includes several static methods that handle common response patterns, such as `ok`, `created`, and `paginated`.\n\n#### Usage:\n\n```typescript\nimport {ApiRes} from 'exutile';\n\n// With paginated\nconst list = asyncHandler(async req =\u003e {\n  const {data, meta} = await getUsers(req.query);\n  return ApiRes.paginated(data, meta, 'Get users list successfully');\n});\n\n// With created\nconst create = asyncHandler(async req =\u003e {\n  const user = await createUser(req.body);\n  return ApiRes.created(user, 'User created successfully');\n});\n\n// With ok\nconst get = asyncHandler(async req =\u003e {\n  const user = await getUser(req.params);\n  return ApiRes.ok(user, 'Get user successfully');\n});\n\n// Routers\napp.route('/').get(list).post(create);\napp.route('/:id').get(get);\n```\n\n### ApiRes Methods\n\n- `ok(result, message)`: Returns a success response (HTTP 200).\n- `created(result, message)`: Returns a resource creation response (HTTP 201).\n- `paginated(data, meta, message)`: Returns a success response (HTTP 200).\n\n## HttpError ❌\n\nThe 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.\n\n#### Usage:\n\n```typescript\nimport {HttpError, HttpStatus} from 'exutile';\n\n// Example without asyncHandler\napp.get('*', () =\u003e {\n  throw new HttpError('Not Found', HttpStatus.NOT_FOUND); // Throw a 404 error\n});\n\n// Example with asyncHandler\napp.post(\n  '/example',\n  asyncHandler(req =\u003e {\n    if (!req.body.name) throw new BadRequestError('Name is required');\n  }),\n);\n```\n\n### HttpError(msg, status, details):\n\n- `msg`: This parameter accepts an error message, which can be a single string or an array of strings (required).\n- `status`: The status code of the error, mirroring `statusCode` for general compatibility (default is 500).\n- `detail`: This is an optional plain object that contains additional information about the error.\n\n```typescript\nconst err = new HttpError('Validation error.', 400, {\n  username: 'Username is required',\n  password: 'Password is required',\n});\n```\n\n\u003e _Note: If only a status code is provided, the **HttpError** class will automatically generate an appropriate error name based on that status code._\n\n### Common HTTP Errors:\n\n- `BadRequestError`\n- `UnAuthorizedError`\n- `NotFoundError`\n- `ConflictError`\n- `ForbiddenError`\n- `PaymentRequiredError`\n- `NotImplementedError`\n- `InternalServerError`\n\n### `isHttpError(value)` Static Method:\n\nThe `HttpError.isHttpError(value)` method determines if a specific value is an instance of the `HttpError` class.\n\n```typescript\n// If it is an HttpError, send a JSON response with the error details\nif (HttpError.isHttpError(err))\n  return res.status(err.status).json(err.toJson());\nelse {\n  // If it's not an HttpError, pass it to the next middleware for further handling\n  next(err);\n}\n```\n\n### Error Properties:\n\n- **status**: The HTTP status code associated with the error.\n- **message**: A brief description of the error.\n- **stack**: The stack trace of the error (available in development mode).\n- **details**: Optional additional information about the error.\n\n### Custom ErrorHandler Middleware\n\n```typescript\nexport const errorHandler: ErrorRequestHandler = (err, req, res, next): any =\u003e {\n  // Handle known HttpError instances\n  if (HttpError.isHttpError(err))\n    return res.status(err.status).json(err.toJson());\n\n  // Log unknown errors\n  console.error(err);\n\n  // Create an InternalServerError for unknown errors\n  const error = new InternalServerError(\n    config.dev ? err.message : 'Something went wrong',\n    config.dev ? err.stack : null,\n  );\n  return res.status(error.status).json(error.toJson());\n};\n```\n\n### `error.toJson()` Method:\n\nConverts an `HttpError` instance into a structured JSON format.\n\n```typescript\nreturn res.status(err.status).json(err.toJson());\n```\n\n\u003e _Note: **details** if applicable then additional information that provides context about the error._\n\n## HttpStatus ✅\n\nThe `HttpStatus` provides readable constants for standard HTTP status codes **(2xx, 3xx, 4xx, 5xx)** and **Names**, improving code clarity and consistency.\n\n#### Usage:\n\n```typescript\nimport {HttpStatus} from 'exutile';\n\n// Example: Basic usage in a route\napp.get('/status-example', (req, res) =\u003e {\n  res.status(HttpStatus.OK).json({message: 'All good!'});\n});\n\n// Example: Custom error handling middleware\napp.use((req, res) =\u003e {\n  res.status(HttpStatus.NOT_FOUND).json({\n    error: 'Resource not found',\n  });\n});\n\n// Example: Response with a 201 Created status\napp.post('/create', (req, res) =\u003e {\n  const resource = createResource(req.body);\n  res.status(HttpStatus.CREATED).json({\n    message: 'Resource created successfully',\n    data: resource,\n  });\n});\n```\n\n### `HttpStatus.NAMES` of HTTP Status Code Name:\n\nThe `NAMES` object provides a simple lookup for the descriptive names of HTTP status codes.\n\n```typescript\nconst statusName = HttpStatus.NAMES.$200; // 'OK'\n```\n\n### Commonly Used HTTP Status Codes:\n\n- **2xx: Success**\n\n  - `HttpStatus.OK`: 200 — Request succeeded.\n  - `HttpStatus.CREATED`: 201 — Resource created.\n  - `HttpStatus.ACCEPTED`: 202 — Request accepted for processing.\n  - `HttpStatus.NO_CONTENT`: 204 — No content to send.\n  - and more ....\n\n- **3xx: Redirection**\n\n  - `HttpStatus.MOVED_PERMANENTLY`: 301 — Resource moved permanently.\n  - `HttpStatus.FOUND`: 302 — Resource found at another URI.\n  - `HttpStatus.NOT_MODIFIED`: 304 — Resource not modified.\n  - and more ....\n\n- **4xx: Client Error**\n\n  - `HttpStatus.BAD_REQUEST`: 400 — Bad request.\n  - `HttpStatus.UNAUTHORIZED`: 401 — Authentication required.\n  - `HttpStatus.FORBIDDEN`: 403 — Access forbidden.\n  - `HttpStatus.NOT_FOUND`: 404 — Resource not found.\n  - and more ....\n\n- **5xx: Server Error**\n  - `HttpStatus.INTERNAL_SERVER_ERROR`: 500 — Internal server error.\n  - `HttpStatus.NOT_IMPLEMENTED`: 501 — Not implemented.\n  - `HttpStatus.SERVICE_UNAVAILABLE`: 503 — Service unavailable.\n  - and more ....\n\n## `proxyWrapper`: Class Controllers 🏗️\n\n`exutile` provides the utility `proxyWrapper` to make simplify working with class-based controllers in Express.\n\n#### Usage:\n\n```typescript\n// example-controller.ts\nimport {Request} from 'express';\n\n// Controller Class\nclass ExampleController {\n  constructor(private message: string) {}\n\n  async getData(req: Request) {\n    // Your logic here\n    return ApiRes.ok({}, this.message);\n  }\n}\n\n// example-routes.ts\nimport {Router} from 'express';\nimport {proxyWrapper} from 'exutile';\nimport {ExampleController} from './example-controller.ts';\n\nconst exampleRoutes = (): Router =\u003e {\n  const router = Router();\n\n  // Create a proxied instance of ExampleController\n  const example = proxyWrapper(ExampleController, 'Hello World');\n\n  // Configure routes\n  return router.post('/data', example.getData);\n};\n```\n\n### `proxyWrapper(clsOrInstance, ...args)`:\n\n- **Parameters**:\n  - `clsOrInstance`: A class constructor or an instance of a class.\n  - `args`: Arguments for the class constructor (if `clsOrInstance` is a constructor).\n- **Returns**: A proxied instance where all methods are wrapped with `asyncHandler`.\n\n### How It Works\n\n- Instantiates the specified class if a constructor is provided.\n- Wraps all its methods with `asyncHandler`, allowing for automatic handling of asynchronous operations.\n- **Prevents method/property** overrides for safety.\n\n### Using Dependency Injection Libraries:\n\nYou can use `proxyWrapper` with dependency injection libraries like `tsyringe` or `typedi`.\n\n#### Example with `tsyringe`\n\n```typescript\nconst exampleRoutes = (): Router =\u003e {\n  const router = Router();\n\n  // Create a proxied instance of ExampleController\n  const example = proxyWrapper(container.resolve(ExampleController));\n\n  // Configure routes\n  return router.post('/data', example.getData);\n};\n```\n\n#### Example with `typedi`\n\n```typescript\nconst exampleRoutes = (): Router =\u003e {\n  const router = Router();\n\n  // Create a proxied instance of ExampleController\n  const example = proxyWrapper(Container.get(ExampleController));\n\n  // Configure routes\n  return router.post('/data', example.getData);\n};\n```\n\n## Conclusion 🏁\n\n`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.\n\n## Contributing 🤝\n\nContributions are highly appreciated! To contribute:\n\n1. Fork the repository.\n2. Create a new branch for your feature or bug fix.\n3. Submit a pull request with a clear description of your changes.\n\n## Author 👤\n\n- Created by **Aashish Panchal**.\n- GitHub: [@aashishpanchal](https://github.com/aashishpanchal)\n\n## License 📜\n\n[MIT © Aashish Panchal](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faashishpanchal%2Fexutile","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faashishpanchal%2Fexutile","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faashishpanchal%2Fexutile/lists"}