Hono.js初探

3,724 阅读2分钟

为什么学习Hono

市面上已经有很多node.js的后端库和框架了,之前用过Nest.js做过一些小项目,功能很全,实现起来项目很快,但是很多功能对于我的小项目来说,实在浪费,而且封装太高,写起来自由度很低。

偶尔网上看到了Hono,看了一下文档:

  • 支持多种运行时(Bun、Deno、Node...)
  • 还支持JSX
  • typescript
  • 自带一些常用的功能:RPC、Validation
  • 常用中间件都有:Auth、JWT、加上三方估计有几十个

很好,学起来。

Hello Word

hono.dev/docs/gettin…

可以通过设置port来修改端口

import { Hono } from 'hono'

const app = new Hono()

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

const port = 8998; // 可以通过设置port来修改端口

console.log(`Server is running on port ${port}`);

serve({  fetch: app.fetch,  port,});

访问:[http://localhost:3000](http://localhost:3000)

路由

// HTTP Methods
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))

层级路由

const apiRoutes = app
    .basePath("/api")
    .route("/expenses", route1)
    .route("/", route2);

前面的basePath("/api")可以让所有路由前缀都加上/api

export const route1 = new Hono()
    .post("/", async (c) => {
        return c.json({ });
    });

通过http://localhost:3000/api/expenses就可以访问到上面的路由了

请求

获取请求的param和query

app.get('/posts/:id', (c) => {
  const page = c.req.query('page')
  const id = c.req.param('id')
  return c.text(`You want see ${page} of ${id}`)
})

查看结果:http://localhost:3000/posts/1?page=12

获取请求中body的内容

app.put("/posts/:id{[0-9]+}", async (c) => {
    const data = await c.req.json();
    return c.json(data);
})

响应

除了text()还有很多json()、html()、notFound()、redirect()等等,来让请求返回不同类型的数据;

html()可以直接返回JSX;

JSX

import type { FC } from 'hono/jsx'

const app = new Hono()

const Layout: FC = (props) => {
  return (
    <html>
      <body>{props.children}</body>
    </html>
  )
}

const Top: FC<{ messages: string[] }> = (props: {
  messages: string[]
}) => {
  return (
    <Layout>
      <h1>Hello Hono!</h1>
      <ul>
        {props.messages.map((message) => {
          return <li>{message}!!</li>
        })}
      </ul>
    </Layout>
  )
}

app.get('/', (c) => {
  const messages = ['Good Morning', 'Good Evening', 'Good Night']
  return c.html(<Top messages={messages} />)
})

export default app

只需要把文件后缀改为tsx,直接写JSX,很React;

验证器

通过 zod@hono/zod-validator 来实现验证器,检查客户端发过来的请求是否符合指定的数据格式;

安装:yarn add zod @hono/zod-validator

比如我们需要验证一个请求中,客户端发过来的数据格式必须是:

account: string;password: string

import { z } from "zod";import { zValidator } from "@hono/zod-validator";
const loginSchema = z.object({
    account: z.string(),
    password: z.string(),
})

app.post("/login", zValidator("json", loginSchema), async (c) => {
    // do something
    const user = c.req.valid("json");
    return c.json({  });
});

通过zod文档了解更多

而且ide是直接可以查看到 user 的类型提示;

RPC

还记得上面的路由吗?

const apiRoutes = app.basePath("/api")
    .route("/expenses", route1)
    .route("/", route2);

RPC包含服务端和客户端;

在项目下:我的frontend目录放着一个vite的vue项目;

server是我的后端的src目录

传统的方式,我们需要在前端项目中使用ajax请求后端接口获取到数据,然后渲染到页面上。

例如:fetch("http://localhost:3000/api")获取到数据,然后把获取的数据通过vue渲染到引擎上,但是后端返回的接口的数据格式,我们只能通过接口文档来知道,说个实话接口文档写的不咋地或者更新不及时,很浪费对接接口的时间;

RPC可以直接在前端通过.route1.$get()的方式来请求到数据,不需要关心请求地址、而且ide会自动补全返回的数据格式;

假设

后端提供了一个接口可以返回所有的文章:

const fakeData = [
    { id: 1, title: "Rent" },
    { id: 2, title: "Groceries" },
    { id: 3, title: "Utilities" },
];

export const postsRoute = new Hono()
    .get("/posts", async (c) => {
        return c.json({ expenses: fakeData });
    })

前端中传统方法通过ajax请求:http://localhost:3000/posts

// 这里我用fetch演示,实际上可能是axios之类的;
async function getAllPosts() {
    const res = await fetch("http://localhost:3000/posts");
    if (!res.ok) {
        throw new Error("server error");
    }
    const data = await res.json();
    return data;
}

但是这样有几个问题:这个data的数据类型是怎么样的,很可能我们还要看接口文档,写type;

但是RPC可以通过以下方式来获取数据

async function getAllExpenses() {
    const res = await api.posts.$get();
    if (!res.ok) {
        throw new Error("server error");
    }
    const data = await res.json();
    return data;
}

ide直接提供了类型提醒;

上面代码的api是怎么来的?

// @server是对应的server项目的目录地址,这里我用了alias,实际上可能是../../../server之类的
import { type ApiRoutes } from "@server/index";
import { hc } from "hono/client";

const client = hc<ApiRoutes>('http://localhost:3000/');
export const api = client.api;

这个ApiRoutes是什么,看服务端代码:

实现zod的一个验证器,

const route = app.post(
  '/posts',
  zValidator(
    'form',
    z.object({
      title: z.string(),
      body: z.string(),
    })
  ),
  (c) => {
    // ...
    return c.json(
      {
        ok: true,
        message: 'Created!',
      },
      201
    )
  }
)

然后导出

export type ApiRoutes = typeof route

这样就搞定了;

先写到这里,后面写写RPC做请求拦截、JWT