成为AI全栈 - 第4课:Drizzle ORM SQLite Elysia 数据库实战

从今天开始, 你是架构师,学会关键词就行,代码让AI实现😁


数据库就像持久化的 Excel,ORM 让你用代码操作它


今天你会学到这些关键词

| 关键词 | 一句话解释 | | :-- | :-- | | Drizzle ORM | TypeScript 友好的 ORM,用代码操作数据库 | | SQLite | 轻量级文件数据库,无需安装 | | Elysia | 高性能 Web 框架,链式 API 设计 | | 参数化查询 | 防止 SQL 注入的安全查询方式 |

一句话总结:用 Drizzle ORM + 参数化查询操作 SQLite,让 Elysia 服务拥有真正的数据持久化能力。


上节课回顾

上节课我们用 Elysia 实现了用户管理 API:

bash 复制代码
GET /users      → 查询所有用户
GET /users/:id  → 查询单个用户
POST /users     → 创建用户
PUT /users/:id  → 更新用户
DELETE /users/:id → 删除用户

但数据存在内存里:

bash 复制代码
const users = new Map();

问题:

  • • 服务重启,数据丢失

  • • 无法多服务共享数据

  • • 不能复杂查询

解决方案:使用数据库。


数据库是什么?

一句话:持久化存储数据的地方。

bash 复制代码
内存存储(Map/数组)    数据库(SQLite/MySQL)
─────────────────────────────────────────────
• 服务重启数据丢失      • 数据持久保存
• 存在内存里            • 存在硬盘上
• 简单快速              • 功能强大,支持复杂查询

类比:

  • • 内存存储 = 草稿纸,写完就扔

  • • 数据库 = 笔记本,永久保存


为什么选择 SQLite 学习数据库?

SQLite 的适用场景:

| 场景 | 是否适合 SQLite | | :-- | :-- | | 学习数据库基础 | ✅ 完美 | | 开发测试环境 | ✅ 快速搭建 | | 小型应用(用户 < 10万) | ✅ 推荐 | | 独立桌面/移动应用 | ✅ 推荐 | | 高并发写入(> 1000 QPS) | ❌ 不推荐 | | 多进程同时写入 | ❌ 不推荐 | | 需要网络访问 | ❌ 不推荐 |

为什么用 SQLite 学习 SQL?

bash 复制代码
传统数据库(MySQL/PostgreSQL)    SQLite
─────────────────────────────────────────────
• 需要安装数据库服务            • 零配置,零安装
• 需要启动数据库服务            • 直接打开文件就能用
• 配置复杂(用户、权限、端口)    • 就是一个 .db 文件
• 占用系统资源多                • 占用资源极少
• 命令行工具需要单独学习         • 直接用 AI 生成 SQL

SQLite 学习路线:

bash 复制代码
第4课(SQLite)→ 理解数据库和 ORM 基础
     ↓
第8课(PostgreSQL)→ Docker 部署生产级数据库
     ↓
第15课(MySQL)→ Java Spring Boot 生产环境

一句话:先用 SQLite 快速入门,等项目大了再换 PostgreSQL。


SQL 是什么?

SQL 是操作数据库的语言。

bash 复制代码
-- 查询所有用户
SELECT * FROM users;

-- 创建新用户
INSERT INTO users (name, email) VALUES ('张三', 'zs@example.com');

-- 更新用户
UPDATE users SET name = '李四' WHERE id = 1;

-- 删除用户
DELETE FROM users WHERE id = 1;

但我们不需要手写 SQL。


ORM 是什么?

ORM = 用代码操作数据库,不用写 SQL。

bash 复制代码
// 不用写 SQL,用代码操作
await db.insert(users).values({ name: "张三", email: "zs@example.com" });

// 自动转成:INSERT INTO users (name, email) VALUES (?, ?)

好处:

  • • ✅ 不用记 SQL 语法

  • • ✅ 类型安全,IDE 有提示

  • • ✅ 代码更易维护


Drizzle ORM 简介

Drizzle 是一个轻量级、类型安全的 ORM,完美支持 Bun.js 和 Node.js。

安装:

