hono icon indicating copy to clipboard operation
hono copied to clipboard

Add NestJS Hono Adapter

Open drdreo opened this issue 1 year ago • 27 comments

What is the feature you are proposing?

Hono Adapter for NestJs

https://hono.dev

Similar to https://docs.nestjs.com/techniques/performance#adapter

Reason

It would be amazing to have the choice for honojs in nestjs applications. Not only would it make hono more recognized in the JavaScript world, but also improve the NestJS ecosystem i believe.

Goal

The goal is to have a simple NestJS application adapter as we have for express and fastify. E.g.:

const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
await app.listen(3000);

and the ideal equivalent:

const app = await NestFactory.create<NestHonoApplication>(AppModule, new HonoAdapter());
await app.listen(3000);

Ideas From

Unfortunately, NestJS is not officially working on a Hono integration:

https://github.com/nestjs/nest/issues/13013

https://github.com/nestjs/nest/issues/13073#issuecomment-1902730322

but they are welcoming community contributions.

My attempt

My repo for testing, any help is appreciated: https://github.com/drdreo/nest-hono-adapter

I tried to get an adapter going but ran into roadblocks so i hope someone from here or the community can contribute and help me solve it.

Problems

For some reason, the response / hono context is not correctly propagated when calling the adapter methods. Instead, an async function is passed around, e.g. to the Nest reply(). Seems to be something like the next() handler function. I have also aligned to use the exact same underlying http.server as the express adapter with no luck.

getRequestHostname and co. seem to get the correct HonoContext.

drdreo avatar May 27 '24 16:05 drdreo

I have been interested in implementing this for some time, thanks for create the issue!

EdamAme-x avatar May 29 '24 12:05 EdamAme-x

https://github.com/nestjs/nest/blob/master/packages/platform-fastify/adapters/fastify-adapter.ts

EdamAme-x avatar May 30 '24 06:05 EdamAme-x

Is it type safe because the code contains so many any...? Either way I would like to work on this.

EdamAme-x avatar May 30 '24 06:05 EdamAme-x

[6:14:45 AM] Starting compilation in watch mode...

[6:14:49 AM] Found 0 errors. Watching for file changes.

[Nest] 5374  - 05/30/2024, 6:14:49 AM   DEBUG [HonoAdapter] constructor
[Nest] 5374  - 05/30/2024, 6:14:49 AM     LOG [NestFactory] Starting Nest application... +3ms
[Nest] 5374  - 05/30/2024, 6:14:49 AM     LOG [InstanceLoader] AppModule dependencies initialized +13ms
[Nest] 5374  - 05/30/2024, 6:14:49 AM   DEBUG [HonoAdapter] initHttpServer
[Nest] 5374  - 05/30/2024, 6:14:49 AM VERBOSE [HonoAdapter] skipping registerParserMiddleware
[Nest] 5374  - 05/30/2024, 6:14:49 AM     LOG [RoutesResolver] AppController {/}: +1ms
[Nest] 5374  - 05/30/2024, 6:14:49 AM     LOG [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 5374  - 05/30/2024, 6:14:49 AM     LOG [RouterExplorer] Mapped {/hono, GET} route +1ms
[Nest] 5374  - 05/30/2024, 6:14:49 AM   DEBUG [HonoAdapter] setting not found handler
[Nest] 5374  - 05/30/2024, 6:14:49 AM   DEBUG [HonoAdapter] setting error handler
[Nest] 5374  - 05/30/2024, 6:14:49 AM     LOG [NestApplication] Nest application successfully started +0ms
[Nest] 5374  - 05/30/2024, 6:14:49 AM   DEBUG [HonoAdapter] listening on port: 3000
[Nest] 5374  - 05/30/2024, 6:14:49 AM     LOG [Bootstrap] Application is running on: http://[::1]:3000
[Nest] 5374  - 05/30/2024, 6:15:00 AM   DEBUG [HonoAdapter] status: 200
[Nest] 5374  - 05/30/2024, 6:15:00 AM   DEBUG [HonoAdapter] reply: undefined
[Nest] 5374  - 05/30/2024, 6:15:00 AM   DEBUG [HonoAdapter] isHeadersSent
[Nest] 5374  - 05/30/2024, 6:15:00 AM   ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'get')
TypeError: Cannot read properties of undefined (reading 'get')
    at HonoAdapter.getHeader (/workspaces/nest-hono-adapter/adapter/hono-adapter.ts:246:33)
    at HonoAdapter.reply (/workspaces/nest-hono-adapter/adapter/hono-adapter.ts:143:42)
    at RouterResponseController.apply (/workspaces/nest-hono-adapter/node_modules/@nestjs/core/router/router-response-controller.js:15:36)
    at /workspaces/nest-hono-adapter/node_modules/@nestjs/core/router/router-execution-context.js:176:48
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at /workspaces/nest-hono-adapter/node_modules/@nestjs/core/router/router-execution-context.js:47:13
    at Array.<anonymous> (/workspaces/nest-hono-adapter/node_modules/@nestjs/core/router/router-proxy.js:9:17)
    at responseViaResponseObject (/workspaces/nest-hono-adapter/node_modules/@hono/node-server/dist/index.js:368:13)

