Использование Sequelize.js и SQLite в приложении Express.js

В этом руководстве я продемонстрирую, как создать простое веб-приложение для управления контактами с использованием Node.js [https://nodejs.org/en/], Express.js [https://expressjs.com/], Vue.js. [https://vuejs.org/] вместе с sequelize.js [http://docs.sequelizejs.com/] объектно-реляционным преобразователем (ORM), поддерживаемым SQLite [https://www.sqlite.org// index.html] база данных. Однако основное внимание в этой статье будет уделено тому, как использовать библиотеку sequelize.js вместе с SQLite, которая расширяет

В этом руководстве я продемонстрирую, как создать простое веб-приложение для управления контактами с использованием Node.js , Express.js , Vue.js в сочетании с объектно-реляционным преобразователем (ORM) sequelize.js, поддерживаемым базой данных SQLite.

Однако основное внимание в этой статье будет уделено тому, как использовать библиотеку sequelize.js в сочетании с SQLite, которая расширяет мою предыдущую статью A SQLite Tutorial with Node.js. Вы можете найти готовый код этого руководства в моейучетной записи GitHub .

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

Для начала я инициализирую новый проект, используя старый добрый npm init и нажимая Enter, чтобы принять все значения по умолчанию, за исключением использования точки входа server.js вместо index.js. Кстати, я использую Node.js версии 8.10.

 $ mkdir node-vue-sqlite-sequelize && cd node-vue-sqlite-sequelize 
 $ npm init 

Далее я установлю зависимости, которые мне понадобятся, а именно: express, body-parser, sequelize, sequelize-cli, sqlite3 и, возможно, nodemon, чтобы мне не приходилось постоянно перезапускать Node.js.

 $ npm install --save express body-parser sequelize sequelize-cli sqlite3 nodemon 

Создание пользовательского интерфейса управления контактами

Сначала я создаю статический каталог в том же каталоге, что и файл package.json:

 $ mkdir static 

Внутри нового статического каталога я создам HTML-файл с именем index.html. Для создания многофункционального и интерактивного веб-интерфейса (или даже настольных приложений с Electron ) я полагаюсь на Vue.js из- за его элегантности и простоты, поэтому снова в этом руководстве я буду использовать Vue.js в файле index.html.

Внутри index.html я начну с простой части шаблона HTML5 и исходного кода Vue.js вместе с Bulma и Font-Awesome, чтобы сделать вещи немного красивее, вместе с axios.js, который я буду использовать для вызовов AJAX позже. .

 <!-- index.html --> 
 <!DOCTYPE html> 
 <html lang="en"> 
 <head> 
 <meta charset="UTF-8"> 
 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 
 <title>Contact</title> 
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.css"> 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17-beta.0/vue.js"></script> 
 <script src="https://unpkg.com/axios/dist/axios.min.js"></script> 
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> 
 <style> 
 .section-container { 
 max-width: 800px; 
 margin: auto; 
 } 
 </style> 
 </head> 
 <body> 
 
 </body> 
 </html> 

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

Поскольку приложение управляет контактами, мне, очевидно, понадобится некоторое представление человека, которое я буду называть объектом «Контакт». Что касается поведения, мне, вероятно, понадобится следующее:

  • Посмотреть список всех контактов
  • Добавить контакты
  • Просмотр сведений о контакте
  • Обновить информацию о контакте
  • Удалить контакт

Довольно простая функциональность CRUD, не так ли?

Кроме того, как я уже упоминал ранее, я буду взаимодействовать с серверной частью через AJAX REST API, поэтому позвольте мне также разметить конечные точки API, которые, как мне кажется, мне понадобятся.

Маршрут Методика Функциональность


/api/contacts ПОЛУЧАТЬ Получить все контакты /api/contacts ПОЧТА Создать контакт /api/contacts/:id СТАВИТЬ Обновить информацию о контакте /api/contacts/:id УДАЛИТЬ Удалить контакт

Теперь, когда я определил, какие функции необходимы, я могу начать собирать вместе компоненты пользовательского интерфейса и реализовывать желаемое поведение JavaScript.

Для начала я создам компонент с именем AddUpdateContact который я могу использовать для добавления новых контактов в приложение или обновления существующих. Итак, внизу body HTML я добавляю script и следующий код JavaScript на основе Vue.js:

 <script> 
 const AddUpdateContact = { 
 props: ['contact', 'title'], 
 data () { 
 return { 
 id: this.contact ? this.contact.id : null, 
 firstName: this.contact ? this.contact.firstName : '', 
 lastName: this.contact ? this.contact.lastName : '', 
 phone: this.contact ? this.contact.phone : '' 
 } 
 }, 
 methods: { 
 save() { 
 this.$emit('save-contact', { id: this.id, firstName: this.firstName, lastName: this.lastName, phone: this.phone }) 
 if (!this.id) { 
 this.firstName = '' 
 this.lastName = '' 
 this.phone = '' 
 } 
 } 
 }, 
 template: ` 
 <form class="form" @submit.prevent="save"> 
 <h3 class='subtitle'>{{ title }}</h3> 
 <div class="field"> 
 <label>First Name</label> 
 <div class="control"> 
 <input class="input" type="text" v-model="firstName"> 
 </div> 
 </div> 
 <div class="field"> 
 <label>Last Name</label> 
 <div class="control"> 
 <input class="input" type="text" v-model="lastName"> 
 </div> 
 </div> 
 <div class="field"> 
 <label>Phone</label> 
 <div class="control"> 
 <input class="input" type="text" v-model="phone"> 
 </div> 
 </div> 
 <div class="field"> 
 <div class="control"> 
 <button class="button is-success">Save</button> 
 </div> 
 </div> 
 </form> 
 ` 
 } 
 </script> 

Компоненту AddUpdateContact можно присвоить два свойства: (1) заголовок и (2) в необязательном случае, когда он будет использоваться для обновления существующего контакта контактного объекта. Члены data инициализируются в зависимости от того, contact объект как опора или нет. Раздел template содержит форму и поля ввода для добавления новой контактной информации или изменения существующей. Затем раздел методов содержит единственный save который передает информацию о контакте до родительского компонента, что согласуется с философией односторонней привязки данных Vue.js.

Далее я создам компонент « Contact script , который будет выглядеть так:

 <script> 
 // omitting the AddUpdateContact component ... 
 const Contact = { 
 props: ['contact'], 
 components: { 'add-update-contact': AddUpdateContact }, 
 data () { 
 return { 
 showDetail: false 
 } 
 }, 
 methods: { 
 onAddOrUpdateContact(contact) { 
 this.$emit('save-contact', contact) 
 }, 
 deleteContact (contact) { 
 this.$emit('delete-contact', contact) 
 } 
 }, 
 template: ` 
 <div class="card"> 
 <header class="card-header"> 
 <p @click="showDetail = !showDetail" class="card-header-title"> 
 {{ contact.firstName }} {{ contact.lastName }} 
 </p> 
 <a class="card-header-icon" @click.stop="deleteContact(contact)"> 
 <span class="icon"> 
 <i class="fa fa-trash"></i> 
 </span> 
 </a> 
 </header> 
 <div v-show="showDetail" class="card-content"> 
 <add-update-contact title="Details" :contact="contact" @save-contact="onAddOrUpdateContact" /> 
 </div> 
 </div> 
 ` 
 } 
 </script> 

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

Функциональность обновления возможна за счет использования метода обработчика событий onAddOrUpdateContact который прослушивает событие save-contact AddUpdateContact , который затем распространяет событие вверх по цепочке на основной экземпляр Vue.js, который будет обсуждаться в ближайшее время. Кроме того, существует также deleteContact компонента Contact, который также передает сообщение об удалении до родительского экземпляра Vue.js.

Хорошо, чтобы завершить внешний вид JavaScript-кода, мне нужно создать экземпляр корневого объекта Vue.js и указать ему, чтобы он привязался к элементу div id "app" следующим образом:

 <script> 
 // omitting AddUpdateContant and Contact components ... 
 new Vue({ 
 el: '#app', 
 components: { contact: Contact, 'add-update-contact': AddUpdateContact }, 
 data: { 
 contacts: [], 
 apiURL: 'http://localhost:3000/api/contacts' 
 }, 
 methods: { 
 onAddOrUpdateContact (contact) { 
 if (contact.id) { 
 this.updateContact(contact) 
 } else { 
 this.addContact(contact) 
 } 
 }, 
 addContact (contact) { 
 return axios.post(this.apiURL, contact) 
 .then((response) => { 
 const copy = this.contacts.slice() 
 copy.push(response.data) 
 this.contacts = copy 
 }) 
 }, 
 updateContact (contact) { 
 return axios.put(`${this.apiURL}/${contact.id}`, contact) 
 .then((response) => { 
 const copy = this.contacts.slice() 
 const idx = copy.findIndex((c) => c.id === response.data.id) 
 copy[idx] = response.data 
 this.contacts = copy 
 }) 
 }, 
 deleteContact (contact) { 
 console.log('deleting', contact) 
 return axios.delete(`${this.apiURL}/${contact.id}`) 
 .then((response) => { 
 let copy = this.contacts.slice() 
 const idx = copy.findIndex((c) => c.id === response.data.id) 
 copy.splice(idx, 1) 
 this.contacts = copy 
 }) 
 } 
 }, 
 beforeMount () { 
 axios.get(this.apiURL) 
 .then((response) => { 
 this.contacts = response.data 
 }) 
 } 
 }) 
 </script> 

Корневой объект Vue.js регистрирует ранее описанные компоненты и определяет набор методов, которые будут взаимодействовать с REST API через вызовы AJAX, выполняемые на сервере приложений Node.js / Express.js, который скоро будет построен.

Последняя часть пользовательского интерфейса, о которой нужно позаботиться, - это HTML, необходимый для рендеринга компонентов Vue.js, который показан ниже полностью в файле index.html.

 <!-- index.html --> 
 <!DOCTYPE html> 
 <html lang="en"> 
 <head> 
 <meta charset="UTF-8"> 
 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 
 <title>Contacts</title> 
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.css"> 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17-beta.0/vue.js"></script> 
 <script src="https://unpkg.com/axios/dist/axios.min.js"></script> 
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> 
 <style> 
 .section-container { 
 max-width: 800px; 
 margin-right: auto; 
 margin-left: auto; 
 } 
 </style> 
 </head> 
 <body> 
 <div id="app" class="container"> 
 <section class="section section-container" style="padding-top: 24px; padding-bottom: 5px;"> 
 <h2 class="title">Contacts</h2> 
 <contact v-for="contact in contacts" 
 :key="contact.name" 
 :contact="contact" 
 @save-contact="onAddOrUpdateContact" 
 @delete-contact="deleteContact" /> 
 </section> 
 <section class="section section-container" style="padding-bottom: 10px;"> 
 <div class="box"> 
 <add-update-contact title="Add Contact" @save-contact="onAddOrUpdateContact" /> 
 </div> 
 </section> 
 </div> 
 <script> 
 const AddUpdateContact = { 
 props: ['contact', 'title'], 
 data () { 
 return { 
 id: this.contact ? this.contact.id : null, 
 firstName: this.contact ? this.contact.firstName : '', 
 lastName: this.contact ? this.contact.lastName : '', 
 phone: this.contact ? this.contact.phone : '' 
 } 
 }, 
 methods: { 
 save() { 
 this.$emit('save-contact', { id: this.id, firstName: this.firstName, lastName: this.lastName, phone: this.phone }) 
 if (!this.id) { 
 this.firstName = '' 
 this.lastName = '' 
 this.phone = '' 
 } 
 } 
 }, 
 template: ` 
 <form class="form" @submit.prevent="save"> 
 <h3 class='subtitle'>{{ title }}</h3> 
 <div class="field"> 
 <label>First Name</label> 
 <div class="control"> 
 <input class="input" type="text" v-model="firstName"> 
 </div> 
 </div> 
 <div class="field"> 
 <label>Last Name</label> 
 <div class="control"> 
 <input class="input" type="text" v-model="lastName"> 
 </div> 
 </div> 
 <div class="field"> 
 <label>Phone</label> 
 <div class="control"> 
 <input class="input" type="text" v-model="phone"> 
 </div> 
 </div> 
 <div class="field"> 
 <div class="control"> 
 <button class="button is-success">Save</button> 
 </div> 
 </div> 
 </form> 
 ` 
 } 
 
 const Contact = { 
 props: ['contact'], 
 components: { 'add-update-contact': AddUpdateContact }, 
 data () { 
 return { 
 showDetail: false 
 } 
 }, 
 methods: { 
 onAddOrUpdateContact(contact) { 
 this.$emit('save-contact', contact) 
 }, 
 deleteContact (contact) { 
 this.$emit('delete-contact', contact) 
 } 
 }, 
 template: ` 
 <div class="card"> 
 <header class="card-header"> 
 <p @click="showDetail = !showDetail" class="card-header-title"> 
 {{ contact.firstName }} {{ contact.lastName }} 
 </p> 
 <a class="card-header-icon" @click.stop="deleteContact(contact)"> 
 <span class="icon"> 
 <i class="fa fa-trash"></i> 
 </span> 
 </a> 
 </header> 
 <div v-show="showDetail" class="card-content"> 
 <add-update-contact title="Details" :contact="contact" @save-contact="onAddOrUpdateContact" /> 
 </div> 
 </div> 
 ` 
 } 
 
 new Vue({ 
 el: '#app', 
 components: { contact: Contact, 'add-update-contact': AddUpdateContact }, 
 data: { 
 contacts: [], 
 apiURL: 'http://localhost:3000/api/contacts' 
 }, 
 methods: { 
 onAddOrUpdateContact (contact) { 
 if (contact.id) { 
 this.updateContact(contact) 
 } else { 
 this.addContact(contact) 
 } 
 }, 
 addContact (contact) { 
 return axios.post(this.apiURL, contact) 
 .then((response) => { 
 const copy = this.contacts.slice() 
 copy.push(response.data) 
 this.contacts = copy 
 }) 
 }, 
 updateContact (contact) { 
 return axios.put(`${this.apiURL}/${contact.id}`, contact) 
 .then((response) => { 
 const copy = this.contacts.slice() 
 const idx = copy.findIndex((c) => c.id === response.data.id) 
 copy[idx] = response.data 
 this.contacts = copy 
 }) 
 }, 
 deleteContact (contact) { 
 console.log('deleting', contact) 
 return axios.delete(`${this.apiURL}/${contact.id}`) 
 .then((response) => { 
 let copy = this.contacts.slice() 
 const idx = copy.findIndex((c) => c.id === response.data.id) 
 copy.splice(idx, 1) 
 this.contacts = copy 
 }) 
 } 
 }, 
 beforeMount () { 
 axios.get(this.apiURL) 
 .then((response) => { 
 this.contacts = response.data 
 }) 
 } 
 }) 
 
 </script> 
 </body> 
 </html> 

Создание каркаса для REST API

Поскольку я использую Express.js для этого приложения, мне нужно будет создать файл JavaScript с именем server.js, который будет обслуживать приложение в том же каталоге, что и файл package.json, а затем открыть его в моем любимом текстовом редакторе разработки (VS Код).

В верхней части файла я извлекаю фреймворк Express.js через require а затем создаю экземпляр приложения. Затем я говорю приложению использовать статическое промежуточное ПО Express для обслуживания статического контента (HTML, JS, CSS и т. Д.) Из «статического» каталога. В самом низу сценария server.js я говорю серверу приложений привязать (прослушивать) к порту 3000 и запросам сервера.

Чтобы вывести конечные точки REST API, ранее определенные в таблице в предыдущем разделе, мне требуется body-parser, и я указываю экспресс-приложению, что он будет использоваться для синтаксического анализа содержимого тела HTTP. Затем я отключаю конечные точки API, которые обслуживают запрошенный контент.

 // server.js 
 
 const express = require('express'); 
 const bodyParser = require('body-parser'); 
 
 const app = express(); 
 
 app.use(bodyParser.json()); 
 app.use(express.static(__dirname + '/static')); 
 
 app.get('/api/contacts', (req, res) => { 
 // TODO: retreive contacts and send to requester 
 }); 
 
 app.post('/api/contacts', (req, res) => { 
 const { firstName, lastName, phone } = req.body 
 // TODO: create contact 
 }); 
 
 app.delete('/api/contacts/:id', (req, res) => { 
 const id = parseInt(req.params.id) 
 // TODO: find and delete contact by id 
 }); 
 
 app.put('/api/contacts/:id', (req, res) => { 
 const id = parseInt(req.params.id) 
 const { firstName, lastName, phone } = req.body 
 // TODO: find and update contact by id 
 }); 
 
 app.listen(3000, () => { 
 console.log('Server is up on port 3000'); 
 }); 

Как только будут определены модели данных и последующая структура базы данных SQLite, я вернусь и предоставлю функциональные возможности для обратных вызовов маршрутов API.

Sequelize ORM и интерфейс командной строки Sequelize

Sequelize.js - это популярный ORM для Node.js версии 4 и выше, который можно использовать для многих различных систем управления базами данных (СУБД), таких как MySQL, Postgres, SQLite и других. Существует дополнительная служебная библиотека под названием sequelize-cli, которая помогает автоматизировать некоторые повседневные, а также несколько нетривиальные части программирования баз данных.

В этом руководстве я буду использовать sequelize-cli для создания языка определения данных (DDL) для создания таблиц базы данных, а также для создания моделей данных, которые создают и выполняют язык манипулирования данными (DML) для запросов к базе данных, и даже система миграции для помощи в управлении версиями схемы базы данных.

Для начала я воспользуюсь sequelize-cli для инициализации уровня доступа к данным проекта следующим образом:

 $ node_modules/.bin/sequelize init 

Эта команда создаст конфигурацию каталогов, миграции, модели и сеялки, в результате чего моя структура проекта будет выглядеть так:

 . 
 ├── config 
 │  └── config.json 
 ├── migrations 
 ├── models 
 │  └── index.js 
 ├── package-lock.json 
 ├── package.json 
 ├── seeders 
 ├── server.js 
 └── static 
 └── index.html 

Назначение каталогов следующее:

  • config / index.js - определяет параметры подключения и sql dialet
  • migrations - содержит сценарии миграции для управления версиями схемы.
  • models - содержит модели данных, которые вы используете для взаимодействия с базой данных в коде вашего приложения.
  • сидеры - содержат скрипты для заполнения вашей базы данных исходными данными

Во-первых, мне нужно отредактировать файл config / config.json, чтобы sequelize знал, что я собираюсь работать с базой данных SQLite. Так что изменю файл из этого ...

 { 
 "development": { 
 "username": "root", 
 "password": null, 
 "database": "database_development", 
 "host": "127.0.0.1", 
 "dialect": "mysql" 
 }, 
 "test": { 
 "username": "root", 
 "password": null, 
 "database": "database_test", 
 "host": "127.0.0.1", 
 "dialect": "mysql" 
 }, 
 "production": { 
 "username": "root", 
 "password": null, 
 "database": "database_production", 
 "host": "127.0.0.1", 
 "dialect": "mysql" 
 } 
 } 

к этому ...

 { 
 "development": { 
 "dialect": "sqlite", 
 "storage": "./database.sqlite3" 
 }, 
 "test": { 
 "dialect": "sqlite", 
 "storage": ":memory" 
 }, 
 "production": { 
 "dialect": "sqlite", 
 "storage": "./database.sqlite3" 
 } 
 } 

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

Теперь я буду следовать этой команде с другой, но на этот раз я буду использовать model:generate для определения моей модели контакта и ее атрибутов, как показано ниже:

 $ node_modules/.bin/sequelize model:generate --name Contact --attributes firstName:string,lastName:string,phone:string,email:string 

Параметр --name очевидно, является именем модели, которая должна быть сгенерирована, а за --attibutes следуют поля объекта, которые определяют его вместе с их типами данных. Результатом этой команды являются два новых файла:

  1. models / contact.js - модель данных, которая будет использоваться в логическом коде приложения Node.js
  2. migrations / yyyymmddHHMMSS-create-contact.js - скрипт миграции, который выдаст DDL SQL для создания таблицы контактов в базе данных.

В дополнение к атрибутам, указанным в model:generate команда sequelize-cli также сгенерирует автоматически увеличивающееся целочисленное id а также поля createdAt и updatedAt date-link.

Следующее, что нужно сделать, это запустить миграцию, чтобы база данных SQLite содержала следующую таблицу контактов:

 $ node_modules/.bin/sequelize db:migrate 

Эта команда укажет, что миграция прошла успешно. Теперь я могу открыть свой недавно созданный файл database.sqlite3 и просмотреть схему следующим образом:

 $ sqlite3 database.sqlite3 
 SQLite version 3.20.1 2017-08-24 16:21:36 
 Enter ".help" for usage hints. 
 sqlite> .schema 
 CREATE TABLE `SequelizeMeta` (`name` VARCHAR(255) NOT NULL UNIQUE PRIMARY KEY); 
 CREATE TABLE `Contacts` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `firstName` VARCHAR(255), `lastName` VARCHAR(255), `phone` VARCHAR(255), `email` VARCHAR(255), `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL); 
 CREATE TABLE sqlite_sequence(name,seq); 

Обратите внимание, что там есть еще одна таблица с именем SequelizeMeta. Это таблица, которую Sequelize.js использует для сохранения порядка, в котором выполняются миграции. Например, выполнение этой команды покажет имя только что запущенного сценария миграции.

 sqlite> .headers ON 
 sqlite> select * from SequelizeMeta; 
 name 
 20180726180039-create-contact.js 

Теперь, когда у меня есть модель Contact, сопоставленная с таблицей базы данных, я хотел бы сгенерировать сценарий сидера, чтобы предварительно заполнить мою базу данных некоторыми данными, чтобы продемонстрировать, как взаимодействовать с ORM в моем коде приложения. Создание сценария сидера очень похоже на предыдущие команды.

 $ node_modules/.bin/sequelize seed:generate --name seed-contact 

Результатом является новый сценарий в каталоге сидеров соглашения об именах yyyymmddHHMMSS-seed-contact.js. Изначально это просто каркас интерфейса сидера вроде этого:

 'use strict'; 
 
 module.exports = { 
 up: (queryInterface, Sequelize) => { 
 /* 
 Add altering commands here. 
 Return a promise to correctly handle asynchronicity. 
 
 Example: 
 return queryInterface.bulkInsert('Person', [{ 
 name: 'John Doe', 
 isBetaMember: false 
 }], {}); 
 */ 
 }, 
 
 down: (queryInterface, Sequelize) => { 
 /* 
 Add reverting commands here. 
 Return a promise to correctly handle asynchronicity. 
 
 Example: 
 return queryInterface.bulkDelete('Person', null, {}); 
 */ 
 } 
 }; 

Я отредактирую его следующим образом, чтобы добавить несколько контактов.

 'use strict'; 
 
 module.exports = { 
 up: (queryInterface, Sequelize) => { 
 /* 
 Add altering commands here. 
 Return a promise to correctly handle asynchronicity. 
 
 Example: 
 return queryInterface.bulkInsert('Person', [{ 
 name: 'John Doe', 
 isBetaMember: false 
 }], {}); 
 */ 
 return queryInterface.bulkInsert('Contacts', [{ 
 firstName: 'Snoop', 
 lastName: 'Dog', 
 phone: '111-222-3333', 
 email: ' [email protected] ', 
 createdAt: new Date().toDateString(), 
 updatedAt: new Date().toDateString() 
 }, { 
 firstName: 'Scooby', 
 lastName: 'Doo', 
 phone: '444-555-6666', 
 email: ' [email protected] ', 
 createdAt: new Date().toDateString(), 
 updatedAt: new Date().toDateString() 
 }, { 
 firstName: 'Herbie', 
 lastName: 'Husker', 
 phone: '402-437-0001', 
 email: ' [email protected] ', 
 createdAt: new Date().toDateString(), 
 updatedAt: new Date().toDateString() 
 }], {}); 
 }, 
 
 down: (queryInterface, Sequelize) => { 
 /* 
 Add reverting commands here. 
 Return a promise to correctly handle asynchronicity. 
 
 Example: 
 return queryInterface.bulkDelete('Person', null, {}); 
 */ 
 return queryInterface.bulkDelete('Contacts', null, {}); 
 } 
 }; 

Наконец, мне нужно запустить сеялку, чтобы заполнить базу данных этими начальными контактами.

 $ node_modules/.bin/sequelize db:seed:all 

Это дает мне вывод в консоли, сообщающий мне, что таблица базы данных была успешно заполнена данными.

Итак, настройка наконец-то завершена. Теперь я могу перейти к более интересной части реального взаимодействия с моей моделью контактов, поддерживаемой базой данных, и предоставить тем, которые ранее были заглушены конечными точками API, их требуемые функции.

Однако, прежде чем я смогу использовать свою модель контакта, мне нужно сообщить приложению Express.js, что оно существует. Я делаю это, добавив require в server.js и присвоения его const переменной называется db , как так:

 // server.js 
 const express = require('express'); 
 const bodyParser = require('body-parser'); 
 const db = require('./models'); // new require for db object 

Эта db будет содержать мою модель контакта, доступную через db.Contact .

Я начинаю с простейшей конечной точки API - конечной точки GET /api/contacts . Этой конечной точке просто необходимо, чтобы все контакты были возвращены из базы данных и сериализованы в ответ вызывающей стороне. Я могу назвать findAll метод Contact объекта и когда thenable обещания вернулось я могу стрелять контакты прочь к клиенту , используя знакомый res.send(...) , представленный в рамках Express.js.

 // server.js 
 // ommitting everything above ... 
 
 app.get('/api/contacts', (req, res) => { 
 return db.Contact.findAll() 
 .then((contacts) => res.send(contacts)) 
 .catch((err) => { 
 console.log('There was an error querying contacts', JSON.stringify(err)) 
 return res.send(err) 
 }); 
 }); 
 
 // omitting everything below... 

Теперь я могу запустить свой сервер Express, указать в браузере localhost: 3000 / index.html, и меня встретит пользовательский интерфейс контактов.

 $ nodemon 

... или, если не используется nodemon:

 $ npm start 

Скриншот приложения\"Контакты\"{.ezlazyload .img-responsive}

Двигаясь дальше, я реализую функцию создания для конечной точки POST /api/contacts Создать новые экземпляры контактов очень просто. Просто вызовите метод create(...) db.Contact , дождитесь разрешения обещания путем связывания с then(...) а затем я верну вновь созданный контакт клиенту, например:

 // server.js 
 // ommitting everything above ... 
 
 app.post('/api/contacts', (req, res) => { 
 const { firstName, lastName, phone } = req.body 
 return db.Contact.create({ firstName, lastName, phone }) 
 .then((contact) => res.send(contact)) 
 .catch((err) => { 
 console.log('***There was an error creating a contact', JSON.stringify(contact)) 
 return res.status(400).send(err) 
 }) 
 }); 
 
 // omitting everything below ... 

Теперь, если я введу другой контакт, скажем, Чудо-женщину, но оставлю номер телефона пустым (она сказала, что позвонит мне вместо того, чтобы давать свой номер), в форме «Добавить контакт» пользовательского интерфейса и нажмите «Сохранить», мой список контактов будет теперь у нас четыре участника: Snoop Dog, Скуби Ду, Херби Хаскер и Чудо-женщина.

Двигаясь дальше, теперь я могу добавить функциональность для реализации поведения обновления для конечной точки PUT /api/contacts/:id С точки зрения программирования баз данных, это взаимодействие состоит из двух частей.

Сначала я начинаю с поиска контакта, соответствующего идентификатору, указанному в URL- db.Contact.findById(...) , затем изменяю поля новыми данными и заканчиваю использованием метода update(...) contact отправляющего обновленную модель обратно в базу данных, где эти изменения будут сохранены.

 // server.js 
 // ommitting everything above ... 
 
 app.put('/api/contacts/:id', (req, res) => { 
 const id = parseInt(req.params.id) 
 return db.Contact.findById(id) 
 .then((contact) => { 
 const { firstName, lastName, phone } = req.body 
 return contact.update({ firstName, lastName, phone }) 
 .then(() => res.send(contact)) 
 .catch((err) => { 
 console.log('***Error updating contact', JSON.stringify(err)) 
 res.status(400).send(err) 
 }) 
 }) 
 }); 
 
 // omitting everything below... 

Теперь я могу обновить номер телефона Чудо-женщины, потому что мой хороший приятель Снуп Дог любезно дал его мне. Для этого я нажимаю имя Чудо-женщины в списке контактов пользовательского интерфейса, чтобы развернуть форму сведений / обновления. После ввода ее номера 111-888-1213 и нажатия кнопки «Сохранить мою Чудо-женщину» контакт обновляется.

Последнее, что нужно сделать, это добавить возможность удаления контакта через конечную точку DELETE /api/contacts/:id Удаление очень похоже на функциональность обновления в том, что экземпляр модели, который вы хотите удалить, сначала нужно получить из базы данных, а затем вы просто вызываете метод destroy() экземпляра модели.

 // server.js 
 // ommitting everything above ... 
 
 app.delete('/api/contacts/:id', (req, res) => { 
 const id = parseInt(req.params.id) 
 return db.Contact.findById(id) 
 .then((contact) => contact.destroy()) 
 .then(() => res.send({ id })) 
 .catch((err) => { 
 console.log('***Error deleting contact', JSON.stringify(err)) 
 res.status(400).send(err) 
 }) 
 }); 
 
 // omitting everything below ... 

Оказывается, хорошо, что контакты в моем списке контактов можно удалить, потому что Чудо-женщина оказалась настоящей королевой драмы и дралась с моей женой, а не с крутой Чудо-женщиной. Вы собираетесь быть удаленными.

В пользовательском интерфейсе я могу удалить Чудо-женщину, щелкнув мусорное ведро справа от ее имени.

Полный сценарий server.js показан ниже для полноты картины.

 // server.js 
 
 // server.js 
 
 const express = require('express'); 
 const bodyParser = require('body-parser'); 
 const db = require('./models'); 
 
 const app = express(); 
 
 app.use(bodyParser.json()); 
 app.use(express.static(__dirname + '/static')); 
 
 app.get('/api/contacts', (req, res) => { 
 return db.Contact.findAll() 
 .then((contacts) => res.send(contacts)) 
 .catch((err) => { 
 console.log('There was an error querying contacts', JSON.stringify(err)) 
 return res.send(err) 
 }); 
 }); 
 
 app.post('/api/contacts', (req, res) => { 
 const { firstName, lastName, phone } = req.body 
 return db.Contact.create({ firstName, lastName, phone }) 
 .then((contact) => res.send(contact)) 
 .catch((err) => { 
 console.log('***There was an error creating a contact', JSON.stringify(contact)) 
 return res.status(400).send(err) 
 }) 
 }); 
 
 app.delete('/api/contacts/:id', (req, res) => { 
 const id = parseInt(req.params.id) 
 return db.Contact.findById(id) 
 .then((contact) => contact.destroy({ force: true })) 
 .then(() => res.send({ id })) 
 .catch((err) => { 
 console.log('***Error deleting contact', JSON.stringify(err)) 
 res.status(400).send(err) 
 }) 
 }); 
 
 app.put('/api/contacts/:id', (req, res) => { 
 const id = parseInt(req.params.id) 
 return db.Contact.findById(id) 
 .then((contact) => { 
 const { firstName, lastName, phone } = req.body 
 return contact.update({ firstName, lastName, phone }) 
 .then(() => res.send(contact)) 
 .catch((err) => { 
 console.log('***Error updating contact', JSON.stringify(err)) 
 res.status(400).send(err) 
 }) 
 }) 
 }); 
 
 app.listen(3000, () => { 
 console.log('Server is up on port 3000'); 
 }); 

Заключение

В этом руководстве я продемонстрировал, как использовать ORM Sequelize.js вместе с его CLI Sequelize для обработки программирования баз данных в сочетании с SQLite. При этом я предоставил глупое приложение со списком контактов, которое использует Node.js / Express.js для сервера приложений вместе с пользовательским интерфейсом на основе Vue.js. Sequelize.js - это довольно полезный ORM, который сокращает многие детали, необходимые для программирования баз данных в приложениях на основе Node.js, и довольно популярен среди разработчиков Node.js.

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

comments powered by Disqus

Содержание