{"id":27286597,"url":"https://github.com/udibo/http-error","last_synced_at":"2026-03-07T01:33:45.040Z","repository":{"id":42637226,"uuid":"417314891","full_name":"udibo/http-error","owner":"udibo","description":"An error class for HTTP requests.","archived":false,"fork":false,"pushed_at":"2025-12-31T17:17:34.000Z","size":95,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-14T05:44:58.769Z","etag":null,"topics":["deno","javascript","typescript"],"latest_commit_sha":null,"homepage":"","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/udibo.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":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-10-14T23:51:32.000Z","updated_at":"2025-12-31T17:16:26.000Z","dependencies_parsed_at":"2025-04-11T19:43:45.800Z","dependency_job_id":"09159d2d-77ba-4dae-a1c9-85cf93f77417","html_url":"https://github.com/udibo/http-error","commit_stats":null,"previous_names":["udibo/http-error","udibo/http_error"],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/udibo/http-error","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udibo%2Fhttp-error","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udibo%2Fhttp-error/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udibo%2Fhttp-error/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udibo%2Fhttp-error/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/udibo","download_url":"https://codeload.github.com/udibo/http-error/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/udibo%2Fhttp-error/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30205134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T19:07:06.838Z","status":"ssl_error","status_checked_at":"2026-03-06T18:57:34.882Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["deno","javascript","typescript"],"created_at":"2025-04-11T19:43:23.891Z","updated_at":"2026-03-07T01:33:45.020Z","avatar_url":"https://github.com/udibo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Http Error\n\n[![JSR](https://jsr.io/badges/@udibo/http-error)](https://jsr.io/@udibo/http-error)\n[![JSR Score](https://jsr.io/badges/@udibo/http-error/score)](https://jsr.io/@udibo/http-error)\n[![CI](https://github.com/udibo/http-error/workflows/CI/badge.svg)](https://github.com/udibo/http-error/actions?query=workflow%3ACI)\n[![codecov](https://codecov.io/gh/udibo/http-error/branch/main/graph/badge.svg?token=8Q7TSUFWUY)](https://codecov.io/gh/udibo/http-error)\n[![license](https://img.shields.io/github/license/udibo/http-error)](https://github.com/udibo/http-error/blob/master/LICENSE)\n\nUtilities for creating and working with Http Errors.\n\nThis package was inspired by\n[http-errors](https://www.npmjs.com/package/http-errors) for Node.js.\n\n## Features\n\n- Framework agnostic\n- RFC 9457 Problem Details compliant error responses\n\n## Usage\n\nBelow are some examples of how to use this module.\n\n### HttpError\n\nThis class can be used on its own to create any `HttpError`. It has a few\ndifferent call signatures you can use. The 4 examples below would throw the same\nerror.\n\n```ts\nimport { HttpError } from \"@udibo/http-error\";\n\nthrow new HttpError(404, \"file not found\");\nthrow new HttpError(404, { message: \"file not found\" });\nthrow new HttpError(\"file not found\", { status: 404 });\nthrow new HttpError({ status: 404, message: \"file not found\" });\n```\n\nYou can also include a `cause` in the optional options argument for it like you\ncan with regular errors. Additional `HttpErrorOptions` include `statusText`,\n`type` (a URI for the problem type), `instance` (a URI for this specific error\noccurrence), `extensions` (an object for additional details), and `headers` (to\ncustomize response headers).\n\n```ts\nimport { HttpError, type HttpErrorOptions } from \"@udibo/http-error\";\n\nconst cause = new Error(\"Underlying issue\");\nthrow new HttpError(400, \"Invalid input\", {\n  cause,\n  type: \"/errors/validation-error\",\n  instance: \"/requests/123/user-field\",\n  extensions: { field: \"username\", reason: \"must be alphanumeric\" },\n  headers: { \"X-Custom-Error-ID\": \"err-987\" },\n});\n```\n\nAll `HttpError` objects have a `status` associated with them. If a status is not\nprovided it will default to 500. The `expose` property will default to `true`\nfor client error statuses (4xx) and `false` for server error statuses (5xx). You\ncan override the default behavior by setting the `expose` property on the\noptions argument.\n\nFor all known HTTP error status codes, a `name` will be generated (e.g.,\n`NotFoundError` for 404). If the name is not known, it will default to\n`UnknownClientError` or `UnknownServerError`.\n\n```ts\nimport { HttpError } from \"@udibo/http-error\";\n\nconst error = new HttpError(404, \"file not found\");\nconsole.log(error.toString()); // NotFoundError: file not found\nconsole.log(error.status); // 404\nconsole.log(error.expose); // true\n```\n\nIf you would like to extend the `HttpError` class, you can pass your own error\nname in the options.\n\n```ts\nimport { HttpError, type HttpErrorOptions } from \"@udibo/http-error\";\n\nclass CustomError extends HttpError {\n  constructor(\n    message?: string,\n    options?: HttpErrorOptions,\n  ) {\n    super(message, { name: \"CustomError\", status: 420, ...options });\n  }\n}\n```\n\n#### `HttpError.from()`\n\nThis static method intelligently converts various error-like inputs into an\n`HttpError` instance.\n\n- If an `HttpError` is passed, it's returned directly.\n- If an `Error` is passed, it's wrapped in a new `HttpError` (usually 500\n  status), with the original error as the `cause`.\n- If a `Response` object is passed, it attempts to parse the body as RFC 9457\n  Problem Details. If successful, it creates an `HttpError` from those details.\n  If parsing fails or the body isn't Problem Details, it creates a generic\n  `HttpError` based on the response status. This method is asynchronous and\n  returns a `Promise\u003cHttpError\u003e`.\n- If a `ProblemDetails` object is passed, it creates an `HttpError` from it.\n- For other unknown inputs, it creates a generic `HttpError` with a 500 status.\n\n```ts\nimport { HttpError } from \"@udibo/http-error\";\n\n// From a standard Error\nconst plainError = new Error(\"Something went wrong\");\nconst httpErrorFromError = HttpError.from(plainError);\nconsole.log(httpErrorFromError.status); // 500\nconsole.log(httpErrorFromError.message); // \"Something went wrong\"\nconsole.log(httpErrorFromError.cause === plainError); // true\n\n// From a Response (example)\nasync function handleErrorResponse(response: Response) {\n  if (!response.ok) {\n    const error = await HttpError.from(response);\n    // Now 'error' is an HttpError instance\n    throw error;\n  }\n  return response.json();\n}\n\n// From a ProblemDetails object\nconst problemDetails = {\n  status: 403,\n  title: \"ForbiddenAccess\",\n  detail: \"You do not have permission.\",\n  type: \"/errors/forbidden\",\n};\nconst httpErrorFromDetails = HttpError.from(problemDetails);\nconsole.log(httpErrorFromDetails.status); // 403\nconsole.log(httpErrorFromDetails.name); // ForbiddenAccess\n```\n\n#### `HttpError.toJSON()`\n\nThis method returns a plain JavaScript object representing the error in the RFC\n9457 Problem Details format. This is useful for serializing the error to a JSON\nresponse body. If `expose` is `false` (default for 5xx errors), the `detail`\n(message) property will be omitted.\n\n```ts\nimport { HttpError } from \"@udibo/http-error\";\n\nconst error = new HttpError(400, \"Invalid input\", {\n  type: \"/errors/validation\",\n  instance: \"/form/user\",\n  extensions: { field: \"email\" },\n});\n\nconst problemDetails = error.toJSON();\nconsole.log(problemDetails);\n// Outputs:\n// {\n//   field: \"email\",\n//   status: 400,\n//   title: \"BadRequestError\",\n//   detail: \"Invalid input\",\n//   type: \"/errors/validation\",\n//   instance: \"/form/user\"\n// }\n\nconst serverError = new HttpError(500, \"Internal details\", { expose: false });\nconsole.log(serverError.toJSON());\n// Outputs (detail omitted):\n// {\n//   status: 500,\n//   title: \"InternalServerError\"\n// }\n```\n\n#### `HttpError.getResponse()`\n\nThis method returns a `Response` object, ready to be sent to the client. The\nresponse body will be the JSON string of the Problem Details object (from\n`toJSON()`), and headers (including `Content-Type: application/problem+json`)\nwill be set. The response status and status text will match the error's\nproperties.\n\n```ts\nimport { HttpError } from \"@udibo/http-error\";\n\nconst error = new HttpError(401, \"Authentication required\");\nconst response = error.getResponse();\n\nconsole.log(response.status); // 401\nconsole.log(response.headers.get(\"Content-Type\")); // application/problem+json\n// response.body can be read to get the JSON string\n```\n\n### `createHttpErrorClass()`\n\nThis factory function allows you to create custom error classes that extend\n`HttpError`. You can provide default options (like `status`, `name`, `message`,\n`extensions`, etc.) for your custom error class.\n\n```ts\nimport { createHttpErrorClass, type HttpErrorOptions } from \"@udibo/http-error\";\n\ninterface MyApiErrorExtensions {\n  errorCode: string;\n  requestId?: string;\n}\n\nconst MyApiError = createHttpErrorClass\u003cMyApiErrorExtensions\u003e({\n  name: \"MyApiError\", // Default name\n  status: 452, // Default status\n  extensions: {\n    errorCode: \"API_GENERAL_FAILURE\", // Default extension value\n  },\n});\n\ntry {\n  throw new MyApiError(\"Specific operation failed.\", {\n    extensions: {\n      errorCode: \"API_OP_X_FAILED\", // Override default extension\n      requestId: \"req-123\", // Add instance-specific extension\n    },\n  });\n} catch (e) {\n  if (e instanceof MyApiError) {\n    console.log(e.name); // \"MyApiError\"\n    console.log(e.status); // 452\n    console.log(e.message); // \"Specific operation failed.\"\n    console.log(e.extensions.errorCode); // \"API_OP_X_FAILED\"\n    console.log(e.extensions.requestId); // \"req-123\"\n    // console.log(e.toJSON());\n  }\n}\n\n// Instance with overridden status\nconst anotherError = new MyApiError(453, \"Another failure\");\nconsole.log(anotherError.status); // 453\nconsole.log(anotherError.extensions.errorCode); // \"API_GENERAL_FAILURE\" (from default)\n```\n\n### Framework Integration\n\n#### Hono\n\nHere's an example of how to use `HttpError` with Hono, utilizing its\n`app.onError` handler to return Problem Details JSON responses.\n\n```ts\nimport { Hono } from \"hono\";\nimport { HTTPException } from \"hono/http-exception\"; // For comparison\nimport { HttpError } from \"@udibo/http-error\";\n\nconst app = new Hono();\n\napp.get(\"/error\", () =\u003e {\n  throw new Error(\"This is an example of a plain Error\");\n});\n\napp.get(\"/hono-error\", () =\u003e {\n  // Hono's native error, for comparison\n  throw new HTTPException(400, {\n    message: \"This is an example of an error from hono\",\n  });\n});\n\napp.get(\"/http-error\", () =\u003e {\n  throw new HttpError(400, \"This is an example of an HttpError\", {\n    type: \"/errors/http-error\",\n    instance: \"/errors/http-error/instance/123\",\n    extensions: {\n      customField: \"customValue\",\n    },\n  });\n});\n\n// Global error handler\napp.onError(async (cause, c) =\u003e { // c is Hono's context\n  console.log(\"Hono onError caught:\", cause);\n\n  // Converts non-HttpError instances to HttpError instances\n  // For Response objects (e.g. from fetch), HttpError.from is async\n  const error = cause instanceof Response\n    ? await HttpError.from(cause)\n    : HttpError.from(cause);\n\n  console.error(error); // Log the full HttpError\n\n  return error.getResponse(); // Return a Response object directly\n});\n\nconsole.log(\"Hono server running on http://localhost:8000\");\nDeno.serve(app.fetch);\n```\n\n#### Oak\n\nHere is an example of how an Oak server could have middleware that converts any\nthrown error into an `HttpError` and returns a Problem Details JSON response.\n\n```ts\nimport { Application, Router } from \"@oak/oak\";\nimport { HttpError } from \"@udibo/http-error\";\n\nconst app = new Application();\n\n// Error handling middleware\napp.use(async (context, next) =\u003e {\n  try {\n    await next();\n  } catch (cause) {\n    // Converts non-HttpError instances to HttpError instances\n    // For Response objects (e.g. from fetch), HttpError.from is async\n    const error = cause instanceof Response\n      ? await HttpError.from(cause)\n      : HttpError.from(cause);\n\n    console.error(error); // Log the full error\n\n    const { response } = context;\n    response.status = error.status;\n    error.headers.forEach((value, key) =\u003e { // Set custom headers from HttpError\n      response.headers.set(key, value);\n    });\n    // Set Content-Type if not already set by error.headers\n    if (!response.headers.has(\"Content-Type\")) {\n      response.headers.set(\"Content-Type\", \"application/problem+json\");\n    }\n    response.body = error.toJSON(); // Send Problem Details as JSON\n  }\n});\n\nconst router = new Router();\nrouter.get(\"/test-error\", () =\u003e {\n  // Will be caught and transformed by the middleware\n  throw new Error(\"A generic error occurred!\");\n});\nrouter.get(\"/test-http-error\", () =\u003e {\n  throw new HttpError(400, \"This is a specific HttpError.\", {\n    type: \"/errors/my-custom-error\",\n    extensions: { info: \"some detail\" },\n  });\n});\n\napp.use(router.routes());\napp.use(router.allowedMethods());\n\nconsole.log(\"Oak server running on http://localhost:8000\");\nawait app.listen({ port: 8000 });\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fudibo%2Fhttp-error","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fudibo%2Fhttp-error","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fudibo%2Fhttp-error/lists"}