/workspaces/nest-hono-adapter/node_modules/@hono/node-server/dist/index.js:207
  for (const [k, v] of headers) {
                       ^
TypeError: headers is not iterable
    at buildOutgoingHttpHeaders (/workspaces/nest-hono-adapter/node_modules/@hono/node-server/dist/index.js:207:24)
    at responseViaResponseObject (/workspaces/nest-hono-adapter/node_modules/@hono/node-server/dist/index.js:374:27)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)

hmm..

EdamAme-x avatar May 30 '24 06:05 EdamAme-x

fixing...

EdamAme-x avatar May 30 '24 06:05 EdamAme-x

image setted at Async function

EdamAme-x avatar May 30 '24 06:05 EdamAme-x

Since there is no typed, it is difficult to determine the cause...

EdamAme-x avatar May 30 '24 06:05 EdamAme-x

@drdreo Do you have any docs of this?

EdamAme-x avatar May 30 '24 06:05 EdamAme-x

Not sure this issue is the best place to have the debug conversation. But thanks for starting to look into it.

Is it type safe because the code contains so many any

From how they've done it with the express and fastify adapter, i am not sure Nest provides the types (defaults to any), but rather the adapter should https://github.com/nestjs/nest/blob/master/packages/core/adapters/http-adapter.ts#L12

So far i have relied on the old console.log and Node debugger to find the types of objects which are passed around. But as you figured out, there is something odd going on with the objects passed to the adapter (Async Function). Therefore, I have copied the express adapter as well, to compare with a working solution. And that one has completely different objects passed to the same interface functions. Even if the http server under the hood is the same.

Do you have any docs of this?

Unfortunately not. I'm trying to do some 'reverse engineering' to figure out how the adapter is supposed to work.

Perhaps some misconfiguration that i'm missing to tell Nest this adapter is valid. I only found that the notFound handler works and serves requests properly.

drdreo avatar May 30 '24 08:05 drdreo

Thanks for your reply. @drdreo It's difficut for me to understand, but I try it for now.

EdamAme-x avatar May 30 '24 09:05 EdamAme-x

Hi @drdreo

It would be amazing to have the choice for honojs in nestjs applications. Not only would it make hono more recognized in the JavaScript world, but also improve the NestJS ecosystem i believe.

This makes sense to me. Great.

What we have to consider it should we include the adapter into the core package like hono/vercel or hono/cloudflare-pages. This is ideal, but it should not dependent on other external libraries if you want to put it into the core. So, if you cant do that, it will be a nice idea to make it as 3rd party middleware or the community middleware which is hosted by the community, not in honojs org.

If I have time, I'll check the code. Thanks!

yusukebe avatar May 31 '24 20:05 yusukebe

I'd like to make this happen, is there any latest code I can copy & paste to try this out ?

I thinks the other way is to make nestjs work like a fetch handler, so I can honoApp.use('/api',createNestJSHandler(nestApp))

wenerme avatar Jun 02 '24 15:06 wenerme

I thinks the other way is to make nestjs work like a fetch handler, so I can honoApp.use('/api',createNestJSHandler(nestApp))

Nest.js is based on express, so it's slow. If we use .use, it will still be slow.

EdamAme-x avatar Jun 02 '24 23:06 EdamAme-x

For me, the usability > speed, I don't want to have tow nest application, one for hono+yoga+type-graphql, one for nestjs+fastify+rest, I hope I can use one unified fetch like application. Event there is hono nestjs adapter, I think I still have to start a hono for yoga, I prefer type-graphql over nestjs and Request&Response like api, just like golang's http handler.

wenerme avatar Jun 03 '24 01:06 wenerme

But if new HonoAdapter() can accept an exiting instance like new HonoAdapter(myHonoApp), maybe this can solve the problem.

wenerme avatar Jun 03 '24 01:06 wenerme

For me, the usability > speed, I don't want to have tow nest application, one for hono+yoga+type-graphql, one for nestjs+fastify+rest, I hope I can use one unified fetch like application. Event there is hono nestjs adapter, I think I still have to start a hono for yoga, I prefer type-graphql over nestjs and Request&Response like api, just like golang's http handler.

Thank for your reply. hmm... I'll measure the speed of "Nest.js on Hono".

EdamAme-x avatar Jun 03 '24 01:06 EdamAme-x

I'd like to make this happen, is there any latest code I can copy & paste to try this out ?

Sorry for my late response, @wenerme you can experiment all you like in my repo https://github.com/drdreo/nest-hono-adapter it includes a test hono app, test nest app and the adapter code

drdreo avatar Jun 04 '24 06:06 drdreo

