Middleware
Un middleware intervient avant ou après le Handler d’un point de terminaison. Il permet d’accéder à la Request avant son traitement ou de modifier la Response après.
Définition d’un middleware
- Gestionnaire - doit renvoyer un objet
Response. Un seul gestionnaire est appelé. - Middleware - doit appeler
await next()et ne rien renvoyer pour appeler le middleware suivant, ou renvoyer uneResponsepour interrompre le traitement.
L’utilisateur peut enregistrer des middlewares avec app.use ou app.HTTP_METHOD, comme les gestionnaires. Il est ainsi facile de définir le chemin et la méthode.
// match any method, all routes
app.use(logger())
// specify path
app.use('/posts/*', cors())
// specify method and path
app.post('/posts/*', basicAuth())Si le gestionnaire renvoie une Response, celle-ci est envoyée à l’utilisateur final et le traitement s’arrête.
app.post('/posts', (c) => c.text('Created!', 201))Dans ce cas, quatre middlewares sont exécutés avant le traitement du gestionnaire, comme suit :
logger() -> cors() -> basicAuth() -> *handler*Ordre d’exécution
L’ordre d’exécution des middlewares dépend de leur ordre d’enregistrement. Le traitement précédant next dans le premier middleware enregistré est exécuté en premier, tandis que celui qui suit next est exécuté en dernier. Voyez ci-dessous.
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!')
})Le résultat est le suivant.
middleware 1 start
middleware 2 start
middleware 3 start
handler
middleware 3 end
middleware 2 end
middleware 1 endSi le gestionnaire ou un middleware lève une exception, Hono la capture et la transmet à votre callback app.onError(), ou la convertit automatiquement en réponse 500, avant de faire remonter la réponse dans la chaîne des middlewares. Cela signifie que next() ne lève jamais d’exception ; il n’est donc pas nécessaire de l’entourer d’un try/catch/finally.
Middlewares intégrés
Hono possède des middlewares intégrés.
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',
})
)Attention
Dans Deno, il est possible d’utiliser une version de middleware différente de celle de Hono, mais cela peut entraîner des bugs. Par exemple, ce code ne fonctionne pas car les versions sont différentes.
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(() => ({
// ...
}))
)Middlewares personnalisés
Vous pouvez écrire vos propres middlewares directement dans app.use() :
// 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!'))Cependant, intégrer un middleware directement dans app.use() peut limiter sa réutilisation. Vous pouvez donc le placer dans un fichier distinct.
Pour conserver les définitions de types de context et next lors de cette séparation, utilisez createMiddleware() de la fabrique Hono. Cela permet aussi aux gestionnaires suivants d’accéder avec sûreté aux données définies avec set dans Context.
import { createMiddleware } from 'hono/factory'
const logger = createMiddleware(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})Information
Vous pouvez utiliser des types génériques avec createMiddleware :
createMiddleware<{Bindings: Bindings}>(async (c, next) =>Modifier la réponse après Next
Les middlewares peuvent également être conçus pour modifier les réponses si nécessaire :
const stripRes = createMiddleware(async (c, next) => {
await next()
c.res = undefined
c.res = new Response('New Response')
})Accéder au contexte dans les arguments d’un middleware
Pour accéder au contexte dans les arguments d’un middleware, utilisez directement le paramètre de contexte fourni par app.use. L’exemple ci-dessous illustre cette approche.
import { cors } from 'hono/cors'
app.use('*', async (c, next) => {
const middleware = cors({
origin: c.env.CORS_ORIGIN,
})
return middleware(c, next)
})Étendre le contexte dans un middleware
Pour étendre le contexte dans un middleware, utilisez c.set. Pour assurer la sûreté des types, transmettez l’argument générique { Variables: { yourVariable: YourVariableType } } à la fonction createMiddleware.
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!'))
})Inférence de types dans les middlewares chaînés
Lorsque vous chaînez plusieurs middlewares avec .use(), Hono accumule automatiquement les types Variables. Les gestionnaires de route qui suivent la chaîne accèdent avec sûreté à toutes les variables des middlewares précédents :
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 })
})Chaque appel à .use() renvoie en effet une nouvelle instance Hono avec le type fusionné ; le type s’enrichit donc au fil de la chaîne. Dans la plupart des cas, il n’est plus nécessaire de déclarer manuellement un type Env combiné à l’avance.
Middlewares tiers
Les middlewares intégrés ne dépendent pas de modules externes, mais les middlewares tiers peuvent dépendre de bibliothèques tierces. Ils permettent donc de créer des applications plus complexes.
Vous pouvez découvrir différents middlewares tiers. Il existe notamment des middlewares GraphQL Server, Sentry et Firebase Auth.