Error Handling
Our backend code generally avoids throwing errors, instead returns objects called “Results”. Results objects will always contain either the desired value or one of the possible error types specified by the Result signature.
From Wikipedia:
In functional programming, a result type is a monadic type holding a returned value or an error code. They provide an elegant way of handling errors, without resorting to exception handling; when a function that may fail returns a result type, the programmer is forced to consider success or failure paths, before getting access to the expected result; this eliminates the possibility of an erroneous programmer assumption.
Example
Section titled “Example”An operation to look up a user record in the database might resolve in one of three ways:
- Succeed and return a User
- Fail when the record isn’t found
- Fail with some other unspecified server error.
Here’s how we might model that in our server-side code:
import { R } from '@mobily/ts-belt'import { NotFoundError, UnknownError } from '#app/utils/errors.server'
interface User { name: string age: number}
function fetchUserById( id: string,):R.Result<User, NotFoundError | UnknownError> { try { // fake database lookup if (id === '123') { return R.Ok({ name: 'Blarg Blargerson', age: 42 }) } else { return R.Error(new NotFoundError('user not found')) } } catch (err) { return R.Error(new UnknownError('something bad happened')) }}
const userResult = fetchUserById('123')
if (R.isOk(userResult)) { console.log(`user is ${userResult._0.age} years old.}`)} else { console.log(`something went wrong! -- ${userResult._0.message}`)}Custom Errors
Section titled “Custom Errors”Custom Error classes extend the standard Error and include a name that matches their class name and a statusCode property:
...export class NotFoundError extends CustomError { public statusCode: number constructor(message: string) { super(message) this.name = 'NotFoundError' this.statusCode = HttpStatusCode.NOT_FOUND }}
export class TokenValidationError extends CustomError { public statusCode: number constructor(message: string) { super(message) this.name = 'TokenValidationError' this.statusCode = HttpStatusCode.UNAUTHORIZED }}...Returned Errors
Section titled “Returned Errors”When returning an error to the API client, this function returns either a CustomError class or ZodError:
...export const jsonFromErrorResult = ( result: R.Error<CustomError | ZodError>,): Record<string, unknown> => { let originalError if ('statusCode' in result._0) { originalError = result._0 as CustomError return json( { message: originalError.message, name: originalError.name }, originalError.statusCode, ) } else { originalError = result._0 as ZodError return json( { issues: originalError.issues, name: originalError.name }, HttpStatusCode.BAD_REQUEST, ) }}...