If anyone needs an adapter https://github.com/nestjs/nest/issues/13013#issuecomment-2147586323

kiyasov avatar Jun 04 '24 18:06 kiyasov

If anyone needs an adapter nestjs/nest#13013 (comment)

image

it works and great works bro!!!

EdamAme-x avatar Jun 04 '24 23:06 EdamAme-x

In gitpod (Large) and AutoCannon.

Benchmark Results

Hello World Application
  • Express 66k requests in 11.02s 66k requests in 11.02s
  • Hono 75k requests in 11.02s 77k requests in 11.02s

Even for applications that only return "hello world" as a response, the difference is 1.15x. I would like to know the benchmarks for other apps.

EdamAme-x avatar Jun 05 '24 00:06 EdamAme-x

@EdamAme-x

If you run it on Node.js with @hono/node-server, the speed performance is not so different between Hono and Express. Especially if the application handles the request body like using c.req.json(). But Hono will be fast on non-Node.js environments like Cloudflare, Deno, or Bun. So, I am interested in whether NextJS will work on these runtimes.

yusukebe avatar Jun 05 '24 00:06 yusukebe

Thank for your reply @yusukebe I'll try it on bun

EdamAme-x avatar Jun 05 '24 02:06 EdamAme-x

Thank you for mentioning my issue. I will do some tests on your repo. Have a great day. But if you want to contact with me in discord based on this issue my username @a4arpon Feel free to knock me.

a4arpon avatar Jun 14 '24 08:06 a4arpon

Maybe @kamilmysliwiec could also contribute to it.

rafaell-lycan avatar Nov 05 '24 17:11 rafaell-lycan

In gitpod (Large) and AutoCannon.

Benchmark Results

Hello World Application
  • Express 66k requests in 11.02s 66k requests in 11.02s
  • Hono 75k requests in 11.02s 77k requests in 11.02s

Even for applications that only return "hello world" as a response, the difference is 1.15x. I would like to know the benchmarks for other apps.

@EdamAme-x Cool! Thanks for the bench, wouldn't it be nice to have in the results also the Fastify adapter? Maybe in a reproducible repo...

IMHO, the adapter should be added as parte of the Nest docs or at least here https://github.com/nestjs/awesome-nestjs (@kiyasov)

andreafspeziale avatar Dec 08 '24 10:12 andreafspeziale

I implemented HonoAdapter. https://github.com/asnowc/nest-hono-adapter

I can't speak English, so I translated what I wanted to say

My design might not be perfect. Therefore, I shared the problems I encountered here to facilitate those who need to re-implement the Hono adapter.

In fact, you mainly need to implement TRequest and TResponse in AbstractHttpAdapter<TServer = any, TRequest = any, TResponse = any>. But there is no official constraint on TRequest and TResponse

TRequest and TServer need to satisfy the following constraints:

type AbstractHttpAdapter<TServer extends NestHttpServerRequired= any, TRequest extends NestReqRequired= any, TResponse = any>= unknown

interface NestHttpServerRequired {
  once(event: "error", listener: (err: any) => void): this;
  removeListener(event: "error", listener: (...args: any[]) => void): this;
  // An object or string must be returned, otherwise the Promise returned by NestApplication.listen() will not be used for resolution
  address(): ReturnType<Server["address"]>;
}

interface NestReqRequired {
  rawBody?: any;
  session?: any; // @Session()
  files?: any;
  body: Record<string, any>; //@Body()
  params: Record<string, any>;; //@Param()
  ip?: string; // @Ip()
  hosts: Record<string, any>; //@HostParam()
  query: Record<string,any>; //@Query()
  headers: Record<string, any>; //@Headers()
}

If these constraints are not met, such as if some necessary properties are missing, then an exception will occur when the corresponding decorator is used. see https://github.com/nestjs/nest/blob/master/packages/core/router/route-params-factory.ts

So far, I don't quite understand what files and hosts are useful for

Hono is a little different from express and fastify in that Hono returns a response via a function, while express and fastify manually call TResponse.send() The response is returned in this way, which makes implementing HonoAdapter a bit difficult. Also note that if the response example is retrieved via the @Res() decorator, nest Won't call AbstractHttpAdapter. Reply (), but by TResponse by the developer to decide when to send the response.

So, I designed HonoResponse

import { Context } from "hono";
type HonoResponse = Omit<Context, "req"> & {
  send(data?: any): void;
};

@Controller()
class ExampleController {
  @Get("res")
  res(@Res() res: HonoResponse) {
    res.send(res.text("/res"));
  }
}

eavidy avatar Jan 21 '25 12:01 eavidy

I created Nest.js style framework on top of Hono.js.

https://honestjs.dev

https://github.com/honestjs/honest

It is just a wrapper around Hono.js, so it is very small & simple, but can be deployed anywhere.

kerimovok avatar Aug 16 '25 18:08 kerimovok