# Bookshelf.js 教程

> 原文： [http://zetcode.com/javascript/bookshelf/](http://zetcode.com/javascript/bookshelf/)

Bookshelf.js 教程展示了如何使用 Bookshelf.js ORM 在 JavaScript 中对数据库进行编程。 Bookshelf.js 构建在 Knex 之上。

## Bookshelf.js

Bookshelf.js 是基于 Knex SQL 查询生成器构建的 Node.js 的 JavaScript ORM。 它支持基于`promise`和传统的回调接口。 书架提供事务支持，渴望/嵌套渴望的关系加载，多态关联以及对一对一，一对多和多对多关系的支持。

Bookshelf.js 可与 PostgreSQL，MySQL 和 SQLite3 一起使用。

对象关系映射（ORM）是一种从面向对象的语言访问关系数据库的技术。 它是 Python 数据库 API 的抽象。 在本教程中，我们使用 PostgreSQL。

## 城市表

我们使用城市表。

`cities_postgresql.sql`

```js
DROP TABLE IF EXISTS cities;
CREATE TABLE cities(id serial PRIMARY KEY, name VARCHAR(255), population INT);

INSERT INTO cities(name, population) VALUES('Bratislava', 432000);
INSERT INTO cities(name, population) VALUES('Budapest', 1759000);
INSERT INTO cities(name, population) VALUES('Prague', 1280000);
INSERT INTO cities(name, population) VALUES('Warsaw', 1748000);
INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000);
INSERT INTO cities(name, population) VALUES('New York', 8550000);
INSERT INTO cities(name, population) VALUES('Edinburgh', 464000);
INSERT INTO cities(name, population) VALUES('Berlin', 3671000);

```

## 安装 Bookshelf.js

我们安装书架。

```js
$ node -v
v11.5.0

```

我们使用 Node 版本 11.5.0。

```js
$ npm init -y

```

我们启动一个新的 Node 应用。

```js
$ npm i pg
$ npm i knex bookshelf

```

我们安装了 PostgreSQL 驱动程序 Knex.js 和 Bookshelf.js。

## Bookshelf.js计数行

在第一个示例中，我们计算`cities`表中的行数。

`config/db.js`

```js
const knex = require('knex')({
    client: 'pg',
    connection: {
        host: '127.0.0.1',
        user: 'postgres',
        password: '',
        database: 'testdb',
        charset: 'utf8'
    }
});

module.exports.knex = knex;

```

在`db.js`文件中，我们定义一个 Knex 客户端对象。

`model/city.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const City = bookshelf.Model.extend({
    tableName: 'cities'
});

module.exports = City;

```

我们有模型对象。 模型对象映射到数据库表中的一行。

`count_cities.js`

```js
const knex = require('./config/db').knex;
const City = require('./model/city');

City.count().then((count) => {
    console.log(`There are ${count} cities`);
}).catch((err) => {
    console.log(err);
}).finally(() => {
    knex.destroy();
});

```

该示例计算`cities`表中的行数。 它使用回调。

```js
$ node count_cities.js
There are 8 cities

```

这是输出。

`count_cities2.js`

```js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function countCities() {

    try {

        let count = await City.count();

        console.log(`There are ${count} cities`);
    } catch (e) {

        logger.info(`No data found ${e}`);
    } finally {

        knex.destroy();
    }
}

countCities();

```

在第二个示例中，我们将`promise`与`async/await`一起使用。

## Bookshelf.js `fetch()`

`fetch()`使用当前在模型上设置的任何属性来从数据库获取模型，以形成选择查询。 设置`require`选项后，如果结果为空，则返回的响应将被`NotFoundError`拒绝。

```js
$ npm i winston

```

在此示例中，我们还使用 Winston 日志记录模块。

`fetch_city.js`

```js
const knex = require('./config/db').knex;
const City = require('./model/city');
const winston = require('winston');

const consoleTransport = new winston.transports.Console()
const options = {
    transports: [consoleTransport]
}
const logger = new winston.createLogger(options)

async function fetch_city() {

    try {

        let val = await City.where({ 'name': 'Bratislava' }).fetch({require:true});
        // console.log(val.toJSON());
        logger.info(val);
    } catch (e) {

        logger.info(`No data found ${e}`);
    } finally {

        knex.destroy();
    }
}

fetch_city();

```

该示例检索指定的城市。

```js
let val = await City.where({ 'name': 'Bratislava' }).fetch({require:true});

```

我们获取一个名为`'Bratislava'`的模型。

```js
logger.info(val);

```

我们记录返回的数据。

```js
$ node fetch_city.js
{"message":{"id":1,"name":"Bratislava","population":432000},"level":"info"}

```

这是输出。

## Bookshelf.js `fetch_all`

`fetch_all()`使用当前在模型上设置的任何查询参数从数据库中获取模型的集合，以形成选择查询。

`fetch_all.js`

```js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function fetch_all() {

    try {

        let vals = await City.fetchAll();
        console.log(vals.toJSON());
    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

fetch_all();

```

该示例检索所有城市。

```js
let vals = await City.fetchAll();

```

我们称为`fetchAll()`函数。

```js
console.log(vals.toJSON());

```

数据以 JSON 格式写入控制台。

```js
$ node fetch_all.js
[ { id: 1, name: 'Bratislava', population: 432000 },
    { id: 2, name: 'Budapest', population: 1759000 },
    { id: 3, name: 'Prague', population: 1280000 },
    { id: 4, name: 'Warsaw', population: 1748000 },
    { id: 5, name: 'Los Angeles', population: 3971000 },
    { id: 6, name: 'New York', population: 8550000 },
    { id: 7, name: 'Edinburgh', population: 464000 },
    { id: 8, name: 'Berlin', population: 3671000 } ]

```

这是输出。

## Bookshelf.js `forge()`辅助函数

Bookshelf 的`forge()`是一个简单的辅助函数，可在不需要`new`关键字的情况下实例化新模型。

`forge_helper.js`

```js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function fetch_city() {

    try {

        let val = await City.forge({ 'id': '4' }).fetch();
        console.log(val.toJSON());
    } catch (e) {

        console.info(`No data found ${e}`);
    } finally {

        knex.destroy();
    }
}

fetch_city();

```

在示例中，我们使用`forge()`帮助器选择一个城市。

## Bookshelf.js `save()`

用`save()`保存新模型。

`save_city.js`

```js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function save_city() {

    try {

        let val = await City.forge({ 'name': 'Kyiv', 'population': 2884000}).save();
        console.log(val.toJSON());
    } catch (e) {

        console.log(`Failed to save data: ${e}`);
    } finally {

        knex.destroy();
    }
}

save_city();

```

该示例将保存一个新城市。

```js
$ node save_city.js
{ name: 'Kyiv', population: 2884000, id: 9 }
$ node fetch_all.js
[ { id: 1, name: 'Bratislava', population: 432000 },
    { id: 2, name: 'Budapest', population: 1759000 },
    { id: 3, name: 'Prague', population: 1280000 },
    { id: 4, name: 'Warsaw', population: 1748000 },
    { id: 5, name: 'Los Angeles', population: 3971000 },
    { id: 6, name: 'New York', population: 8550000 },
    { id: 7, name: 'Edinburgh', population: 464000 },
    { id: 8, name: 'Berlin', population: 3671000 },
    { id: 9, name: 'Kyiv', population: 2884000 } ]

```

这是输出。

## Bookshelf.js `orderBy()`

`orderBy()`函数按指定的列名和排序顺序对检索到的数据进行排序。 `order`参数是可选的，默认为'ASC'。

`order_by.js`

```js
const knex = require('./config/db').knex;
const City = require('./model/city');

async function fetch_city() {

    try {

        let vals = await City.forge().orderBy('name', 'DESC').fetchAll({require:true});
        console.log(vals.toJSON());
    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

fetch_city();

```

在示例中，我们获取所有城市，并按名称降序排列它们。

```js
$ node order_by.js
[ { id: 4, name: 'Warsaw', population: 1748000 },
  { id: 3, name: 'Prague', population: 1280000 },
  { id: 6, name: 'New York', population: 8550000 },
  { id: 5, name: 'Los Angeles', population: 3971000 },
  { id: 9, name: 'Kyiv', population: 2884000 },
  { id: 7, name: 'Edinburgh', population: 464000 },
  { id: 2, name: 'Budapest', population: 1759000 },
  { id: 1, name: 'Bratislava', population: 432000 },
  { id: 8, name: 'Berlin', population: 3671000 } ]

```

这是输出。

## Bookshelf.js 一对一关系

一对一的关系由`hasOne()`和`belongsTo()`函数定义。

`employees_projects.sql`

```js
DROP TABLE IF EXISTS employees;
DROP TABLE IF EXISTS projects;

CREATE TABLE projects(id serial PRIMARY KEY, name VARCHAR(255));
INSERT INTO projects(name) VALUES('Project A');
INSERT INTO projects(name) VALUES('Project B');
INSERT INTO projects(name) VALUES('Project C');

CREATE TABLE employees(id serial PRIMARY KEY, project_id INT REFERENCES projects (id),
    name VARCHAR(255));
INSERT INTO employees(project_id, name) VALUES(2, 'John Doe');
INSERT INTO employees(project_id, name) VALUES(1, 'Lucia Smith');

```

我们有`employees`和`projects`。 员工只能分配到一个项目。

### Bookshelf.js `hasOne()`

`hasOne()`函数定义模型之间的一对一关系。 `hasOne`关系指定表恰好具有另一种对象类型，该对象由另一表中的外键指定。

`model/project.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);
const Employee = require('./employee');

const Project = bookshelf.Model.extend({
    tableName: 'projects',
    employee: function () {
        return this.hasOne(Employee);
    }
});

module.exports = Project;

```

`Project`模型包含`hasOne()`函数。 通过查询项目，我们可以获取其链接的员工。

`model/employee.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const Employee = bookshelf.Model.extend({
    tableName: 'employees'
});

module.exports = Employee;

```

这是`Employee`模型。

`has_one.js`

```js
const knex = require('./config/db').knex;
const Project = require('./model/project');

async function doQuery() {

    try {

        let val = await Project.where({ id: 2 }).fetch({
            withRelated: ['employee']
        });

        console.log(val.toJSON());
    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

doQuery();

```

在示例中，我们获取一个项目及其关联的员工。

```js
let val = await Project.where({ id: 3 }).fetch({
    withRelated: ['employee']
});

```

指定`withRelated`选项以获取集合的模型，并希望加载该模型上命名的任何指定关系。 如果没有此选项，我们将仅获得没有关联员工的项目。

```js
$ node has_one.js
{ id: 2,
  name: 'Project B',
  employee: { id: 1, project_id: 2, name: 'John Doe' } }

```

这是输出。

### Bookshelf.js `belongsTo()`一对一

当一个模型是另一个目标模型的成员时，将使用`belongsTo()`函数。 外键在当前（源）模型中定义。 `belongsTo()`函数用于一对一和`one-to-many`关系。

`model/project.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const Project = bookshelf.Model.extend({
    tableName: 'projects'
});

module.exports = Project;

```

这是`Project`模型。

`model/employee.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);
const Project = require('./project');

const Employee = bookshelf.Model.extend({
    tableName: 'employees',
    project: function () {
        return this.belongsTo(Project);
    }
});

module.exports = Employee;

```

`Employee`包含`belongsTo()`函数。

`belongs_to.js`

```js
const knex = require('./config/db').knex;
const Employee = require('./model/employee');

async function doQuery() {

    try {

        let val = await Employee.where({ id: 1 }).fetch({
            withRelated: ['project'], require: true
        });

        console.log(val.toJSON());

    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

doQuery();

```

在该示例中，我们通过其链接项目获取一名雇员。

```js
$ node belongs_to.js
{ id: 1,
    project_id: 2,
    name: 'John Doe',
    project: { id: 2, name: 'Project B' } }

```

这是输出。

## Bookshelf.js 一对多关系

一对多关系通过`hasMany()`和`belongsTo()`函数定义。

`users_tasks.js`

```js
DROP TABLE IF EXISTS tasks;
DROP TABLE IF EXISTS users;

CREATE TABLE users(id serial PRIMARY KEY, name VARCHAR(255));
INSERT INTO users(name) VALUES('John Doe');
INSERT INTO users(name) VALUES('Lucia Smith');

CREATE TABLE tasks(id serial PRIMARY KEY, user_id INT REFERENCES users (id),
    name VARCHAR(255));
INSERT INTO tasks(user_id, name) VALUES(1, 'Task A');
INSERT INTO tasks(user_id, name) VALUES(1, 'Task B');
INSERT INTO tasks(user_id, name) VALUES(1, 'Task C');
INSERT INTO tasks(user_id, name) VALUES(2, 'Task D');
INSERT INTO tasks(user_id, name) VALUES(2, 'Task E');

```

我们有`users`和`tasks`。 用户可以执行一个或多个任务。 一个任务只能由一个用户拥有。

### Bookshelf.js `hasMany()`

`hasMany()`定义了模型之间的一对多关系。 该关系指定当前模型在另一张表中具有一个或多个与该模型的主键匹配的行。

`model/user.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);
const Task = require('./task');

const User = bookshelf.Model.extend({
    tableName: 'users',
    tasks: function() {
        return this.hasMany(Task);
      }
});

module.exports = User;

```

`User`模型包含`hasMany()`函数。

`model/task.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const Task = bookshelf.Model.extend({
    tableName: 'tasks',
});

module.exports = Task;

```

这是`Task`模型。

`has_many.js`

```js
const knex = require('./config/db').knex;
const User = require('./model/user');

async function doQuery() {

    try {

        let val = await User.where({ id: 1 }).fetch({
            withRelated: ['tasks'], require: true
        });

        console.log(val.toJSON());

    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

doQuery();

```

在示例中，我们获取用户及其任务。

```js
$ node has_many.js
{ id: 1,
    name: 'John Doe',
    tasks:
    [ { id: 1, user_id: 1, name: 'Task A' },
        { id: 2, user_id: 1, name: 'Task B' },
        { id: 3, user_id: 1, name: 'Task C' } ] }

```

ID 为 1 的用户具有三个任务。

### Bookshelf.js `belongsTo()`一对多

在一对多的情况下，`belongsTo()`是`hasMany()`的倒数，并且是关联的一侧。

`model/user.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);

const User = bookshelf.Model.extend({
    tableName: 'users',
});

module.exports = User;

```

这是`User`模型。

`model/task.js`

```js
const knex = require('../config/db').knex;
const bookshelf = require('bookshelf')(knex);
const User = require('./user');

const Task = bookshelf.Model.extend({
    tableName: 'tasks',
    user: function() {
        return this.belongsTo(User);
    }
});

module.exports = Task;

```

`Task`模型包含`belongsTo()`函数。

`belongs_to2.js`

```js
const knex = require('./config/db').knex;
const Task = require('./model/task');

async function doQuery() {

    try {

        let val = await Task.where({ id: 4 }).fetch({
            withRelated: ['user'], require: true
        });

        console.log(val.toJSON());

    } catch (e) {

        console.log(`Failed to fetch data: ${e}`);
    } finally {

        knex.destroy();
    }
}

doQuery();

```

在示例中，我们与任务的关联用户一起获取任务。

在本教程中，我们使用书架库。 我们创建了一些与 PostgreSQL 交互的命令行程序。

您可能也对以下相关教程感兴趣： [Sequelize 教程](/javascript/sequelize/)， [Node Postgres 教程](/javascript/nodepostgres/)或 [JavaScript 教程](/all/#js)。