Node.js ORM 入门教程:用 Sutando 构建你的第一个应用
如果你刚开始接触 Node.js 的数据库操作,这篇教程就是为你准备的。我们将从零开始,用 Sutando 构建一个完整的应用,涵盖安装、配置、模型定义、CRUD 操作和关联查询。
如果你刚开始接触 Node.js 的数据库操作,这篇教程就是为你准备的。我们将从零开始,用 Sutando 构建一个完整的应用,涵盖安装、配置、模型定义、CRUD 操作和关联查询。
什么是 ORM?
ORM(对象关系映射)让你用代码对象操作数据库,而不是直接写 SQL。比如,不用写 SELECT * FROM users WHERE active = 1,而是写:
const users = await User.query().where('active', true).get();
Sutando 使用 Active Record 模式,每个模型类对应一张表,每个实例代表一行记录。
第 1 步:安装
mkdir my-app && cd my-app
npm init -y
npm install sutando mysql2
第 2 步:连接数据库
创建 db.ts:
import { sutando } from 'sutando';
sutando.addConnection({
client: 'mysql2',
connection: {
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '你的密码',
database: 'my_app'
}
});
export default sutando;
第 3 步:创建表
用 Schema 构造器创建表:
import sutando from './db';
async function setup() {
await sutando.schema().createTable('users', table => {
table.increments('id').primary();
table.string('name').notNullable();
table.string('email').notNullable().unique();
table.timestamps();
});
await sutando.schema().createTable('posts', table => {
table.increments('id').primary();
table.string('title').notNullable();
table.text('content');
table.integer('user_id').unsigned().references('id').inTable('users');
table.boolean('published').defaultTo(false);
table.timestamps();
});
console.log('表创建成功');
}
setup();
运行 npx tsx db.ts 创建表。
第 4 步:定义模型
创建 models.ts:
import { Model } from 'sutando';
class User extends Model {
table = 'users';
// 定义关联:一个用户有多篇文章
relationPosts() {
return this.hasMany(Post, 'user_id');
}
}
class Post extends Model {
table = 'posts';
// 类型转换
casts = {
published: 'boolean',
};
// 定义关联:一篇文章属于一个用户
relationUser() {
return this.belongsTo(User, 'user_id');
}
// 查询作用域:只查已发布的
scopePublished(query) {
return query.where('published', true);
}
}
export { User, Post };
第 5 步:CRUD 操作
创建
import { User, Post } from './models';
// 方式一:实例化后保存
const user = new User();
user.name = '张三';
user.email = 'zhangsan@example.com';
await user.save();
// 方式二:用 create 方法
const post = await Post.query().create({
title: '我的第一篇文章',
content: 'Hello World!',
user_id: user.id,
published: true,
});
查询
// 查询所有
const allPosts = await Post.query().get();
// 条件查询
const published = await Post.query()
.where('published', true)
.orderBy('created_at', 'desc')
.limit(10)
.get();
// 按 ID 查找
const post = await Post.query().find(1);
// 使用作用域
const publishedPosts = await Post.query().published().get();
// 预加载关联(避免 N+1 问题)
const users = await User.query().with('posts').get();
// users[0].posts → 该用户的所有文章
更新
const post = await Post.query().find(1);
post.title = '更新后的标题';
post.published = true;
await post.save();
// 批量更新
await Post.query().where('user_id', 1).update({ published: true });
删除
const post = await Post.query().find(1);
await post.delete();
// 批量删除
await Post.query().where('published', false).delete();
第 6 步:关联操作
创建关联记录
const user = await User.query().find(1);
const post = await user.related('posts').create({
title: '新文章',
content: '内容',
});
查询关联
const user = await User.query().find(1);
const publishedPosts = await user.related('posts').where('published', true).get();
嵌套预加载
// 一次查询加载用户 → 文章 → 评论 → 评论作者
const users = await User.query()
.with('posts.comments.user')
.get();
第 7 步:完整 API 示例
import express from 'express';
import './db';
import { User, Post } from './models';
const app = express();
app.use(express.json());
// 文章列表
app.get('/posts', async (req, res) => {
const posts = await Post.query()
.with('user')
.published()
.orderBy('created_at', 'desc')
.limit(20)
.get();
res.json(posts);
});
// 文章详情
app.get('/posts/:id', async (req, res) => {
const post = await Post.query().with('user').find(req.params.id);
if (!post) return res.status(404).json({ error: '文章不存在' });
res.json(post);
});
// 创建文章
app.post('/posts', async (req, res) => {
const post = await Post.query().create(req.body);
res.status(201).json(post);
});
// 更新文章
app.put('/posts/:id', async (req, res) => {
const post = await Post.query().find(req.params.id);
if (!post) return res.status(404).json({ error: '文章不存在' });
post.fill(req.body);
await post.save();
res.json(post);
});
// 删除文章
app.delete('/posts/:id', async (req, res) => {
const post = await Post.query().find(req.params.id);
if (!post) return res.status(404).json({ error: '文章不存在' });
await post.delete();
res.json({ success: true });
});
app.listen(3000, () => console.log('服务器运行在 http://localhost:3000'));
常见问题
什么是 N+1 问题?
// 不好:N+1 查询(1 次查用户 + N 次查每个用户的文章)
const users = await User.query().get();
for (const user of users) {
console.log(await user.getRelated('posts')); // 每个用户一次查询
}
// 好:2 次查询
const users = await User.query().with('posts').get();
什么是查询作用域?
作用域是可复用的查询条件,避免到处重复写 where('published', true):
// 定义
scopePublished(query) {
return query.where('published', true);
}
// 使用
const posts = await Post.query().published().get();
什么是模型事件?
在模型创建、更新、删除时自动执行的逻辑:
Post.creating(async (post) => {
post.slug = post.title.toLowerCase().replace(/\s+/g, '-');
});
下一步
你现在已经有了一个完整的 Sutando 应用。完整文档请访问 sutando.org。