elysia
elysia copied to clipboard
Option to return errors as JSON
Hey, it would be nice to be able to do that. I understand the onError callback but that requires a good bit of finessing to convert to JSON. In particular I am thinking of validator errors.
I'm not sure if you mean or:
- Returning Error from handler as JSON
- Returning built-in Elysia errors like ValidationError, NotFound as JSON.
Either way, you should be able to use https://elysiajs.com/patterns/error-handling.html#catching-all-error
what he means is for elysia to automatically send back the validation error as JSON not in plain text
what should I do with this in my client side app? parse it?
Exactly @oSethoum!
If not automatically then it would be worth it to make it easier.
Automatically does make sense though, because going from JSON -> Text is super easy. But the other way around is much more tricky.
import { Elysia, t } from "elysia";
import { PrismaClient } from "@prisma/client";
const db = new PrismaClient();
const app = new Elysia()
.post(
"/sign-up",
async ({ body }) =>
db.user.create({
data: body,
}),
{
body: t.Object({
username: t.String(),
password: t.String({
minLength: 8,
}),
}),
}
)
.onError(({ code, error }) => {
switch (code) {
case "VALIDATION":
console.log(error.all);
return error.all;
default:
return {
name: error.name,
message: error.message,
};
}
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
I'm also looking for this support. At the moment, I can hack around it by adding this at the top of the Elysia chain:
.onError(({ error }) => {
return {errors: [error.message]};
})
But this doesn't get handled nicely by Eden since it doesn't know that {error: string} will now be a valid response type from all API endpoints, so you end up needing to hack around the missing types on the client:
import { edenTreaty } from '@elysiajs/eden';
import type { App } from 'backend';
const api = edenTreaty<App>('api/');
const {data, error} = api.someendpoint.get();
// Eden doesn't know that `data` will return `{errors: Array<string>}` when a failure occurs :(
// So we have to add a type assertion
const typedData = data as (typeof data) | {errors: Array<string>}
if (error) {
console.log("Failed due to", typedData.errors[0]);
}
Furthermore, onError doesn't seem to get called at all when using import {error} from 'elysia' mechanism. And I can't find any documentation about when it should be used.
Ok, I think I have something more-or-less functional in Elysia 1.0.
Step 1
Create a custom Error object as per the docs. In my case I called it APIError. This is needed so you can pass custom values (like status codes) through to Elysia's onError function.
Mine looks like this:
import type { StatusCodes } from 'http-status-codes';
export class APIError extends Error {
public readonly message: string;
public readonly httpCode: StatusCodes;
constructor(httpCode: StatusCodes, message: string, options?: ErrorOptions) {
super(message, options);
this.httpCode = httpCode;
this.message = message;
this.name = 'APIError';
Object.setPrototypeOf(this, APIError.prototype);
Error.captureStackTrace(this);
}
}
Step 2
In your route handlers, throw these custom errors as needed. Example:
new Elysia().post('/myendpoint', async () => {
if (somethingWentWrong) throw new APIError(422, 'Your custom error message here')
});
Step 3
In your global Elysia chain, add an onError handler that will intercept errors:
import type { ParseError, ValidationError } from 'elysia';
const errors = new Elysia()
.error({ APIError })
.onError({ as: 'global' }, ({ error, request, set, code }) => {
logger.error(error);
captureException(error);
let message = 'Unknown error';
set.status = StatusCodes.INTERNAL_SERVER_ERROR;
switch (code) {
case 'NOT_FOUND':
set.status = StatusCodes.NOT_FOUND;
message = `${request.method} ${request.url} is not a valid endpoint.`;
break;
case 'PARSE':
set.status = error.status;
message = error.message;
break;
case 'VALIDATION':
set.status = error.status;
// NOTE: You might want to change how these get returned to handle all possible typebox error states
message = error.all[0].schema.error;
break;
case 'APIError':
set.status = error.httpCode;
message = error.message;
break;
}
return Response.json(
{ error: message, otherstuff: 'whatever you want' },
{ status: set.status },
);
});
[!NOTE] Do not use
import {error} from 'elysia'. It seems to bypass theonErrorhandler.
Step 4
Seems to work fine in treaty as well, like so:
import { treaty } from '@elysiajs/eden';
import { app } from './index';
const api = treaty(app);
const { error, status } = await api.myendpoint.post();
console.log(error.value)
// => {error: 'Your custom error message here', otherstuff: 'whatever you want'}
Pitfalls
- Unfortunately
errorwill be typed asunknownin this case. It looks like there should be a way to fix this, but my TypeScript skills are not good enough to figure out how. Any help would be appreciated. - If you print
erroryou get this ugly/useless thing:Error: [object Object]. It's not obvious that you have to useerror.value.
💎 ev is offering a $100 bounty for this issue
👉 Got a pull request resolving this? Claim the bounty by adding @algora-pbc /claim #313 in the PR body and joining algora.io
All Elysia built-in errors should have been returning as JSON since ~0.7.0 by default
I'll try to describe the request and problem more clearly.
Elysia's "Error Handling" docs provide this basic example:
const app = new Elysia()
.onError(({ code, error }) => {
return new Response(error.toString())
})
.get('/', () => {
throw new Error('Server is during maintenance')
})
This results in a request to / returning a 200 text response "Error: Server is during maintenance".
Let's change this to return a JSON error instead:
const app = new Elysia()
.onError(({ code, error }) => {
// With this change, I'm hoping Elysia will now return {failure: 'Server is during maintenance'}
return {failure: error.message}
})
.get('/', () => {
throw new Error('Server is during maintenance')
})
Ok great! It works. A request to / now returns a 500 error with {"failure":"Server is during maintenance"}.
[!NOTE] Interestingly (but unrelated), a
return {failure: error.message}will set the response status to 500, butreturn Response.json({failure: error.message})will return a status of 200.
Now let's try to use this API from the client-side using Eden (as per the docs):
import { treaty } from '@elysiajs/eden'
import type { App } from '../src'
const app = treaty<App>('localhost:3000')
const { data, error } = await app.index.get();
if (error) {
// The desire is that `error` would have the type: `{failure: string}`
console.log(error);
}
However, instead, the type is derived as:
{
status: unknown;
value: unknown;
} | null
You can also see this on the Elysia docs website itself too:
Instead, I'm expecting error to have the type:
{
failure: string
}
Any update on this? I really don't like having status and value being of type unknown when their structure is known. Any one know of a way to cast the error in each request? Can it somehow be casted globally when instantiating the Eden client?