Knex.js 查询构建器:灵活构建 SQL 语句
Knex.js 查询构建器:优雅地构建 SQL 语句
Knex.js 是一个功能强大的 Node.js SQL 查询构建器,它为 PostgreSQL、MySQL、SQLite3 等多种数据库提供了统一的接口。通过链式调用,你可以用 JavaScript 代码灵活地构建出安全、可读的 SQL 语句,而无需手动拼接字符串。本教程将带你从零开始,掌握 Knex 查询构建器的核心用法。
初始化和数据库连接
在开始构建查询之前,需要先安装 Knex 和对应的数据库驱动,并完成配置。
npm install knex pg # 以 PostgreSQL 为例
创建 knexfile.js 或直接通过配置对象初始化 Knex 实例:
const knex = require('knex')({
client: 'pg',
connection: {
host: '127.0.0.1',
port: 5432,
user: 'your_user',
password: 'your_password',
database: 'your_database'
}
});
连接对象也可以是一个指向环境变量的字符串,便于管理敏感信息。
基础查询语句
Knex 的查询通常以 knex('table_name') 开始,返回一个查询构建器对象,然后串联各种方法。
SELECT 查询
// 获取所有用户
knex.select('*').from('users')
// 或使用更简洁的写法
knex('users');
指定列、添加别名:
knex.select('id', 'name', 'email as user_email').from('users');
使用 where 条件筛选:
knex('users')
.where('age', '>', 18)
.andWhere('status', 'active');
复杂条件可以用回调函数分组:
knex('users').where(function() {
this.where('credits', '>', 100).orWhere('vip', true);
});
INSERT 语句
knex('users').insert({
name: 'Alice',
email: 'alice@example.com',
age: 28
});
批量插入传入数组:
knex('users').insert([
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Charlie', email: 'charlie@example.com' }
]);
某些数据库支持 .returning() 方法返回插入后的字段(如 PostgreSQL):
knex('users').insert({ name: 'Alice' }).returning('id');
UPDATE 语句
更新操作链式调用 .update() 和 .where():
knex('users')
.where('id', 1)
.update({ email: 'newalice@example.com' });
更新多列时,直接传入一个包含新值的对象。也可以使用 .increment() 和 .decrement() 方便地修改数值字段:
knex('users').where('id', 1).increment('login_count', 1);
knex('accounts').where('id', 10).decrement('balance', 50);
DELETE 语句
knex('users')
.where('id', 5)
.del()
.then(affectedRows => {
console.log(`删除了 ${affectedRows} 行`);
});
永远不要忘记 where 条件,否则会清空整张表。为了防止误操作,可以配置 Knex 不允许不带条件的删除(部分数据库驱动支持)。
高级查询技巧
JOIN 查询
Knex 提供了直观的 join 方法:
knex('users')
.join('posts', 'users.id', '=', 'posts.user_id')
.select('users.name', 'posts.title');
左连接:
knex('users')
.leftJoin('posts', 'users.id', 'posts.user_id')
.select('users.name', 'posts.title');
多表连接和复杂的 on 条件:
knex('users')
.join('orders', function() {
this.on('users.id', '=', 'orders.user_id')
.andOn('orders.status', '=', 'completed');
});
子查询和派生表
在 where 或 from 中使用子查询:
knex('users').whereIn('id', function() {
this.select('user_id').from('orders').where('amount', '>', 100);
});
将子查询作为派生表:
knex('users').join(
knex('orders').select('user_id').sum('amount as total').groupBy('user_id').as('order_summary'),
'users.id',
'order_summary.user_id'
);
聚合和分组
knex('orders')
.groupBy('status')
.select('status', knex.raw('count(*) as count'));
常用聚合函数如 .sum(), .avg(), .min(), .max() 可以直接使用:
knex('products').max('price as max_price').where('category', 'electronics');
排序、分页与限制
knex('posts')
.orderBy('created_at', 'desc')
.limit(10)
.offset(20); // 跳过前20条,取10条
.paginate() 方法可简化分页计算(需要额外安装或自行封装,常见用法是配合 limit 和 offset)。
原生表达式与事务
knex.raw 使用
当构建器无法满足需求时,可使用 knex.raw 编写安全原生 SQL:
knex('users')
.select(knex.raw('CURRENT_DATE AS today'))
.whereRaw('age > ? AND status = ?', [18, 'active']);
参数绑定使用 ? 占位符或 :name 命名参数(取决于数据库),防止 SQL 注入。
事务处理
通过 knex.transaction() 创建事务,自动提交或回滚:
knex.transaction(function(trx) {
return trx('users').insert({ name: 'Eve' }).returning('id')
.then(function([id]) {
return trx('profiles').insert({ user_id: id, bio: 'hello' });
});
})
.then(() => console.log('事务成功'))
.catch(err => console.log('事务回滚', err));
事务对象 trx 替代 knex 进行所有查询,确保原子性。
调试与日志
查看生成的 SQL 语句,可在查询链末尾添加 .toSQL() 或 .toString():
const query = knex('users').where('id', 1).toString();
console.log(query); // select * from "users" where "id" = 1
开发时启用 Knex 的调试模式打印所有查询:
const knex = require('knex')({
client: 'pg',
connection: {...},
debug: true // 输出每条SQL到控制台
});
或者监听 query 事件:
knex.on('query', queryData => {
console.log(queryData.sql, queryData.bindings);
});
实战示例:构建一个用户搜索 API
结合所学,完成一个支持分页和过滤的查询:
async function searchUsers({ keyword, status, page = 1, pageSize = 10 }) {
const offset = (page - 1) * pageSize;
const query = knex('users')
.modify(function(qb) {
if (keyword) {
qb.where('name', 'ilike', `%${keyword}%`);
}
if (status) {
qb.andWhere('status', status);
}
});
const [totalResult, users] = await Promise.all([
query.clone().count('id as total').first(),
query.clone().select('*').limit(pageSize).offset(offset)
]);
return {
data: users,
total: totalResult.total,
page,
pageSize
};
}
使用 .modify() 可以保持链式调用的同时复用条件构建逻辑,.clone() 则避免查询器被提前执行影响后续操作。
总结
Knex.js 查询构建器让 JavaScript 操作数据库变得既直观又安全。无论是简单的 CRUD 还是复杂的多表关联、事务处理,它都能提供清晰的链式语法。配合连接池和迁移工具,Knex 可以胜任从小型项目到企业级应用的数据库交互需求。开始尝试在你的项目中用 Knex 替换硬编码 SQL,体验无字符串拼接的开发乐趣。