从头开始创建一个自动产生文档/类型安全的现代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;

修改 .env :

1
2
3
4
NODE_ENV=development
PORT=3001
LOG_LEVEL=debug
DATABASE_URL=file:./dev.db

添加drizzle配置文件 drizzle.config.ts:

1
2
3
4
5
6
7
8
9
10
11
12
import { defineConfig } from "drizzle-kit";

import env from "@/utility/env";

export default defineConfig({
schema: "./db/schema.ts",
out: "./db/migrations",
dialect: "sqlite",
dbCredentials: {
url: env.DATABASE_URL,
},
});

连接数据库

创建文件 db/index.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createClient } from "@libsql/client";
import { drizzle } from "drizzle-orm/libsql";

import env from "@/utility/env";

import * as schema from "./schema";

const client = createClient({
url: env.DATABASE_URL,
authToken: env.DATABASE_AUTH_TOKEN,
});

const db = drizzle(client, {
schema,
});

export default db;

创建文件 db/schema.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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: integer("created_at", { mode: "timestamp" })
.$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" })
.$defaultFn(() => new Date())
.$onUpdate(() => new Date()),
});

export const selectTasksSchema = createSelectSchema(tasks);

生成数据库

配置完毕后,我们可以利用 drizzle-kit生成数据库:

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

此时查看,项目文件夹中会出现一个 dev.db 的文件,这个就是 SQLite 的数据库文件。

可以用以下命令,图形化访问数据库,添加数据:

1
bun drizzle-kit studio

这样访问 https://local.drizzle.studio/ 就可以看到数据库的情况,并对数据进行操作:

如果是操作的是远程服务器,直接访问这个地址就不行了,如果你用的是VS Code,可以开启端口转发,转发 4983 端口:

读取数据库/同步Openapi Doc

下面我们将API改为读取数据库。
修改文件 app/api/[[...route]]/routes/tasks/tasks.handlers.ts:

1
2
3
4
5
6
7
8
import { AppRouteHandler } from "@/utility/types";
import type { ListRoute } from "./tasks.routes";
import db from "@/db";

export const list: AppRouteHandler<ListRoute> = async (c) => {
const tasks = await db.query.tasks.findMany();
return c.json(tasks);
};

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

1
2
3
4
5
6
7
8
...
responses: {
[HttpStatusCodes.OK]: jsonContent(
z.array(selectTasksSchema),
"List of tasks",
),
},
...

用 scalar 查看 API

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

点击 Show Schema,可以看到详细的定义:

点击测试,可以看到读取数据库的结果:

作者:Bearalise
出处:从头开始创建一个自动产生文档/类型安全的现代API(7) 读数据库
版权:本文版权归作者所有
转载:欢迎转载,但未经作者同意,必须保留此段声明,必须在文章中给出原文链接。

请我喝杯咖啡吧~

支付宝
微信