bash 复制代码
bun add drizzle-orm
bun add -d drizzle-kit

# Node.js 用户
npm install drizzle-orm
npm install -D drizzle-kit

用 AI 生成完整代码

复制这段提示词:

bash 复制代码
用 Bun.js + Drizzle ORM + SQLite 创建用户管理系统

要求:
1. 数据库配置
   - 使用 bun:sqlite(Bun 用户)或 better-sqlite3(Node.js 用户)
   - 数据库文件 app.db

2. 用户表结构:
   - id: 整数,自增主键
   - name: 文本,必填
   - email: 文本,必填,唯一
   - createdAt: 时间戳,默认当前时间

3. 实现 RESTful API:
   - GET /users - 查询所有用户
   - GET /users/:id - 查询单个用户
   - POST /users - 创建用户
   - PUT /users/:id - 更新用户
   - DELETE /users/:id - 删除用户

4. 安全要求:
   - 所有 SQL 使用 :name 占位符
   - 禁止字符串拼接 SQL

5. 统一响应格式:
   { success, data, message }

6. 使用 Elysia 框架

请生成完整的项目代码,包含:
- schema.ts - 表定义
- db.ts - 数据库连接
- index.ts - API 路由

AI 生成的代码结构

1. schema.ts - 定义表结构

bash 复制代码
import { sqliteTable, integer, text } from "drizzle-orm/sqlite-core";

export const users = sqliteTable("users", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  name: text("name").notNull(),
  email: text("email").notNull().unique(),
  createdAt: integer("created_at", { mode: "timestamp" })
    .notNull()
    .$defaultFn(() => new Date())
});

export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;

对应 SQL:

bash 复制代码
CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE,
  created_at INTEGER NOT NULL
);

2. db.ts - 数据库连接

💡 Bun 用户 :使用 bun:sqlite💡 Node.js 用户 :使用 better-sqlite3

bash 复制代码
// Bun 用户
import { Database } from "bun:sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite";
import * as schema from "./schema";

const sqlite = new Database("app.db");
export const db = drizzle(sqlite, { schema });

// 自动创建表
sqlite.run(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    created_at INTEGER NOT NULL DEFAULT (unixepoch())
  )
`);
bash 复制代码
// Node.js 用户
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite";
import * as schema from "./schema";

const sqlite = new Database("app.db");
export const db = drizzle(sqlite, { schema });

// 自动创建表
sqlite.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT NOT NULL UNIQUE,
    created_at INTEGER NOT NULL DEFAULT (unixepoch())
  )
`);

3. index.ts - API 实现

bash 复制代码
import { eq } from "drizzle-orm";
import { Elysia } from "elysia";
import { db } from "./db";
import { users } from "./schema";

const app = new Elysia()
  .onRequest(({ request }) => {
    console.log(`${request.method} ${new URL(request.url).pathname}`);
  })

  // 查询所有用户
  .get("/users", async () => {
    const allUsers = await db.select().from(users).all();
    return {
      success: true,
      data: allUsers,
      message: "查询成功"
    };
  })

  // 查询单个用户
  .get("/users/:id", async ({ params: { id }, set }) => {
    const user = await db.select().from(users).where(eq(users.id, Number(id))).get();

    if (!user) {
      set.status = 404;
      return { success: false, data: null, message: "用户不存在" };
    }

    return {
      success: true,
      data: user,
      message: "查询成功"
    };
  })

  // 创建用户
  .post("/users", async ({ body, set }) => {
    const { name, email } = body as { name: string; email: string };

    if (!name || !email) {
      set.status = 400;
      return { success: false, data: null, message: "name 和 email 不能为空" };
    }

    try {
      const result = await db.insert(users).values({
        name,
        email
      }).returning();

      set.status = 201;
      return {
        success: true,
        data: result[0],
        message: "创建成功"
      };
    } catch (error) {
      set.status = 400;
      return { success: false, data: null, message: "邮箱已存在" };
    }
  })

  // 更新用户
  .put("/users/:id", async ({ params: { id }, body, set }) => {
    const { name, email } = body as { name?: string; email?: string };

    const result = await db.update(users)
      .set({ name, email })
      .where(eq(users.id, Number(id)))
      .returning();

    if (result.length === 0) {
      set.status = 404;
      return { success: false, data: null, message: "用户不存在" };
    }

    return {
      success: true,
      data: result[0],
      message: "更新成功"
    };
  })

  // 删除用户
  .delete("/users/:id", async ({ params: { id }, set }) => {
    const result = await db.delete(users).where(eq(users.id, Number(id))).returning();

    if (result.length === 0) {
      set.status = 404;
      return { success: false, data: null, message: "用户不存在" };
    }

    return {
      success: true,
      data: null,
      message: "删除成功"
    };
  })

  .listen(3000);

