Create standardized Javascript objects for API error responses, per RFC 9457.
Pronounced "Pie Joy", the name is an acronym for Problem Instance Javascript Object Yo!. I took some poetic license from the RFC verbiage "problem details", to arrive at "problem instance".
The original name was "pijo", but the NPM registry rejected that on the basis it was too much like the existing package "pino". Good. I already have another package that ends in y for "yo!".
"problem details" - One or more Javascript object key/value pairs, that provide details about API request errors. Some, or all, of which are defined by RFC 9457.
"problem instance" - A Javascript object consisting of problem details.
If you're new to this concept, below are a few snippets from the RFC. Embedded links have been removed.
"This document defines a "problem detail" to carry machine-readable details of errors in HTTP response content to avoid the need to define new error response formats for HTTP APIs.
HTTP status codes (Section 15 of HTTP) cannot always convey enough information about errors to be helpful.
To address that shortcoming, this specification defines simple JSON and XML document formats to describe the specifics of a problem encountered -- "problem details".
For example, consider a response indicating that the client's account doesn't have enough credit. The API's designer might decide to use the 403 Forbidden status code to inform generic HTTP software (such as client libraries, caches, and proxies) of the response's general semantics. API-specific problem details (such as why the server refused the request and the applicable account balance) can be carried in the response content so that the client can act upon them appropriately (for example, triggering a transfer of more credit into the account)."
npm install pijoy
The defined members of a problem instance are type
, status
, title
, detail
, and instance
. The rest are extension members, whose names you define.
The Content-Type for responses must be application/problem+json
; which is set in the pijoy json
function, if you choose to use it.
import { problem, json } from "pijoy"
/* Create a Problem Instance */
const pijoy = problem({ status: 403 })
/* Return a JSON response with headers */
return json(pijoy)
/* Returned JSON object */
{
"type": "https://www.rfc-editor.org/rfc/rfc9110#name-403-forbidden",
"status": 403,
"title": "Forbidden"
}
import { problem, json } from "pijoy"
/* API endpoint to purchase a product */
export const POST = async ({ request }) => {
const res = await fetch("http://db.example.com")
const { data, error } = await res.json()
if (error) {
const pijoy = createPijoy(error)
return json({ data: null, error: pijoy })
}
function createPijoy(error) {
/* Assuming your database server returns these items */
const { status, message, ...rest } = error
return problem({
status, // 403
detail: message, // "You do not have enough credit to purchase this item."
instance: request.url, // "https://example.com/product/1234"
...rest // { reason: "LackOfCredit", balance: 30, cost: 50, accounts: [ "/account/12345", "/account/67890" ] }
})
}
}
/* Returned JSON object to client */
{
"type": "https://www.rfc-editor.org/rfc/rfc9110#name-403-forbidden",
"status": 403,
"title": "Forbidden",
"detail": "You do not have enough credits to purchase this item.",
"instance": "http://example.com/product/1234",
"reason": "LackOfCredit",
"balance": 30,
"cost": 50,
"accounts": [ "/account/12345", "/account/67890" ]
}
It's important to note that none of the RFC-defined members are required, but this library ensures that type
, status
, and title
are all present within a problem instance.
The below definitions are based on the RFC, but may contain different links and slightly different verbiage.
type
- A string containing a URI reference that identifies the problem type. If you pass in an HTTP status code to status
that isn't defined in RFC 9110, and don't also pass in this member, pijoy sets type
to "about:blank".
status
- A number indicating a valid HTTP status code, for this occurrence of the problem. This member is only advisory, is used for the convenience of the consumer, and if present, must be used in the actual HTTP response.
title
- A string containing a short, human-readable summary of the problem type. This member is only advisory. If you pass in a valid HTTP status code to status
, that isn't defined in RFC 9110, and don't also pass in this member, pijoy sets title
to "Unknown Error".
detail
- A string containing a human-readable explanation specific to this occurrence of the problem. This member, if present, ought to focus on helping the client correct the problem, rather than giving debugging information.
instance
- A string containing a URI reference that identifies the specific occurrence of the problem. This is typically the API endpoint, and it is recommended to be an absolute URI.
HttpStatusCodes
is a number range between 100-599.[key:string]: any
implies any number of additional extension members you wish to include.
problem()
- Create a Problem Instance.
function problem(data: ProblemDetail): ProblemInstance
ProblemDetail = {
status: HttpStatusCodes;
type?: string;
title?: string;
detail?: string;
instance?: string;
[key:string]: any;
}
ProblemInstance = {
type: string;
status: HttpStatusCodes;
title: string;
detail?: string;
instance?: string;
[key:string]: any;
}
json()
- Create a Response. This includes a Content-Type
header set to application/problem+json
, and also a Content-Length
header.
function json(data: ProblemInstance): Response
ProblemInstance = {
type: string;
status: HttpStatusCodes;
title: string;
detail?: string;
instance?: string;
[key:string]: any;
}