从头开始创建一个自动产生文档/类型安全的现代API(10) 按id修改任务

下面我们给 API 添加按id修改任务。

添加根据 ID 修改任务

添加路径

添加路径,修改app/api/[[...route]]/routes/tasks/tasks.routes.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
...
import { notFoundSchema, IdParamsSchema, badRequestSchema } from "@/utility/constants";
...
export const patch = createRoute({
tags: ["Tasks"],
path: "/tasks/{id}",
method: "patch",
request: {
params: IdParamsSchema,
body: jsonContentRequired(
patchTasksSchema,
"The task to be changed",
)
},
responses: {
[HttpStatusCodes.OK]: jsonContent(
selectTasksSchema,
"The changed task",
),
[HttpStatusCodes.NOT_FOUND]: jsonContent(
notFoundSchema,
"The task id was not found",
),
[HttpStatusCodes.BAD_REQUEST]: jsonContent(
badRequestSchema,
"Update data is empty",
),
[HttpStatusCodes.UNPROCESSABLE_ENTITY]: jsonContent(
createErrorSchema(IdParamsSchema)
.or(createErrorSchema(patchTasksSchema)),
"The validation errors",
),
},
});
...
export type PatchRoute = typeof patch;
More...

如何修改 git 最近一次 commit 的描述

首先,确保你的工作树是干净的(即,所有更改都已经被提交或暂存)。

  1. 使用以下命令来修改最近一次 commit 的描述:

    1
    git commit --amend

    这将打开默认的文本编辑器,允许你编辑上次提交的 commit 信息。

  2. 修改描述后,保存并关闭编辑器。(我这边默认会打开Nano,按Ctrl+X 退出)新的描述会覆盖原来的描述,但请注意,这相当于创建了一个新的 commit,旧的 commit 会被删除。

  3. 如果你已经将代码推送到远程仓库,需要强制推送:

    1
    git push --force
More...

从头开始创建一个自动产生文档/类型安全的现代API(9) 建立校验规则/按id读取任务

下面我们给 API 参数添加校验功能。

在Schema中添加规则

修改 db/schema.ts:

1
2
3
4
5
6
7
8
9
10
11
12
...
export const insertTasksSchema = createInsertSchema(
tasks,
{
name: z.string().min(1).max(255),
done: z.boolean(),
},
).omit({
id: true,
createdAt: true,
updatedAt: true,
});

这里我添加了规则,name的长度只能在1到255之间,规避了0长度和过长的name。

访问 localhost:3000/reference, 可以看到API已更新:

More...

从头开始创建一个自动产生文档/类型安全的现代API(8) 创建数据

下面我们给API添加创建功能。

添加路径

修改文件 app/api/[[...route]]/routes/tasks/tasks.routes.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
export const create = createRoute({
tags: ["Tasks"],
path: "/tasks",
method: "post",
request: {
body: jsonContentRequired(
insertTasksSchema,
"The task to create",
)
},
responses: {
[HttpStatusCodes.OK]: jsonContent(
selectTasksSchema,
"The created task",
),
},
});

export type CreateRoute = typeof create;
...
More...

如何设置 Drizzle Schema 让 SQLite 支持自动填入创建时间

背景

之前用下面的Schema创建了表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export const tasks = sqliteTable("tasks", {
id: integer("id", { mode: "number" })
.primaryKey({ autoIncrement: true }),
name: text("name")
.notNull(),
done: integer("done", { mode: "boolean" })
.notNull()
.default(false),
createdAt: integer("created_at", { mode: "timestamp" })
.$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" })
.$defaultFn(() => new Date())
.$onUpdate(() => new Date()),
});

发现这个创建时间和更新时间并没有生效,研究了一下,这样写可能有问题。

解决方案

查询了一下,修改为下面的语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { sql } from "drizzle-orm";
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
import { createSelectSchema } from "drizzle-zod";

export const tasks = sqliteTable("tasks", {
id: integer("id", { mode: "number" })
.primaryKey({ autoIncrement: true }),
name: text("name")
.notNull(),
done: integer("done", { mode: "boolean" })
.notNull()
.default(false),
createdAt: text('created_at')
.notNull()
.default(sql`(current_timestamp)`),
updatedAt:text('updated_at')
.notNull()
.default(sql`(current_timestamp)`)
});

export const selectTasksSchema = createSelectSchema(tasks);

updateAt字段无效,需要更新时主动更新。

效果

修改完后,利用 drizzle-kit 重新生成数据库:

1
2
bun drizzle-kit generate
bun drizzle-kit push

试了一下,插入数据后自动产生了时间:

1
1|Hello Hone|0|2025-02-24 06:13:34|2025-02-24 06:13:34
More...

从头开始创建一个自动产生文档/类型安全的现代API(7) 读数据库

之前API的实现只是简单的返回固定值,本章开始实行数据库读写。

安装Drizzle

Drizzle ORM 是用于 SQL 数据库的 TypeScript ORM,在设计时考虑到了最大的类型安全性。它带有用于自动生成 SQL 迁移的 drizzle-kit CLI 。Drizzle ORM 是一个库,而不是一个框架,它的主要哲学是 “如果你知道 SQL,你就知道 Drizzle ORM”,因此设计的时候尽可能遵循类似 SQL 的语法,强类型化并在编译时就会失败,而不是在运行时。
安装 Drizzle, Drizzle 的安装根据支持的数据库,命令略有不同,这里我们采用 SQLite , 命令如下:

1
2
3
bun add drizzle-orm @libsql/client dotenv
bun add -D drizzle-kit tsx
bun add drizzle-zod

注:drizzle-zod 是Zod搭配drizzle的一个包,后面会用到。

定义环境变量

修改 utility/env.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { z } from "zod";
import { expand } from 'dotenv-expand';
import { config } from 'dotenv';

expand(config());

const EnvSchema = z.object({
NODE_ENV: z.string().default("development"),
PORT: z.coerce.number().default(9999),
LOG_LEVEL: z.enum(["fatal","error","warn", "info", "debug","trace"]),
DATABASE_URL: z.string().url(),
DATABASE_AUTH_TOKEN: z.string().optional(),
});

export type env = z.infer<typeof EnvSchema>;
// eslint-disable-next-line ts/no-redeclare
const parsedEnv = EnvSchema.safeParse(process.env);

if ( parsedEnv.success === false ) {
console.error("❌ Invalid env:");
console.error(JSON.stringify(parsedEnv.error.flatten().fieldErrors, null, 2));
process.exit(1);
}

const env = parsedEnv.data;

export default env;
More...

从头开始创建一个自动产生文档/类型安全的现代API(6) 创建子路径

创建子路径

上一篇我们创建了 api/节点,这次我们继续创建子节点,通过在Routes路径下创建子目录和文件实现。
首先, 添加 app/api/[[...route]]/routes/tasks/tasks.index.ts:

1
2
3
4
5
6
7
8
9
import { createRouter } from "../../lib/create-app";

import * as handlers from "./tasks.handlers";
import * as routes from "./tasks.routes";

const router = createRouter()
.openapi(routes.list, handlers.list);

export default router;

这个文件作为索引文件,链接了handler和router两个文件,我们来创建它们。

More...

从头开始创建一个自动产生文档/类型安全的现代API(5) 文档生成

添加Doc支持

添加 app/api/[[...route]]/lab/configure-openapi.ts:

1
2
3
4
5
6
7
8
9
10
11
12
import type { AppOpenAPI } from "@/utility/types";
import packageJSON from "@/package.json" with { type: "json" };

export default function configureOpenAPI(app : AppOpenAPI){
app.doc("/doc", {
openapi: "3.0.0",
info: {
version: packageJSON.version,
title: "Tasks API",
},
});
}

修改 app/api/[[...route]]/route.ts,添加调用:

1
2
3
4
5
6
7
...
import configureOpenAPI from './lab/configure-openapi';

const app = createApp();

configureOpenAPI(app);
...

访问 网址 localhost:3001/api/doc,显示如下:

More...

从头开始创建一个自动产生文档/类型安全的现代API(4) 启用Zod/代码重组

启用Zod

Zod 是一个 TypeScript 优先的模式声明和验证库。我使用术语 “模式” 来广义地指任何数据类型,从简单的 字符串 到复杂的嵌套对象。Zod 围绕尽可能友好的开发体验而设计。其目的是消除重复的类型声明。使用 Zod,你只需声明 一次 验证器,Zod 就会自动推断出静态 TypeScript 类型。将简单类型组合成复杂的数据结构非常容易。我的理解,它补足了 Typescript 在动态类型检查上的缺陷,让程序可以更稳定的运行,减少因为类型不匹配导致的问题。
可以用 bun add zod 安装,但实际上我们之前因为依赖,已经安装过了。

创建Zod Schema

创建文件 utility/env.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { z } from "zod";
import { expand } from 'dotenv-expand';
import { config } from 'dotenv';

expand(config());

const EnvSchema = z.object({
NODE_ENV: z.string().default("development"),
PORT: z.coerce.number().default(9999),
LOG_LEVEL: z.enum(["fatal","error","warn", "info", "debug","trace"]),
});

export type env = z.infer<typeof EnvSchema>;
// eslint-disable-next-line ts/no-redeclare
const { data: env, error } = EnvSchema.safeParse(process.env);

if (error) {
console.error("❌ Invalid env:");
console.error(JSON.stringify(error.flatten().fieldErrors, null, 2));
process.exit(1);
}

export default env;

其他代码中用到 process.env.xxx 都改成 env.xxx

More...

从头开始创建一个自动产生文档/类型安全的现代API(3) 日志搭建 - Bearalise

搭建日志平台Pino

Pino 是一个非常快速且简洁的 Node.js 日志库,其设计宗旨在于提供最小的开销以及高性能的日志记录功能。下面我们来在项目中搭建它。
安装:

1
bun add hono-pino pino

代码实现:
添加文件:middlewares/pino-logger.ts:

1
2
3
4
5
import { pinoLogger } from 'hono-pino';

export function pnLogger() {
return pinoLogger();
}

运行程序:bun run dev
访问localhost,返回 “Hello Hono”,同时控制台显示:
apidemo4

More...

请我喝杯咖啡吧~

支付宝
微信