console.log(`Server running at http://localhost:${app.server?.port}`);

💡 Node.js 用户注意:需要使用 Node 适配器

bash 复制代码
import { Elysia } from "elysia";
import { node } from "@elysiajs/node";

// ... 路由代码 ...

.listen(3000, node);  // ← 加上 node 适配器

运行测试

安装依赖:

bash 复制代码
bun install
# Node.js 用户
npm install

启动服务:

bash 复制代码
bun run index.ts
# Node.js 用户
npm run dev

测试接口:

bash 复制代码
# 创建用户
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "张三", "email": "zs@example.com"}'

# 查询所有用户
curl http://localhost:3000/users

# 服务重启后,数据还在!

核心概念对照

| 概念 | Drizzle 代码 | 对应 SQL | | :-- | :-- | :-- | | 查询所有 | db.select().from(users) | SELECT * FROM users | | 条件查询 | .where(eq(users.id, id)) | WHERE id = ? | | 插入 | db.insert(users).values({...}) | INSERT INTO users ... | | 更新 | db.update(users).set({...}) | UPDATE users SET ... | | 删除 | db.delete(users) | DELETE FROM users |


安全提示:参数化查询

❌ 错误写法(SQL 注入风险):

bash 复制代码
const sql = `SELECT * FROM users WHERE email = '${email}'`;
// 如果 email = "' OR '1'='1"
// 变成:SELECT * FROM users WHERE email = '' OR '1'='1'
// 结果:返回所有用户!

✅ 正确写法(Drizzle 自动参数化):

bash 复制代码
await db.select().from(users).where(eq(users.email, email));
// 自动使用占位符,安全!

核心收获

今天学习了:

数据库 = 持久化存储✅ ORM = 用代码操作数据库✅ Drizzle = Bun.js/Node.js 的 ORM 选择✅ 参数化查询 = 防止 SQL 注入


下节课预告

第5课:登录功能怎么实现?一文搞懂认证

我们将:

  • • 理解认证的概念

  • • 学习 JWT 工作原理

  • • 实现注册登录功能


思考题:

如果要给文章表添加一个外键关联用户(作者),表结构应该怎么设计?

欢迎在评论区分享你的设计。


如果觉得有帮助,欢迎点赞、在看、转发。

相关推荐
ascarl20101 小时前
Linux.do 帖子整理:AI 调用 Chrome DevTools 调试前端页面
linux·前端·人工智能
DanCheOo1 小时前
开源 | 我是怎么用 ai-memory 让 Cursor 每次开新对话都自动知道项目背景的
前端·人工智能·ai·ai编程
用户0534369380732 小时前
# LangChainRust Agent 引擎:Graph 构建到执行
后端
彭于晏Yan2 小时前
Spring Boot 聚合MongoDB查询
spring boot·后端·mongodb
MPGWJPMTJT2 小时前
告别手动切换 Node 版本:从 nvm 迁移到 Volta
前端
Nyarlathotep01132 小时前
并发集合类(3):LinkedBlockingQueue
java·后端
Apifox2 小时前
Apifox 近期更新|AI Agent Debugger、A2A Debugger、Postman API 导入、Ask AI 侧边栏对话
前端·人工智能·后端
嗷o嗷o2 小时前
Android 前台服务为什么越来越难用了?很多问题不是限制多,而是你任务模型就不该用 FGS
前端
摇滚侠2 小时前
软件开发外包项目组,如何提高代码质量和开发效率
java·开发语言·前端·ide·intellij-idea