个人对hono和bun很感兴趣,近期在学习相关知识,水一贴。当然,bun的安装,配置,和基本知识在此不赘述。
Q:为什么选择hono?
A:在参考众多node端的框架后,选择hono的原因有两点:首先api逻辑简单,没什么学习的历史包袱。如果使用装饰器我为什么不去使用spring呢?其次,hono很明显符合现在时代的发展:无论是用在边缘计算也好,还是某些简单项目的快速云上部署,Cloudflare,Vercel,AWL Cloud之类的,都很方便。
新建一个项目
首先我们新建一个hono项目
bun create hono@latest hono-in-bun
cd my-app
bun install
hono是个很轻量,很简单的api服务,启动一个服务的简单代码如下:
const app = new Hono ();
// 声明根路由的api响应
app.get('/', (ctx) =>
ctx.text('Hello Hono! ')
);
export default app;
我们只需要执行 bun run dev 即可,启动服务,可以通过导出port进行端口号指定:
import { Hono } from 'hono';
const app = new Hono ();
app.get('/', (ctx) =>
ctx.text('Hello Hono! ')
);
export default {
fetch: app.fetch,
// 自定义你的端口,可以选择想办法从env中获取
port: 3001,
};
路由声明
hono的路由声明十分简单,通配符部分,与tanstack-router,react-router等类似,就如同文章开头的那个路由。
由此我们可以拓展更多:
app.get('/item/:id', (ctx) => {
const id = c.req.param('id');
//查询后返回相关数据,逻辑代码
const item = await db.select().from().where(eq('item.id',id))
return ctx.json({ data:item });
});
app.delete('/:id', async (c) => {
const id = c.req.param('id');
// 删除相关记录
await db.delete(items).where(eq(items.id, id));
return c.json({ message: 'Item deleted', id });
})
中间件的使用
可以使用use语法去为服务载入中间件,让我们的服务更健壮,比如CORS,SecurityHeader,JWT等。
简单使用
我们可以在官网middleware一栏中找到很多hono官方做的中间件,我们可以先搞一个简单的Request Id中间件试试手:
import { Hono } from 'hono';
import { requestId } from 'hono/request-id';
const app = new Hono();
//通配所有路由
app.use('*', requestId());
app.get('/', (ctx) =>
ctx.text(`Hello Hono! Your request id: ${ctx.get('requestId')}`)
);
export default {
fetch: app.fetch,
port: 3001,
};
如果我们想让代码健壮,提示信息全一点,可以导入requestId的type。
import { Hono } from 'hono';
import { requestId } from 'hono/request-id';
import type { RequestIdVariables } from 'hono/request-id';
//声明类型后,context.get就会有代码提示
const app = new Hono<{
Variables: RequestIdVariables;
}>();
//通配所有路由
app.use('*', requestId());
app.get('/', (ctx) =>
ctx.text(`Hello Hono! Your request id: ${ctx.get('requestId')}`)
);
export default {
fetch: app.fetch,
port: 3001,
};
进阶使用
我们现在可以自定义一个好玩的中间件了,我们新建一个middlewares文件夹,创建我们第一个中间件。来为请求增加很多很好玩的response header信息。
import type { Context, Next } from 'hono';
export const setHeaders = async (ctx: Context, next: Next) => {
await next();
ctx.header('X-Powered-By', 'hono');
ctx.header('X-App-Id', 'hono-in-bun');
ctx.header('X-Add-123123', 'hhh');
};
根据官网文档所知,我们的中间件只需要满足一个简单的条件:中间件不应返回任何内容,且使用 await next() 继续处理下一个中间件。
我们通过ctx获取context,获取header方法去设置header相关字段,next来表示去执行下一个middleware。
还有一个很好玩的用法的例子,是request-logger这种类型的,需要在request 请求后执行的方法,我们可以用这种方式巧妙地实现:
import type { Context, Next } from 'hono';
import { logger } from '../libs/logger';
export const requstLogger = async (ctx: Context, next: Next) => {
const started = performance.now();
try {
await next();
} finally {
const duration = +(performance.now() - started).toFixed(2);
logger.info({
requestId: ctx.get('requestId'),
method: ctx.req.method,
path: ctx.req.path,
status: ctx.res.status,
durationMs: duration,
});
}
};
无需纠结logger是如何实现的,调用了什么工具类,重点是try...finally实现了这个功能,这个思路很值得学习。
除了手动声明,我们也可以使用hono提供的factory来生成中间件:
import { createMiddleware } from 'hono/factory'
const logger = createMiddleware(async (ctx, next) => {
console.log(`[${ctx.req.method}] ${ctx.req.url}`)
await next()
})
了解和明白这些以后,大概大家心里也有思路了,JWT,auth,或者什么其他基于完整/部分路由的特殊处理都可以交给中间件来处理了。
需要记住的地方
hono官方文档标注中间件按注册顺序依次进入;第一个中间件里 await next() 之前的逻辑最先执行;等内层全部跑完后,再按相反顺序执行 await next() 之后的逻辑(“回溯”阶段)。这是典型洋葱模型。
中间件报错后hono会主动捕获异常,要么传递给app.onError回调,要么在沿着中间件链向上返回之前自动将其转换为 500 响应。
小结
自己阅读文档,写代码时感觉理解的还挺到位,但是转化为文档的时候,就总会想着补全点,把之前“学来用”更深了一次,后续hono项目搭建和学习的相关文章会持续更新,直到项目搭建起来为止。会包含接入数据库,Redis等中间件,也有一些架构和openapi的相关搭建。