elysiajs / elysia

Ergonomic Framework for Humans
https://elysiajs.com
MIT License
9.86k stars 211 forks source link

Way to get response in `onAfterHandle` to perform response transform #171

Closed masnormen closed 2 weeks ago

masnormen commented 12 months ago

Hi, I'm currently seeking way to transform response from a handler and/or on the global ElysiaApp.

{
  code: number,
  data: T, // <= T is the returned data from main handler
  status: boolean,
  message: "OK" | "ERROR"
}

I want all of my API's response shape to be uniform like above. But I don't necessarily want to return { code: xx, data: xx, ....... } on every handler.

Naturally I tried using onAfterHandle, but looks like it doesn't support getting response from the main handler. Sure there's a set object but it only allows setting headers.

This would also allow something like "Prety JSON Middleware" in Hono.js (https://hono.dev/middleware/builtin/pretty-json), which transforms the output into pretty JSON.

Is there a way to achieve this? Thank you.

bogeychan commented 12 months ago

Hi 👋

you could do something like this:

import { Elysia, type NotFoundError } from 'elysia';

type MyResponse<T = unknown> = {
  code: number;
  data: T;
  status: boolean;
  message: 'OK' | 'ERROR';
};

new Elysia()
  .onError((ctx) => {
    return {
      code: (ctx.error as NotFoundError).status ?? 500,
      data: ctx.error.message,
      status: false,
      message: 'ERROR'
    } satisfies MyResponse;
  })
  .onAfterHandle((ctx, response) => {
    return Response.json(
      {
        code: ctx.set.status ?? 200,
        data: response,
        status: true,
        message: 'OK'
      } satisfies MyResponse,
      ctx.set
    );
  })
  .get('/', () => {
    return {
      my: 'json'
    };
  })
  .get('/throw', () => {
    throw new Error('something went wrong!');
  })
  .listen(3000);

You have to wrap the response in Response.json until https://github.com/elysiajs/elysia/pull/167 is merged.

masnormen commented 12 months ago

Hi, thank you! Awesome. I didn't realize there's a second argument you can use in Handler 😅

I've read the docs a dozen times and looks like the response argument is not documented enough... I appreciate the amazing works of the maintainers though

However when I ran this code, I got an error because the schema I defined in response.200 doesn't match the type returned by onAfterHandle's transform.

new Elysia()
  .onAfterHandle((ctx, response) => {
    return Response.json(
      {
        code: ctx.set.status ?? 200,
        data: response,
        status: true,
        message: 'OK'
      } satisfies MyResponse,
      ctx.set
    );
  })
  .get('/', () => {
    return {
      name: 'json'
    };
  }, {
    response: {
      200: nameSchema
    }
  })
  .listen(3000);
Invalid response, 'name': Expected string

Expected: {
  "name": ""
}

Found: {}

when I remove the response property in the hook, I got the wanted result:

{"code":200,"data":{"name":"json"},"status":true,"message":"OK"}

If I read the docs correctly, Validation on Response must've happened right before After Handle, not after it. Is there something I'm missing or is this a bug?

adarshaacharya commented 2 months ago

Hi, thank you! Awesome. I didn't realize there's a second argument you can use in Handler 😅

I've read the docs a dozen times and looks like the response argument is not documented enough... I appreciate the amazing works of the maintainers though

However when I ran this code, I got an error because the schema I defined in response.200 doesn't match the type returned by onAfterHandle's transform.

new Elysia()
  .onAfterHandle((ctx, response) => {
    return Response.json(
      {
        code: ctx.set.status ?? 200,
        data: response,
        status: true,
        message: 'OK'
      } satisfies MyResponse,
      ctx.set
    );
  })
  .get('/', () => {
    return {
      name: 'json'
    };
  }, {
    response: {
      200: nameSchema
    }
  })
  .listen(3000);
Invalid response, 'name': Expected string

Expected: {
  "name": ""
}

Found: {}

when I remove the response property in the hook, I got the wanted result:

{"code":200,"data":{"name":"json"},"status":true,"message":"OK"}

If I read the docs correctly, Validation on Response must've happened right before After Handle, not after it. Is there something I'm missing or is this a bug?

did you found any solution for this, facing same issue

masnormen commented 1 month ago

@adarshaacharya Nah. I ended up wrapping every endpoint's response manually with a helper function.

So return ok(data) becomes { code: 200, data: data, ... }

I don't know if this issue still applies to the new Elysia versions though, as I haven't worked with the code again.

adarshaacharya commented 1 month ago

@masnormen i think that means we have to wrap every response schema with helper function as well.

I'm trying with latest version and facing same issue, guess we have to wrap every response + response schema with the utility functions :)

SaltyAom commented 2 weeks ago

Closing as implementation of https://elysiajs.com/life-cycle/map-response.html and https://elysiajs.com/life-cycle/after-handle.html

Feels free to reopen a new issue if this doesn't answer your question, thanks