Учебник по SQLite с Node.js

В этом руководстве я продемонстрирую, как использовать SQLite в сочетании с JavaScript внутри среды Node.js с помощью драйвера sqlite3 Node.js. Для тех, кто не знаком с SQLite [https://en.wikipedia.org/wiki/SQLite], это простая однофайловая реляционная база данных, очень популярная среди интеллектуальных устройств, встроенных систем и даже небольших веб-приложений. Настройка и установка Я начну с создания нового пакета npm с помощью npm init внутри пустого каталога с именем node.

В этом руководстве я продемонстрирую, как использовать SQLite в сочетании с JavaScript внутри среды Node.js с помощью драйвера sqlite3 Node.js. Для тех, кто не знаком с SQLite , это простая однофайловая реляционная база данных, которая очень популярна среди интеллектуальных устройств, встроенных систем и даже небольших веб-приложений.

Установка и установка

Я начну с создания нового пакета npm с помощью npm init внутри пустого каталога под названием node-sqlite-tutorial.

 $ npm init 
 This utility will walk you through creating a package.json file. 
 It only covers the most common items, and tries to guess sane defaults. 
 
 See `npm help json` for definitive documentation on these fields 
 and exactly what they do. 
 
 Use `npm install <pkg> --save` afterwards to install a package and 
 save it as a dependency in the package.json file. 
 
 Press ^C at any time to quit. 
 name: (app) node-sqlite 
 version: (0.0.0) 0.1.0 
 description: Code for tutorial blog on node and sqlite 
 entry point: (index.js) main.js 
 test command: 
 git repository: 
 keywords: 
 author: Adam McQuistan 
 license: (BSD) MIT 
 About to write to /node-sqlite/app/package.json: 
 
 { 
 "name": "node-sqlite", 
 "version": "0.1.0", 
 "description": "Code for tutorial blog on node and sqlite", 
 "main": "main.js", 
 "scripts": { 
 "test": "echo \"Error: no test specified\" && exit 1" 
 }, 
 "repository": "", 
 "author": "Adam McQuistan", 
 "license": "MIT" 
 } 
 
 
 Is this ok? (yes) 

Затем мне нужно будет установить пакет sqlite3 через npm следующим образом:

 $ npm install --save sqlite3 

В дополнение к sqlite3 я собираюсь установить Bluebird, чтобы использовать знакомую функциональность обещаний при программировании базы данных.

 $ npm install --save bluebird 

Теперь я создам пустой файл рядом с файлом package.json с именем database.sqlite3, в котором SQLite будет хранить данные.

Проектирование базы данных

Как и почти во всех других моих статьях, я буду использовать придуманное приложение, чтобы помочь описать некоторые важные аспекты программирования баз данных с помощью Node.js и SQLite. В этой статье я исхожу из предположения, что я создаю уровень доступа к данным для проекта и приложения для отслеживания задач. Основные бизнес-правила для уровня доступа к данным этого приложения следующие:

  • В приложении есть проекты
  • У каждого проекта может быть одна или несколько задач, которые нужно выполнить.

С изложенными бизнес-правилами я могу взять эту информацию и приступить к разработке необходимых таблиц и их полей. Понятно, что мне понадобится таблица проектов и таблица задач . В остальном я просто воспользуюсь небольшой интуицией, некоторыми составленными тестовыми данными и буду работать с ними (обычная рабочая характеристика для большинства разработчиков).

таблица проектов

я бы название


1 Написать Node.js - Учебное пособие по SQLite

таблица задач

я бы название описание завершено projectId


1 Контур Обзор разделов на высоком уровне 1 1 2 Писать Напишите содержание статьи и примеры кода 0 1

Хорошо, теперь, когда я знаю, что мне нужно создать, я могу перевести это в код.

Создание базы данных

Для начала мне нужно создать файл main.js вместе с файлом dao.js (или объектом доступа к данным ) в том же каталоге, что и файл package.json.

Внутри dao.js я добавлю импорт для объектов Promise AppDAO класс доступа к данным под названием AppDAO, который установит соединение с базой данных внутри конструктора и назначит его полю-члену под названием db .

 // dao.js 
 
 const sqlite3 = require('sqlite3') 
 const Promise = require('bluebird') 
 
 class AppDAO { 
 constructor(dbFilePath) { 
 this.db = new sqlite3.Database(dbFilePath, (err) => { 
 if (err) { 
 console.log('Could not connect to database', err) 
 } else { 
 console.log('Connected to database') 
 } 
 }) 
 } 
 } 
 
 module.exports = AppDAO 

Связь довольно проста. Вы просто создаете экземпляр Database sqlite3, передавая ему путь к файлу базы данных SQLite, к которому вы хотите подключиться, и, при необходимости, проверяйте наличие ошибок, которые могут возникнуть. Как отмечалось выше, я сохраняю этот объект подключения в поле с именем db в классе AppDAO

Я продолжу объяснять, как использовать объект подключения для отправки запросов в базу данных. Пакет sqlite3 Node.js предоставляет несколько различных методов для выполнения запросов, но в этом руководстве я остановлюсь на следующих:

  • run : используется для создания или изменения таблиц, а также для вставки или обновления данных таблицы
  • get : выбрать одну строку данных из одной или нескольких таблиц
  • all : выбрать несколько строк данных из одной или нескольких таблиц

Для начала я хотел бы изучить метод run Его общий синтаксис выглядит так:

 db.run('SOME SQL QUERY', [param1, param2], (err) => { 
 if (err) { 
 console.log('ERROR!', err) 
 } 
 }) 

Первый параметр, переданный в run(...) представляет собой строку SQL, которая будет выполняться, и является единственным обязательным параметром. Второй - это необязательный массив параметров, которые библиотека sqlite3 заменит для любого символа '?' заполнители в запросе (я немного продемонстрирую это). Финал - это функция обратного вызова ошибки.

Как вы могли догадаться, я буду использовать run(...) для создания и обновления моих проектов и задач. Однако на самом деле я собираюсь обернуть его в свою собственную версию метода run AppDAO потому что я хотел бы инкапсулировать его в bluebird Promise чтобы сделать вещи явно асинхронными и основанными на обещаниях, например:

 // dao.js 
 
 const sqlite3 = require('sqlite3') 
 const Promise = require('bluebird') 
 
 class AppDAO { 
 // omitting constructor code 
 
 run(sql, params = []) { 
 return new Promise((resolve, reject) => { 
 this.db.run(sql, params, function (err) { 
 if (err) { 
 console.log('Error running sql ' + sql) 
 console.log(err) 
 reject(err) 
 } else { 
 resolve({ id: this.lastID }) 
 } 
 }) 
 }) 
 } 
 } 

С помощью моего собственного AppDAO.run(...) теперь я могу использовать его для создания таблиц продуктов и задач.

Для начала я добавляю в свой проект еще два файла: project_repository.js и task_repository.js. Внутри project_repository.js я определяю класс под названием ProjectRepository , который имеет конструктор , который принимает экземпляр AppDAO объекта и createTable метод , который выполняет некоторые DDL (Definition Language Data) SQL следующим образом:

 // project_repository.js 
 
 class ProjectRepository { 
 constructor(dao) { 
 this.dao = dao 
 } 
 
 createTable() { 
 const sql = ` 
 CREATE TABLE IF NOT EXISTS projects ( 
 id INTEGER PRIMARY KEY AUTOINCREMENT, 
 name TEXT)` 
 return this.dao.run(sql) 
 } 
 } 
 
 module.exports = ProjectRepository; 

Затем я снова делаю то же самое, но на этот раз в файле task_repository.js.

 // task_repository.js 
 
 class TaskRepository { 
 constructor(dao) { 
 this.dao = dao 
 } 
 
 createTable() { 
 const sql = ` 
 CREATE TABLE IF NOT EXISTS tasks ( 
 id INTEGER PRIMARY KEY AUTOINCREMENT, 
 name TEXT, 
 description TEXT, 
 isComplete INTEGER DEFAULT 0, 
 projectId INTEGER, 
 CONSTRAINT tasks_fk_projectId FOREIGN KEY (projectId) 
 REFERENCES projects(id) ON UPDATE CASCADE ON DELETE CASCADE)` 
 return this.dao.run(sql) 
 } 
 } 
 
 module.exports = TaskRepository; 

DDL SQL для создания таблиц готов, поэтому я перейду к методам вставки данных в таблицы.

Вставка данных

В ProjectRepository мне нужно добавить create который получает имя проекта для создания и выполняет соответствующий оператор INSERT с помощью AppDAO.run(...) . Обратите внимание, как я использовал '?' для представления значения имени проекта, а затем поместите name в необязательный аргумент массива params в метод run(...) . Это называется параметризованным оператором запроса, который очищает входные данные, чтобы минимизировать риски внедрения SQL.

 // project_repository.js 
 
 class ProjectRepository { 
 // omitting other methods 
 
 create(name) { 
 return this.dao.run( 
 'INSERT INTO projects (name) VALUES (?)', 
 [name]) 
 } 
 } 
 
 module.exports = ProjectRepository; 

Аналогичный метод создания необходим для класса TaskRepository

 // task_repository.js 
 
 class TaskRepository { 
 // omitting other methods 
 
 create(name, description, isComplete, projectId) { 
 return this.dao.run( 
 `INSERT INTO tasks (name, description, isComplete, projectId) 
 VALUES (?, ?, ?, ?)`, 
 [name, description, isComplete, projectId]) 
 } 
 } 
 
 module.exports = TaskRepository; 

Теперь, когда у меня есть возможность ВСТАВЛЯТЬ данные в базу данных, я хотел бы добавить функции для ее обновления.

Обновление данных

В ProjectRepository я добавлю update который принимает project и снова обновляет все поля для записи базы данных этого проекта, используя метод AppDAO.run(...) , например:

 // project_repository.js 
 
 class ProjectRepository { 
 // omitting other methods 
 
 update(project) { 
 const { id, name } = project 
 return this.dao.run( 
 `UPDATE projects SET name = ? WHERE id = ?`, 
 [name, id] 
 ) 
 } 
 } 
 
 module.exports = ProjectRepository; 

Далее нужно добавить соответствующий метод обновления в класс TaskRepository

 // task_repository.js 
 
 class TaskRepository { 
 // omitting other methods 
 
 update(task) { 
 const { id, name, description, isComplete, projectId } = task 
 return this.dao.run( 
 `UPDATE tasks 
 SET name = ?, 
 description = ?, 
 isComplete = ?, 
 projectId = ? 
 WHERE id = ?`, 
 [name, description, isComplete, projectId, id] 
 ) 
 } 
 } 
 
 module.exports = TaskRepository; 

Удаление данных

Последняя мутационная функция, которую необходимо реализовать, - это предоставить возможность удалять записи из базы данных. Для этого я снова буду использовать метод AppDAO.run(...) в сочетании с новыми методами delete для классов ProjectRepository и TaskRepository

Для ProjectRepository это выглядит так:

 // project_repository.js 
 
 class ProjectRepository { 
 // omitting other methods 
 
 delete(id) { 
 return this.dao.run( 
 `DELETE FROM projects WHERE id = ?`, 
 [id] 
 ) 
 } 
 } 
 
 module.exports = ProjectRepository; 

А для TaskRepository это выглядит так:

 // task_repository.js 
 
 class TaskRepository { 
 // omitting other methods 
 
 delete(id) { 
 return this.dao.run( 
 `DELETE FROM tasks WHERE id = ?`, 
 [id] 
 ) 
 } 
 } 
 
 module.exports = TaskRepository; 

Хорошо, это завершает все способы, которыми я буду использовать метод run Далее я представлю два других связанных get и all методы пакета sqlite3 Node.js.

Чтение данных

В этом разделе я собираюсь рассказать, как использовать get и all методы библиотеки sqlite3 Node.js. Как упоминалось ранее, get используется для получения одной строки данных, в то время как all используется для запроса многих строк данных.

Базовый синтаксис использования get выглядит так:

 db.get('SELECT ...', [param1, param2], (err, result) => { 
 if (err) { 
 console.log(err) 
 } else { 
 // do something with result 
 } 
 }) 

Где db - объект подключения sqlite3. Вы заметите, что синтаксис по существу идентичен run за исключением того, что обратный вызов имеет дополнительный параметр, который содержит объект результата запроса, при условии, что ошибки не было.

Базовый синтаксис для all по сути снова тот же, за исключением того, что второй параметр обратного вызова представляет собой массив результатов, возвращаемых запросом, например:

 db.all('SELECT ...', [param1, param2], (err, results) => { 
 if (err) { 
 console.log(err) 
 } else { 
 // do something with results 
 } 
 }) 

Так же , как я сделал с sqlite3 run метода я собираюсь реализовать get и all методов , использующих bluebird Promise в AppDAO класса , как показано ниже:

 // dao.js 
 
 const sqlite3 = require('sqlite3').verbose() 
 const Promise = require('bluebird') 
 
 class AppDAO { 
 // omitting other methods 
 
 get(sql, params = []) { 
 return new Promise((resolve, reject) => { 
 this.db.get(sql, params, (err, result) => { 
 if (err) { 
 console.log('Error running sql: ' + sql) 
 console.log(err) 
 reject(err) 
 } else { 
 resolve(result) 
 } 
 }) 
 }) 
 } 
 
 all(sql, params = []) { 
 return new Promise((resolve, reject) => { 
 this.db.all(sql, params, (err, rows) => { 
 if (err) { 
 console.log('Error running sql: ' + sql) 
 console.log(err) 
 reject(err) 
 } else { 
 resolve(rows) 
 } 
 }) 
 }) 
 } 
 } 

Теперь я могу использовать эти методы в ProjectRepository и TaskRepository для извлечения данных из базы данных SQLite.

Для начала я добавлю getById чтобы выбирать их записи по id.

В ProjectRepository я добавляю это:

 // project_repository.js 
 
 class ProjectRepository { 
 // omitting other methods 
 
 getById(id) { 
 return this.dao.get( 
 `SELECT * FROM projects WHERE id = ?`, 
 [id]) 
 } 
 } 
 
 module.exports = ProjectRepository; 

И в TaskRepository аналогично:

 // task_repository.js 
 
 class TaskRepository { 
 // omitting other methods 
 
 getById(id) { 
 return this.dao.get( 
 `SELECT * FROM tasks WHERE id = ?`, 
 [id]) 
 } 
 } 
 
 module.exports = TaskRepository; 

Чтобы продемонстрировать метод AppDAO.all(...) я добавлю возможность выбирать все проекты, а также все задачи для данного проекта.

Код для выбора всех проектов выглядит так:

 // project_repository.js 
 
 class ProjectRepository { 
 // omitting other methods 
 
 getAll() { 
 return this.dao.all(`SELECT * FROM projects`) 
 } 
 } 
 
 module.exports = ProjectRepository; 

Затем, чтобы выбрать все задачи для проекта, я буду использовать метод getTasks(projectId) который ожидает идентификатор проекта, для которого вы хотите получить задачи.

 // project_repository.js 
 class ProjectRepository { 
 // omitting other methods 
 
 getTasks(projectId) { 
 return this.dao.all( 
 `SELECT * FROM tasks WHERE projectId = ?`, 
 [projectId]) 
 } 
 } 
 
 module.exports = ProjectRepository; 

Использование кода доступа к данным

До сих пор я в основном создал библиотеку доступа к данным для этого вымышленного проекта и приложения для отслеживания задач. Сейчас я хотел бы использовать его для загрузки моих тестовых данных, отображаемых в таблицах в разделе « Проектирование базы данных ».

В файле main.js я хочу TaskRepository классы AppDAO , ProjectRepository и require . Затем я буду использовать их для создания таблиц, заполнения их данными, извлечения данных из базы данных и отображения на консоли.

 // main.js 
 
 const Promise = require('bluebird') 
 const AppDAO = require('./dao') 
 const ProjectRepository = require('./project_repository') 
 const TaskRepository = require('./task_repository') 
 
 function main() { 
 const dao = new AppDAO('./database.sqlite3') 
 const blogProjectData = { name: 'Write Node.js - SQLite Tutorial' } 
 const projectRepo = new ProjectRepository(dao) 
 const taskRepo = new TaskRepository(dao) 
 let projectId 
 
 projectRepo.createTable() 
 .then(() => taskRepo.createTable()) 
 .then(() => projectRepo.create(blogProjectData.name)) 
 .then((data) => { 
 projectId = data.id 
 const tasks = [ 
 { 
 name: 'Outline', 
 description: 'High level overview of sections', 
 isComplete: 1, 
 projectId 
 }, 
 { 
 name: 'Write', 
 description: 'Write article contents and code examples', 
 isComplete: 0, 
 projectId 
 } 
 ] 
 return Promise.all(tasks.map((task) => { 
 const { name, description, isComplete, projectId } = task 
 return taskRepo.create(name, description, isComplete, projectId) 
 })) 
 }) 
 .then(() => projectRepo.getById(projectId)) 
 .then((project) => { 
 console.log(`\nRetreived project from database`) 
 console.log(`project id = ${project.id}`) 
 console.log(`project name = ${project.name}`) 
 return projectRepo.getTasks(project.id) 
 }) 
 .then((tasks) => { 
 console.log('\nRetrieved project tasks from database') 
 return new Promise((resolve, reject) => { 
 tasks.forEach((task) => { 
 console.log(`task id = ${task.id}`) 
 console.log(`task name = ${task.name}`) 
 console.log(`task description = ${task.description}`) 
 console.log(`task isComplete = ${task.isComplete}`) 
 console.log(`task projectId = ${task.projectId}`) 
 }) 
 }) 
 resolve('success') 
 }) 
 .catch((err) => { 
 console.log('Error: ') 
 console.log(JSON.stringify(err)) 
 }) 
 } 
 
 main() 

Запустите с помощью node следующим образом:

 $ node main.js 

И вы увидите результат, как показано ниже.

 Connected to database 
 Retreived project from database 
 project id = 1 
 project name = 1 
 Retrieved project tasks from database 
 task id = 1 
 task name = Outline 
 task description = High level overview of sections 
 task isComplete = 1 
 task projectId = 1 
 task id = 2 
 task name = Write 
 task description = Write article contents and code examples 
 task isComplete = 0 
 task projectId = 1 

Заключение

В этом руководстве я рассмотрел основы API пакета Node.js sqlite3 и продемонстрировал, как вы можете обернуть эту функциональность в объектно-ориентированный JavaScript с акцентом на асинхронную реализацию на основе Promise.

Как всегда, я благодарю вас за чтение и приветствую комментарии и критику ниже.

comments powered by Disqus