// src/errors/app-error.ts
export class AppError extends Error {
constructor (
public readonly code : string ,
public readonly status : number ,
public readonly detail ?: string ,
public readonly meta ?: Record < string , unknown >
) {
super (detail ?? code);
this .name = "AppError" ;
}
}
export class NotFoundError extends AppError {
constructor ( resource : string , id : string ) {
super ( "NOT_FOUND" , 404 , `${ resource } ${ id } not found` , { resource, id });
}
}
// src/http/problem.ts
import type { AppError } from "../errors/app-error" ;
export type ProblemDetails = {
type : string ;
title : string ;
status : number ;
detail ?: string ;
code : string ;
i18nKey : string ;
meta ?: Record < string , unknown >;
};
const ERROR_BASE = "https://api.acme.example/errors" ;
export function toProblem ( err : AppError ) : ProblemDetails {
return {
type: `${ ERROR_BASE }/${ err . code }` ,
title: err.code,
status: err.status,
detail: err.detail,
code: err.code,
i18nKey: `errors.${ err . code . toLowerCase () }` ,
meta: err.meta,
};
}
// src/http/error-middleware.ts
import type { ErrorRequestHandler } from "express" ;
import { AppError } from "../errors/app-error" ;
import { toProblem } from "./problem" ;
export const errorMiddleware : ErrorRequestHandler = ( err , req , res , _next ) => {
if (err instanceof AppError ) {
const problem = toProblem (err);
return res. status (err.status). type ( "application/problem+json" ). json (problem);
}
req.log?. error ({ err }, "unhandled error" );
const status = 500 ;
res. status (status). type ( "application/problem+json" ). json ({
type: `${ ERROR_BASE }/INTERNAL_ERROR` ,
title: "INTERNAL_ERROR" ,
status,
code: "INTERNAL_ERROR" ,
i18nKey: "errors.internal" ,
detail: process.env. NODE_ENV === "production" ? undefined : String (err),
});
};
// use case
import { NotFoundError } from "../errors/app-error" ;
export async function getOrder ( repo : { findById : ( id : string ) => Promise < unknown > }, id : string ) {
const order = await repo. findById (id);
if ( ! order) throw new NotFoundError ( "Order" , id);
return order;
}