Add NestJS Hono Adapter
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.
I have been interested in implementing this for some time, thanks for create the issue!
https://github.com/nestjs/nest/blob/master/packages/platform-fastify/adapters/fastify-adapter.ts
Is it type safe because the code contains so many any...?
Either way I would like to work on this.
[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..
fixing...
setted at Async function
Since there is no typed, it is difficult to determine the cause...
@drdreo Do you have any docs of this?
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.
Thanks for your reply. @drdreo It's difficut for me to understand, but I try it for now.
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!
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))
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.
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.
But if new HonoAdapter() can accept an exiting instance like new HonoAdapter(myHonoApp), maybe this can solve the problem.
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".
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
If anyone needs an adapter https://github.com/nestjs/nest/issues/13013#issuecomment-2147586323
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
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.
Thank for your reply @yusukebe I'll try it on bun
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.
Maybe @kamilmysliwiec could also contribute to it.
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)
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"));
}
}
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.