middleware icon indicating copy to clipboard operation
middleware copied to clipboard

Express middleware for submounting Hono app

Open sor4chi opened this issue 1 year ago • 15 comments

Which middleware is the feature for?

@hono/express

What is the feature you are proposing?

As I presented at Hono Conf 2024, if we can mount Hono as a sub-application of Express, it would enable a gradual migration from Express to Hono, which could increase the motivation to use Hono.

As a precedent, there is a framework with a similar motivation called @fastify/express.

I already have a POC implementation in hand, but I wanted to raise an issue for discussion. How do you think?

sor4chi avatar Jan 14 '25 23:01 sor4chi

https://github.com/sor4chi/middleware/tree/feat/express-hono-middleware/packages/express

sor4chi avatar Jan 14 '25 23:01 sor4chi

@sor4chi Thanks!

Related to #928 by @EdamAme-x

yusukebe avatar Jan 15 '25 02:01 yusukebe

Wow, we already had a proposal! I didn't notice because there was no issue, sorry.

sor4chi avatar Jan 15 '25 03:01 sor4chi

@sor4chi No problem!

Let's discuss it on #928.

yusukebe avatar Jan 15 '25 03:01 yusukebe

BTW, the code that @EdamAme-X is suggesting is Express on Hono, but what I want to propose here is Hono on Express.

In reality, it would be quite challenging for an organization that uses Express to suddenly replace it with Hono, so I would like to propose a bottom-up migration approach.

sor4chi avatar Jan 15 '25 03:01 sor4chi

I thought so too. Can't this package (@hono/connect and @hono/express) be combined into one?

In Hono

app.use(connect(helmet()))

In Express (Connect)

app.use(hono(secureHeaders()))

EdamAme-x avatar Jan 15 '25 03:01 EdamAme-x

const app = express()
app.use('/hono', hono(new Hono().get('/', (c) => c.text('Hello Hono in Express!'))))

app.listen(3000, () => {
  console.log('Server is running on http://localhost:3000')
})

It seems difficult.
I am imagining the submount of this kind of Hono application.

sor4chi avatar Jan 15 '25 06:01 sor4chi

@sor4chi @EdamAme-x

Ah, super sorry! I misunderstood. It can be enabled by using the Node Adapter:

import { Hono } from 'hono'
import { getRequestListener } from '@hono/node-server'
import express from 'express'

const hono = new Hono()
hono.get('/', (c) => c.text('Hello from Hono!'))

const app = express()
app.use('/hono', getRequestListener(hono.fetch))

app.listen(3000)

yusukebe avatar Jan 15 '25 07:01 yusukebe

Yes, that's true, but when considering usage in an actual environment, I think it will be necessary to pass things like loggers and DB connections, which can usually be included in the context, from Express to Hono.

sor4chi avatar Jan 15 '25 08:01 sor4chi

@yusukebe it works great. by the way, a similar approach is offered by better-auth the only important point is to use this before express.json() prev. body-parser

// index.ts
import { Hono } from 'hono'
import { getRequestListener } from '@hono/node-server'
import express from 'express'

const hono = new Hono()
hono.get('/', (c) => c.text('Hello from Hono!'))

const app = express()
app.use('/hono', getRequestListener(hono.fetch))

// !!! after hono routes 
app.use(express.json())

app.listen(3000)

reslear avatar Apr 21 '25 12:04 reslear

@yusukebe I found a little problem since Hono 404 is returned by default there is no way to use the root path, I wonder if there is a solution or a workaround, I would like to have some routing in the root.


// index.ts
import { Hono } from 'hono'
import { getRequestListener } from '@hono/node-server'
import express from 'express'

const app = express()
const hono = new Hono()
hono.get('/hello', (c) => c.text('Hello from Hono!'))

// 1. ⬇️ 
app.use('/', getRequestListener(hono.fetch))

// express middleware's
app.use(express.json())

// 2. ❌ - Now unavailable 
app.get('/test', (req, res) => res.send('test'))

// sad, can't do that in Hono. 
app.use((req, res, next) => {
  res.status(404).send('Route not found')
})

app.listen(3000)

if there is a way to prevent the Hono error behavior or turn it off ?

reslear avatar Apr 22 '25 03:04 reslear

what about this?

import express from 'express'
import {Hono} from 'hono';
import {getRequestListener, type HttpBindings} from '@hono/node-server';

const app = express()
const hono = new Hono<{Bindings: HttpBindings}>();
hono.get('/hono-test', (c) => c.text('Hello from Hono!'));
const honoMiddlewares = hono.router.match('*', '/*').length;

// let hono try to handle this route first
app.use((req, res, next) => {
  const [match] = hono.router.match(req.method, req.path);
  if (match.length > honoMiddlewares) return getRequestListener(hono.fetch)(req, res);

  next(); // <--- will only be called if hono doesn't have a handler for this route
});

app.get('/foo', (req, res) => res.send('Hello from express'))

app.listen(3000)

mbrevda avatar May 04 '25 14:05 mbrevda

@reslear

since Hono 404 is returned by default there is no way to use the root path, I wonder if there is a solution or a workaround, I would like to have some routing in the root.

Hmmmmm. I think there's no way because you use app.use('/', ...) and the Hono app is receiving all access.

yusukebe avatar May 14 '25 03:05 yusukebe

See my solution above, should allow any route to be defined in either hono or express

mbrevda avatar May 14 '25 05:05 mbrevda

Guys I had a npm package for hono sessions and have been using it in production for months now, I even shared it in hono discussions: https://github.com/orgs/honojs/discussions/3799

mguleryuz avatar Aug 07 '25 14:08 mguleryuz