本文へ移動

ミドルウェア ​

ミドルウェアは、エンドポイントの Handler の前後で動作します。ディスパッチ前に Request を取得したり、ディスパッチ後に Response を操作したりできます。

ミドルウェアの定義 ​

  • ハンドラー:Response オブジェクトを返します。呼び出されるハンドラーは 1 つだけです。
  • ミドルウェア:await next() を実行して何も返さず、次のミドルウェアを呼び出します。または、Response を返して処理を早期終了します。

ハンドラーと同様に、app.use または app.HTTP_METHOD でミドルウェアを登録できます。これにより、パスとメソッドを簡単に指定できます。

ts
// match any method, all routes
app.use(logger())

// specify path
app.use('/posts/*', cors())

// specify method and path
app.post('/posts/*', basicAuth())

ハンドラーが Response を返すと、それがユーザーへのレスポンスとなり、処理が終了します。

ts
app.post('/posts', (c) => c.text('Created!', 201))

この例では、ディスパッチ前に 4 つのミドルウェアが次のように処理されます。

ts
logger() -> cors() -> basicAuth() -> *handler*

実行順序 ​

ミドルウェアの実行順序は、登録順で決まります。 最初に登録したミドルウェアの next より前の処理が最初に実行され、 next より後の処理が最後に実行されます。 次の例を見てください。

ts
app.use(async (_, next) => {
  console.log('middleware 1 start')
  await next()
  console.log('middleware 1 end')
})
app.use(async (_, next) => {
  console.log('middleware 2 start')
  await next()
  console.log('middleware 2 end')
})
app.use(async (_, next) => {
  console.log('middleware 3 start')
  await next()
  console.log('middleware 3 end')
})

app.get('/', (c) => {
  console.log('handler')
  return c.text('Hello!')
})

結果は次のとおりです。

middleware 1 start
  middleware 2 start
    middleware 3 start
      handler
    middleware 3 end
  middleware 2 end
middleware 1 end

ハンドラーやミドルウェアが例外を投げると、Hono が捕捉し、アプリケーションの app.onError() コールバックに渡すか、500 レスポンスに自動変換してからミドルウェアの呼び出し元へ返します。そのため、next() が例外を投げることはなく、try/catch/finally で囲む必要はありません。

組み込みミドルウェア ​

Hono には組み込みのミドルウェアがあります。

ts
import { Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'

const app = new Hono()

app.use(poweredBy())
app.use(logger())

app.use(
  '/auth/*',
  basicAuth({
    username: 'hono',
    password: 'acoolproject',
  })
)

注意

Deno では、Hono と異なるバージョンのミドルウェアも使用できますが、不具合の原因になる場合があります。 たとえば、次のコードはバージョンが異なるため動作しません。

ts
import { Hono } from 'jsr:@hono/hono@4.4.0'
import { upgradeWebSocket } from 'jsr:@hono/hono@4.4.5/deno'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket(() => ({
    // ...
  }))
)

独自のミドルウェア ​

app.use() 内に直接、独自のミドルウェアを記述できます。

ts
// Custom logger
app.use(async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

// Add a custom header
app.use('/message/*', async (c, next) => {
  await next()
  c.header('x-message', 'This is middleware!')
})

app.get('/message/hello', (c) => c.text('Hello Middleware!'))

ただし、app.use() 内に直接記述すると、再利用が難しくなります。そのため、ミドルウェアを別のファイルに分離できます。

ミドルウェアを分離する際に、Hono のファクトリーの createMiddleware() を使うと、context と next の型定義を維持できます。また、後続のハンドラーから、Context に set したデータに型安全にアクセスできます。

ts
import { createMiddleware } from 'hono/factory'

const logger = createMiddleware(async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

情報

createMiddleware にはジェネリクスを指定できます。

ts
createMiddleware<{Bindings: Bindings}>(async (c, next) =>

Next の後でレスポンスを変更する ​

また、必要に応じてレスポンスを変更するミドルウェアも作成できます。

ts
const stripRes = createMiddleware(async (c, next) => {
  await next()
  c.res = undefined
  c.res = new Response('New Response')
})

ミドルウェアの引数内でのコンテキストへのアクセス ​

ミドルウェアの引数内でコンテキストにアクセスするには、app.use が提供するコンテキスト引数を直接使います。次の例を参照してください。

ts
import { cors } from 'hono/cors'

app.use('*', async (c, next) => {
  const middleware = cors({
    origin: c.env.CORS_ORIGIN,
  })
  return middleware(c, next)
})

ミドルウェアでコンテキストを拡張する ​

ミドルウェアでコンテキストを拡張するには c.set を使います。createMiddleware 関数に { Variables: { yourVariable: YourVariableType } } を型引数として渡すと、型安全にできます。

ts
import { createMiddleware } from 'hono/factory'

const echoMiddleware = createMiddleware<{
  Variables: {
    echo: (str: string) => string
  }
}>(async (c, next) => {
  c.set('echo', (str) => str)
  await next()
})

app.get('/echo', echoMiddleware, (c) => {
  return c.text(c.var.echo('Hello!'))
})

連結したミドルウェア間の型推論 ​

.use() で複数のミドルウェアを連結すると、Hono は Variables の型を自動的に集約します。ミドルウェアチェーンの後に続くルートハンドラーは、先行するすべてのミドルウェアの変数に型安全にアクセスできます。

ts
import { createMiddleware } from 'hono/factory'

const authMiddleware = createMiddleware<{
  Variables: { user: { id: string; name: string } }
}>(async (c, next) => {
  c.set('user', { id: '123', name: 'Alice' })
  await next()
})

const dbMiddleware = createMiddleware<{
  Variables: { db: { query: (sql: string) => Promise<unknown> } }
}>(async (c, next) => {
  c.set('db', {
    query: async (sql) => {
      /* ... */
    },
  })
  await next()
})

const app = new Hono()
  .use(authMiddleware)
  .use(dbMiddleware)
  .get('/', (c) => {
    // Both `user` and `db` are available and type-safe
    const user = c.var.user // { id: string; name: string }
    const db = c.var.db // { query: (sql: string) => Promise<unknown> }
    return c.json({ user })
  })

これは、各 .use() 呼び出しが、統合した型を持つ新しい Hono インスタンスを返すためです。ミドルウェアを連結するたびに型が拡張されます。そのため、多くの用途では、統合された Env 型を事前に手動で宣言する必要がありません。

サードパーティーのミドルウェア ​

組み込みミドルウェアは外部モジュールに依存しませんが、サードパーティーのミドルウェアは外部ライブラリに依存できます。それらを使うことで、より複雑なアプリケーションを構築できます。

さまざまなサードパーティーのミドルウェアを利用できます。 たとえば、GraphQL サーバー、Sentry、Firebase 認証などのミドルウェアがあります。

MIT ライセンスで公開されています。