```html
Dans le monde en constante évolution du développement web, trouver le bon framework peut être un défi. Vous voulez quelque chose de rapide, léger et suffisamment flexible pour fonctionner dans différents environnements. Entrez Hono – un framework web qui gagne rapidement du terrain auprès des développeurs grâce à sa vitesse impressionnante, son empreinte minimale et sa conception conviviale pour les développeurs.
Hono (signifiant "flamme" 🔥 en japonais) porte bien son nom – il est incroyablement rapide et illumine votre expérience de développement avec son élégante simplicité. Que vous construisiez des API, des microservices ou des applications full-stack, Hono offre une alternative convaincante aux frameworks plus lourds.
Ce guide vous expliquera tout ce que vous devez savoir pour commencer avec Hono.js, de l'installation à la création de votre première application et à la compréhension de ses concepts de base.
Vous voulez une plateforme intégrée, tout-en-un, pour que votre équipe de développeurs travaille ensemble avec une productivité maximale ?
Apidog répond à toutes vos demandes et remplace Postman à un prix beaucoup plus abordable !
Qu'est-ce que Hono ?

Hono est un framework web petit, simple et ultra-rapide, basé sur les normes web. Il fonctionne sur pratiquement n'importe quel runtime JavaScript : Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, Netlify, AWS Lambda, Lambda@Edge et Node.js.
À la base, Hono adopte les API des normes web (comme Request, Response et Fetch), ce qui lui confère une portabilité remarquable sur toutes les plateformes. Cela signifie que vous pouvez écrire votre code une fois et le déployer presque n'importe où sans modifications majeures.
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hono!'))
export default app
Cet exemple simple démontre la syntaxe claire de Hono, inspirée d'Express mais modernisée pour l'écosystème JavaScript actuel.
Plongeons-nous !
1. Installer Hono.js avec Bun et configurer le projet
Bun est un runtime JavaScript moderne connu pour sa vitesse et sa boîte à outils tout-en-un, comprenant un gestionnaire de paquets, un bundler et un exécuteur de tests. La configuration de Hono avec Bun est simple.
Conditions préalables
Assurez-vous que Bun est installé. Sinon, vous pouvez l'installer en suivant les instructions sur le site officiel de Bun.
Création d'un nouveau projet Hono avec Bun
Le moyen le plus rapide de démarrer un nouveau projet Hono avec Bun est d'utiliser l'outil d'échafaudage create-hono
:
bun create hono@latest my-hono-app
Cette commande vous invitera à choisir un modèle. Pour ce guide, sélectionnez le modèle bun
.
? Which template do you want to use? › - Use arrow keys. Return to submit.
❯ bun
cloudflare-workers
cloudflare-pages
deno
fastly
netlify
nodejs
vercel
Après avoir sélectionné le modèle, accédez à votre nouveau répertoire de projet et installez les dépendances :
cd my-hono-app
bun install
Ajout de Hono à un projet Bun existant
Si vous avez un projet Bun existant, vous pouvez ajouter Hono en tant que dépendance :
bun add hono
2. Création d'une application de base avec Hono.js : "Hello Hono !"
Créons une simple application "Hello World" (ou plutôt, "Hello Hono !").
À l'intérieur de votre projet, vous devriez avoir un répertoire src
. Créez ou ouvrez src/index.ts
(ou index.js
si vous préférez JavaScript simple, bien que TypeScript soit recommandé pour tous les avantages de Hono).
// src/index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
export default app
Décomposons cela :
- Nous importons la classe
Hono
du paquethono
. - Nous créons une nouvelle instance de
Hono
, généralement nomméeapp
. - Nous définissons un itinéraire pour les requêtes
GET
vers le chemin racine (/
). - Le gestionnaire d'itinéraire est une fonction qui prend un objet
Context
(conventionnellementc
) comme argument. c.text('Hello Hono!')
crée un objet Response avec le texte brut "Hello Hono !".- Enfin, nous exportons l'instance
app
. Pour Bun, cette exportation par défaut est suffisante pour que le serveur de développement la récupère.
Exécution de votre application
Pour exécuter votre application en mode développement, utilisez le script dev
défini dans votre package.json
(que create-hono
configure pour vous) :
bun run dev
Cela démarrera généralement un serveur sur http://localhost:3000
. Ouvrez cette URL dans votre navigateur web et vous devriez voir "Hello Hono !".
Changement du port
Si vous devez exécuter votre application sur un port différent, vous pouvez modifier l'exportation dans src/index.ts
:
// src/index.ts
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono on port 8080!')
})
export default {
port: 8080, // Specify your desired port
fetch: app.fetch,
}
Maintenant, lorsque vous exécutez bun run dev
, l'application sera servie sur http://localhost:8080
.
3. Création d'une API de base avec Hono.js
Hono excelle dans la création d'API. Explorons le routage et la façon de gérer les requêtes et de créer des réponses.
Routage
Le routage dans Hono est intuitif. L'instance app
possède des méthodes correspondant aux verbes HTTP (.get()
, .post()
, .put()
, .delete()
, etc.).
Route GET de base
Nous avons déjà vu une route GET de base :
app.get('/hello', (c) => {
return c.text('Hello, world!')
})
Paramètres de chemin
Vous pouvez définir des itinéraires avec des paramètres de chemin en utilisant la syntaxe :paramName
. Ces paramètres sont accessibles via c.req.param('paramName')
.
// Example: /users/123
app.get('/users/:id', (c) => {
const userId = c.req.param('id')
return c.text(`User ID: ${userId}`)
})
Paramètres de requête
Les paramètres de requête de l'URL (par exemple, /search?q=hono
) sont accessibles à l'aide de c.req.query('queryName')
.
// Example: /search?category=frameworks&limit=10
app.get('/search', (c) => {
const category = c.req.query('category')
const limit = c.req.query('limit')
return c.text(`Searching in category: ${category}, Limit: ${limit}`)
})
Vous pouvez également obtenir tous les paramètres de requête sous forme d'objet avec c.req.query()
.
Gestion de différentes méthodes HTTP
Hono facilite la gestion de diverses méthodes HTTP pour le même chemin :
// src/index.ts
import { Hono } from 'hono'
const app = new Hono()
// GET a list of posts
app.get('/posts', (c) => {
// In a real app, you'd fetch this from a database
const posts = [
{ id: '1', title: 'Getting Started with Hono' },
{ id: '2', title: 'Advanced Hono Techniques' },
];
return c.json(posts); // Return JSON response
})
// GET a single post by ID
app.get('/posts/:id', (c) => {
const id = c.req.param('id')
// Fetch post by ID from a database
const post = { id: id, title: `Post ${id}`, content: 'This is the content...' };
if (!post) {
return c.notFound() // Built-in helper for 404
}
return c.json(post)
})
// CREATE a new post
app.post('/posts', async (c) => {
const body = await c.req.json() // Parse JSON body
// In a real app, you'd save this to a database
console.log('Creating post:', body)
return c.json({ message: 'Post created successfully!', data: body }, 201) // Respond with 201 Created
})
// UPDATE an existing post
app.put('/posts/:id', async (c) => {
const id = c.req.param('id')
const body = await c.req.json()
// Update post in database
console.log(`Updating post ${id}:`, body)
return c.json({ message: `Post ${id} updated successfully!`, data: body })
})
// DELETE a post
app.delete('/posts/:id', (c) => {
const id = c.req.param('id')
// Delete post from database
console.log(`Deleting post ${id}`)
return c.json({ message: `Post ${id} deleted successfully!` })
})
export default {
port: 3000,
fetch: app.fetch
}
Objet Request (c.req
)
L'objet Context
(c
) donne accès aux détails de la requête via c.req
. Les propriétés et méthodes clés incluent :
c.req.url
: la chaîne d'URL complète.c.req.method
: la méthode HTTP (par exemple, 'GET', 'POST').c.req.headers
: un objet Headers. Accédez aux en-têtes commec.req.header('Content-Type')
.c.req.param('name')
: obtenir un paramètre de chemin.c.req.query('name')
: obtenir un paramètre de requête.c.req.queries('name')
: obtenir toutes les valeurs d'un paramètre de requête répété sous forme de tableau.c.req.json()
: analyse le corps de la requête en tant que JSON. (Retourne une promesse)c.req.text()
: lit le corps de la requête en texte brut. (Retourne une promesse)c.req.arrayBuffer()
: lit le corps de la requête en tant qu'ArrayBuffer. (Retourne une promesse)c.req.formData()
: analyse le corps de la requête en tant que FormData. (Retourne une promesse)c.req.valid('type')
: accéder aux données validées (utilisé avec les validateurs, abordé plus tard).
Objet Response (c
) et assistants
L'objet Context
(c
) fournit également des assistants pratiques pour la création d'objets Response
:
c.text(text, status?, headers?)
: renvoie une réponse en texte brut.c.json(object, status?, headers?)
: renvoie une réponse JSON. LeContent-Type
est automatiquement défini surapplication/json
.c.html(html, status?, headers?)
: renvoie une réponse HTML.c.redirect(location, status?)
: renvoie une réponse de redirection (statut par défaut 302).c.notFound()
: renvoie une réponse 404 Not Found.c.newResponse(body, status?, headers?)
: crée un nouvel objetResponse
.body
peut êtrenull
,ReadableStream
,ArrayBuffer
,FormData
,URLSearchParams
oustring
.c.header(name, value)
: définit un en-tête de réponse.
Exemple : Définition d'en-têtes personnalisés
app.get('/custom-header', (c) => {
c.header('X-Powered-By', 'HonoJS-Flame')
c.header('Cache-Control', 'no-cache')
return c.text('Check the response headers!')
})
4. Utilisation des intergiciels dans Hono.js
Les fonctions d'intergiciel sont une pierre angulaire de Hono (et de nombreux frameworks web). Ce sont des fonctions qui peuvent traiter une requête avant qu'elle n'atteigne le gestionnaire d'itinéraire principal, ou traiter une réponse après l'exécution du gestionnaire. L'intergiciel est idéal pour des tâches telles que la journalisation, l'authentification, la validation des données, la compression, CORS, et plus encore.
La signature de base d'une fonction d'intergiciel est async (c, next) => { ... }
.
c
: l'objet Context.next
: une fonction à appeler pour passer le contrôle à l'intergiciel suivant dans la chaîne, ou au gestionnaire d'itinéraire final. Vous devezawait next()
si vous souhaitez que l'intergiciel suivant ou le gestionnaire s'exécute.
Utilisation de l'intergiciel intégré
Hono est livré avec un riche ensemble d'intergiciels intégrés. Vous pouvez les importer depuis hono/middleware-name
(par exemple, hono/logger
, hono/cors
).
Pour appliquer l'intergiciel à tous les itinéraires, utilisez app.use(middlewareFunction)
.
Pour appliquer l'intergiciel à des itinéraires spécifiques, fournissez un modèle de chemin comme premier argument : app.use('/admin/*', authMiddleware)
.
Exemple : Intergiciel Logger et ETag
// src/index.ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { etag } from 'hono/etag'
import { prettyJSON } from 'hono/pretty-json' // For nicely formatted JSON responses
const app = new Hono()
// Apply middleware to all routes
app.use(logger()) // Logs request and response info to the console
app.use(etag()) // Adds ETag headers for caching
app.use(prettyJSON()) // Formats JSON responses with indentation
app.get('/', (c) => {
return c.text('Hello with Logger and ETag!')
})
app.get('/data', (c) => {
return c.json({ message: 'This is some data.', timestamp: Date.now() })
})
export default app
Lorsque vous exécutez ceci et accédez à /
ou /data
, vous verrez des journaux dans votre console et les réponses incluront un en-tête ETag
. Les réponses JSON de /data
seront joliment formatées.
Autres intergiciels intégrés utiles :
cors
: gère le partage de ressources entre origines multiples.basicAuth
: implémente l'authentification de base.jwt
: authentification JWT (JSON Web Token).compress
: compresse les corps de réponse.cache
: implémente la mise en cache à l'aide de l'API Cache.secureHeaders
: ajoute divers en-têtes HTTP liés à la sécurité.bodyLimit
: limite la taille du corps de la requête.
Vous pouvez trouver une liste complète et de la documentation dans les documents Hono sous "Intergiciel".
Création d'un intergiciel personnalisé
Vous pouvez facilement écrire votre propre intergiciel.
Exemple 1 : Minuteur de requête simple
// src/index.ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
const app = new Hono()
app.use(logger())
// Custom middleware to measure request processing time
app.use(async (c, next) => {
const start = Date.now()
console.log(`Request received at: ${new Date(start).toISOString()}`)
await next() // Call the next middleware or route handler
const ms = Date.now() - start
c.header('X-Response-Time', `${ms}ms`) // Add custom header to the response
console.log(`Request processed in ${ms}ms`)
})
app.get('/', (c) => {
return c.text('Hello from Hono with custom timing middleware!')
})
app.get('/slow', async (c) => {
// Simulate a slow operation
await new Promise(resolve => setTimeout(resolve, 1500))
return c.text('This was a slow response.')
})
export default app
Accédez à /
et /slow
pour voir les journaux de synchronisation et l'en-tête X-Response-Time
.
Exemple 2 : Intergiciel d'authentification par clé API simple
Il s'agit d'un exemple très basique à des fins de démonstration. Dans une application réelle, utilisez des méthodes d'authentification plus robustes comme JWT ou OAuth, et stockez les clés API en toute sécurité.
// src/index.ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { HTTPException } from 'hono/http-exception' // For throwing standard HTTP errors
const app = new Hono()
app.use(logger())
const API_KEY = "supersecretapikey"; // In a real app, store this securely (e.g., env variable)
// Custom API Key Authentication Middleware
const apiKeyAuth = async (c, next) => {
const apiKeyHeader = c.req.header('X-API-KEY')
if (apiKeyHeader && apiKeyHeader === API_KEY) {
await next()
} else {
// Using HTTPException to return a standardized error response
throw new HTTPException(401, { message: 'Unauthorized: Invalid API Key' })
}
}
// Apply this middleware to a specific group of routes
app.use('/api/v1/*', apiKeyAuth)
app.get('/api/v1/me', (c) => {
return c.json({ user: 'Authenticated User', email: 'user@example.com' })
})
app.get('/public/info', (c) => {
return c.text('This is public information, no API key needed.')
})
// Error handler for HTTPException
app.onError((err, c) => {
if (err instanceof HTTPException) {
return err.getResponse()
}
// For other errors
console.error('Unhandled error:', err)
return c.text('Internal Server Error', 500)
})
export default app
Pour tester ceci :
curl http://localhost:3000/api/v1/me
(devrait renvoyer 401 Unauthorized)curl -H "X-API-KEY: supersecretapikey" http://localhost:3000/api/v1/me
(devrait renvoyer les données utilisateur)curl http://localhost:3000/public/info
(devrait renvoyer des informations publiques)
Réutilisation de l'intergiciel personnalisé avec createMiddleware
Pour un intergiciel plus complexe ou réutilisable, vous pouvez le définir séparément à l'aide de createMiddleware
de hono/factory
. Cela aide également à l'inférence de type TypeScript.
// src/middlewares/timing.ts
import { createMiddleware } from 'hono/factory'
export const timingMiddleware = createMiddleware(async (c, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
c.res.headers.set('X-Response-Time', `${ms}ms`) // Note: c.res.headers.set
console.log(` -> Processed in ${ms}ms`)
})
// src/middlewares/auth.ts
import { createMiddleware } from 'hono/factory'
import { HTTPException } from 'hono/http-exception'
const VALID_API_KEY = "anothersecretkey";
// Type for environment variables if your middleware needs them
type Env = {
Variables: {
user?: { id: string } // Example: set user data after auth
}
// Bindings: { MY_KV_NAMESPACE: KVNamespace } // Example for Cloudflare Workers
}
export const secureApiKeyAuth = createMiddleware<Env>(async (c, next) => {
const apiKey = c.req.header('Authorization')?.replace('Bearer ', '')
if (apiKey === VALID_API_KEY) {
// Optionally, you can set variables in the context for downstream handlers
c.set('user', { id: 'user123' })
await next()
} else {
throw new HTTPException(401, { message: 'Access Denied: Secure API Key Required.'})
}
})
// src/index.ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { timingMiddleware } from './middlewares/timing' // Assuming they are in a middlewares folder
import { secureApiKeyAuth } from './middlewares/auth'
const app = new Hono()
app.use(logger())
app.use(timingMiddleware) // Apply to all routes
app.get('/', (c) => {
return c.text('Hello from Hono with factored middleware!')
})
app.use('/secure/data/*', secureApiKeyAuth) // Apply only to /secure/data/*
app.get('/secure/data/profile', (c) => {
// const user = c.get('user') // Access variable set by middleware
return c.json({ profileData: 'Sensitive profile information', /*user: user*/ })
})
app.get('/public/data', (c) => {
return c.text('This is public data, no key needed.')
})
// General error handler
app.onError((err, c) => {
if (err instanceof HTTPException) {
return err.getResponse();
}
console.error('Error:', err.message);
return c.json({ error: 'Internal Server Error', message: err.message }, 500);
});
export default app
Cette structure rend votre intergiciel plus modulaire et plus facile à tester indépendamment.
5. Tester vos applications Hono.js
Hono est conçu pour être facilement testable. Bun est livré avec son propre exécuteur de tests, bun:test
, qui fonctionne bien avec Hono. Vous pouvez également utiliser d'autres exécuteurs de tests comme Vitest.
Utilisation de bun:test
(Basique)
La documentation bun.md
fournit un exemple de base de test avec bun:test
:
Créez un fichier de test, par exemple, src/index.test.ts
:
// src/index.test.ts
import { describe, expect, it } from 'bun:test'
import app from '.' // Imports your app from src/index.ts
describe('Hono Application Tests', () => {
it('Should return 200 OK for GET /', async () => {
const req = new Request('http://localhost/') // Base URL doesn't matter much here
const res = await app.fetch(req)
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello Hono!') // Or whatever your root route returns
})
it('Should return JSON for GET /posts', async () => {
const req = new Request('http://localhost/posts')
const res = await app.fetch(req)
expect(res.status).toBe(200)
const json = await res.json()
expect(json).toBeArray() // Assuming /posts returns an array
// Add more specific assertions about the JSON content if needed
})
it('Should create a post for POST /posts', async () => {
const postData = { title: 'Test Post', content: 'This is a test.' };
const req = new Request('http://localhost/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(postData),
});
const res = await app.fetch(req);
expect(res.status).toBe(201); // Expect 201 Created
const jsonResponse = await res.json();
expect(jsonResponse.message).toBe('Post created successfully!');
expect(jsonResponse.data.title).toBe(postData.title);
});
})
Pour exécuter vos tests :
bun test
Ou, si vous souhaitez exécuter un fichier spécifique :
bun test src/index.test.ts
Utilisation de l'assistant app.request()
Hono fournit une méthode pratique app.request()
qui simplifie les tests en vous permettant de passer directement un chemin et des options, plutôt que de construire un objet Request
complet à chaque fois. Ceci est illustré dans docs/guides/testing.md
.
// src/index.test.ts
import { describe, expect, it } from 'bun:test' // Or import from 'vitest'
import app from '.' // Your Hono app instance
describe('Hono App with app.request()', () => {
it('GET / should return "Hello Hono!"', async () => {
const res = await app.request('/')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello Hono!') // Adjust to your actual root response
})
it('GET /posts/:id should return a specific post', async () => {
// Assuming your app has a route like app.get('/posts/:id', ...)
// and for this test, it might return { id: '123', title: 'Test Post 123' }
const res = await app.request('/posts/123')
expect(res.status).toBe(200)
const data = await res.json()
expect(data.id).toBe('123')
// expect(data.title).toBe('Post 123') // Or based on your app's logic
})
it('POST /posts should create a new post', async () => {
const newPost = { title: 'My New Post', content: 'Awesome content here.' }
const res = await app.request('/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newPost),
})
expect(res.status).toBe(201) // Assuming your POST /posts returns 201
const responseData = await res.json()
expect(responseData.message).toBe('Post created successfully!')
expect(responseData.data.title).toBe(newPost.title)
})
it('GET /api/v1/me without API key should be 401', async () => {
// Assuming the API key middleware from earlier example is active on this route
const res = await app.request('/api/v1/me')
expect(res.status).toBe(401)
})
it('GET /api/v1/me with correct API key should be 200', async () => {
const res = await app.request('/api/v1/me', {
headers: {
'X-API-KEY': 'supersecretapikey' // Use the key from your middleware
}
})
expect(res.status).toBe(200)
const data = await res.json();
expect(data.user).toBe('Authenticated User')
})
})
Utilisation de l'assistant testClient()
pour des tests de type sécurisé
Pour une meilleure sécurité de type, en particulier si vous utilisez les capacités RPC de Hono ou si vous avez des schémas d'itinéraire bien définis, Hono fournit un assistant testClient
(de hono/testing
). Ce client est typé en fonction des itinéraires de votre application Hono, ce qui vous donne l'autocomplétion et la vérification de type dans vos tests.
Remarque importante pour l'inférence de type testClient
:
Pour que testClient
infère correctement les types, vous devez définir vos itinéraires à l'aide de méthodes enchaînées directement sur l'instance Hono
(par exemple, const app = new Hono().get(...).post(...)
) ou exporter le type d'itinéraire si vous utilisez RPC. Si vous définissez des itinéraires séparément (par exemple, const app = new Hono