Использование Sequelize ORM с Node.js и Express

Введение Sequelize [https://github.com/sequelize/sequelize/] - популярная ORM, созданная для Node.js, и в этом руководстве мы будем использовать ее для создания CRUD API для управления заметками. Взаимодействие с базами данных - обычная задача для серверных приложений. Обычно это выполнялось с помощью необработанных SQL-запросов, которые может быть сложно построить, особенно для тех, кто плохо знаком с SQL или базами данных в целом. В конце концов, появились объектно-реляционные сопоставители (ORM), призванные упростить управление базами данных. Они

Вступление

Sequelize - это популярный ORM, созданный для Node.js, и в этом руководстве мы будем использовать его для создания CRUD API для управления заметками.

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

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

Мы больше не будем писать необработанные SQL-запросы и выполнять их в базе данных. Предоставляя нам программный способ подключения нашего кода к базе данных и управления сохраненными данными, мы можем больше сосредоточиться на бизнес-логике, а не на подверженном ошибкам SQL.

Что такое ORM?

Реляционное сопоставление объектов - это метод сопоставления программных объектов с таблицами базы данных. Разработчики могут взаимодействовать с объектами вместо того, чтобы писать какие-либо запросы к базе данных. Когда объект читается, создается, обновляется или удаляется, ORM строит и выполняет запрос к базе данных под капотом.

Еще одно преимущество ORM заключается в том, что они поддерживают несколько баз данных: Postgres , MySQL , SQLite и т. Д. Если вы пишете приложение с использованием сырых запросов, будет сложно перейти к другой базе данных, потому что многие запросы придется переписывать.

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

Продолжить

Существует множество узлов ORM, включая популярные Bookshelf.js и TypeORM .

Итак, почему и когда выбрать Sequelize?

Во-первых, он существует давно - с 2011 года. Он имеет тысячи звезд на GitHub и используется множеством приложений. Благодаря своему возрасту и популярности он стабилен и имеет множество документации, доступной в Интернете.

Помимо зрелости и стабильности, Sequelize имеет большой набор функций, который охватывает: запросы, области, отношения, транзакции, необработанные запросы, миграции, репликацию чтения и т. Д.

Следует отметить, что Sequelize основан на обещаниях, что упрощает управление асинхронными функциями и исключениями. Он также поддерживает все популярные диалекты SQL: PostgreSQL, MySQL, MariaDB, SQLite и MSSQL.

С другой стороны, нет поддержки NoSQL, которую можно увидеть в ORM (или в Object Document Mappers, в данном случае), таких как Mongoose . На самом деле решение, какой ORM выбрать, зависит в основном от требований проекта, над которым вы работаете.

Установка Sequelize

Примечание . Если вы хотите следить за кодом, вы можете найти его здесь, на GitHub .

Давайте сделаем скелет приложения Node и установим Sequelize. Прежде всего, давайте создадим каталог для нашего проекта, войдем в него и создадим проект с настройками по умолчанию:

 $ mkdir notes-app 
 $ cd notes-app 
 $ npm init -y 

Затем мы создадим файл приложения с базовым сервером Express и маршрутизатором. Назовем его index.js чтобы он соответствовал имени файла по умолчанию из npm init :

Далее, чтобы легко создать веб-сервер, мы установим Express:

 $ npm install --save express 

И когда он установлен, давайте настроим сервер:

 const express = require('express'); 
 const app = express(); 
 const port = 3000; 
 
 app.get('/', (req, res) => res.send('Notes App')); 
 
 app.listen(port, () => console.log(`notes-app listening on port ${port}!`)); 

Наконец, мы можем продолжить и установить Sequelize и нашу выбранную базу данных через npm :

 $ npm install --save sequelize 
 $ npm install --save sqlite3 

Неважно, какую базу данных вы используете, поскольку Sequelize не зависит от базы данных. То, как мы его используем, одинаково, независимо от базовой базы данных. SQLite3 прост в использовании для локальной разработки и является популярным выбором для этих целей.

Теперь давайте добавим код в index.js чтобы настроить базу данных и проверить соединение с помощью Sequelize. В зависимости от того, какую базу данных вы используете, вам может потребоваться определить другой диалект:

 const Sequelize = require('sequelize'); 
 const sequelize = new Sequelize({ 
 // The `host` parameter is required for other databases 
 // host: 'localhost' 
 dialect: 'sqlite', 
 storage: './database.sqlite' 
 }); 

После импорта Sequelize мы настроили его с параметрами, необходимыми для запуска. Вы также можете добавить сюда дополнительные параметры, такие как pool , хотя того, что у нас есть, достаточно для начала. dialect зависит от того, какую базу данных вы используете, а storage просто указывает на файл базы данных.

database.sqlite создается автоматически на корневом уровне нашего проекта.

Примечание: стоит проверить документацию Sequelize для настройки различных баз данных и необходимой информации для каждой из них.

Если вы используете MySQL, Postgres, MariaDB или MSSQL, вместо того, чтобы передавать каждый параметр отдельно, вы также можете просто передать URI соединения:

 const sequelize = new Sequelize('postgres://user: [email protected] :5432/dbname'); 

Наконец, давайте проверим соединение, запустив метод .authenticate() . Под капотом он просто запускает SELECT и проверяет, правильно ли отвечает база данных:

 sequelize 
 .authenticate() 
 .then(() => { 
 console.log('Connection has been established successfully.'); 
 }) 
 .catch(err => { 
 console.error('Unable to connect to the database:', err); 
 }); 

Запустив приложение, нас встречает:

 $ node index.js 
 notes-app listening on port 3000! 
 Executing (default): SELECT 1+1 AS result 
 Connection has been established successfully. 

Создание модели для картографии

Прежде чем мы сможем создать API заметок, нам нужно создать таблицу заметок. Для этого нам нужно определить Note , которую мы назначим константе, чтобы ее можно было использовать во всем нашем API. В define мы указываем имя таблицы и поля. В этом случае текстовое поле для заметки и строка для тега:

Как и в случае с реляционными базами данных, перед созданием API нам нужно сначала создать соответствующие таблицы. Поскольку мы не хотим создавать его вручную с помощью SQL, мы определим Model а затем заставим Sequelize отобразить его в таблице.

Это можно сделать, расширив Sequelize.Model и запустив .init() , передав параметры или определив const и назначив ей возвращаемое значение метода .define() из Sequelize.

Последнее более лаконично, поэтому мы остановимся на этом:

 const Note = sequelize.define('notes', { note: Sequelize.TEXT, tag: Sequelize.STRING }); 

Сопоставление модели с базой данных

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

Хотя для краткости мы будем использовать метод .sync() . Что .sync() просто - он синхронизирует все определенные модели с базой данных:

 sequelize.sync({ force: true }) 
 .then(() => { 
 console.log(`Database & tables created!`); 
 }); 

Здесь мы использовали force и установили для него значение true . Если таблица уже существует, метод DROP ее и CREATE новую. Если его нет, просто создается таблица.

Наконец, давайте создадим несколько примеров заметок, которые мы затем сохраним в базе данных:

 sequelize.sync({ force: true }) 
 .then(() => { 
 console.log(`Database & tables created!`); 
 
 Note.bulkCreate([ 
 { note: 'pick up some bread after work', tag: 'shopping' }, 
 { note: 'remember to write up meeting notes', tag: 'work' }, 
 { note: 'learn how to use node orm', tag: 'work' } 
 ]).then(function() { 
 return Note.findAll(); 
 }).then(function(notes) { 
 console.log(notes); 
 }); 
 }); 

При запуске сервера наши заметки распечатываются в консоли, а также операции SQL, выполняемые Sequelize. Давайте подключимся к базе данных, чтобы убедиться, что записи действительно были добавлены правильно:

 $ sqlite3 database.sqlite 
 sqlite> select * from notes; 
 1|pick up some bread after work|shopping|2020-02-21 18:24:19.402 +00:00|2020-02-21 18:24:19.402 +00:00 
 2|remember to write up meeting notes|work|2020-02-21 18:24:19.402 +00:00|2020-02-21 18:24:19.402 +00:00 
 3|learn how to use node orm|work|2020-02-21 18:24:19.402 +00:00|2020-02-21 18:24:19.402 +00:00 
 sqlite> .exit 

Создав базу данных и наши таблицы, давайте продолжим и реализуем базовую функциональность CRUD.

Чтение сущностей

Наша модель, Note , теперь имеет встроенные методы, которые помогают нам выполнять операции с сохраненными записями в базе данных.

Читать все объекты

Например, мы можем прочитать все записи этого класса, сохраненные с помощью .findAll() . Давайте создадим простую конечную точку, которая обслуживает все сохраняемые сущности:

 app.get('/notes', function(req, res) { 
 Note.findAll().then(notes => res.json(notes)); 
 }); 

Метод .findAll() возвращает массив заметок, который мы можем использовать для визуализации тела ответа через res.json .

Давайте протестируем конечную точку через curl :

 $ curl http://localhost:3000/notes 
 [{"id":1,"note":"pick up some bread after work","tag":"shopping","createdAt":"2020-02-27T17:02:10.881Z","updatedAt":"2020-02-27T17:02:10.881Z"},{"id":2,"note":"remember to write up meeting notes","tag":"work","createdAt":"2020-02-27T17:02:10.881Z","updatedAt":"2020-02-27T17:02:10.881Z"},{"id":3,"note":"learn how to use node orm","tag":"work","createdAt":"2020-02-27T17:02:10.881Z","updatedAt":"2020-02-27T17:02:10.881Z"}] 

Как видите, все записи нашей базы данных были возвращены нам, но в форме JSON.

Хотя, если мы хотим добавить немного больше функциональности, у нас есть такие операции запроса, как SELECT , WHERE , AND , OR и LIMIT поддерживаемые этим методом.

Полный список поддерживаемых методов запроса можно найти на странице Sequelize Docs.

Читать сущности ГДЕ

Имея это в виду, давайте создадим конечную точку, которая будет обслуживать одну конкретную заметку:

 app.get('/notes/:id', function(req, res) { 
 Note.findAll({ where: { id: req.params.id } }).then(notes => res.json(notes)); 
 }); 

Конечные точки принимают id , используемый для поиска примечания через WHERE . Давайте проверим это через curl :

 $ curl http://localhost:3000/notes/2 
 [{"id":2,"note":"remember to write up meeting notes","tag":"work","createdAt":"2020-02-27T17:03:17.592Z","updatedAt":"2020-02-27T17:03:17.592Z"}] 

Примечание . Поскольку в этом маршруте используется подстановочный знак :id , он будет соответствовать любой строке, идущей после /notes/ . По этой причине этот маршрут должен находиться в конце вашего файла index.js. Это позволяет другим маршрутам, таким как /notes/search , обрабатывать запрос до того, как /notes/:id его заберет. В противном случае search в пути URL будет рассматриваться как идентификатор.

Читать сущности ГДЕ И

Для еще более конкретных запросов давайте создадим конечную точку, используя WHERE и AND :

 app.get('/notes/search', function(req, res) { 
 Note.findAll({ where: { note: req.query.note, tag: req.query.tag } }).then(notes => res.json(notes)); 
 }); 

Здесь мы ищем заметки, которые соответствуют как note и tag заданным параметрами. Опять же, давайте проверим это через curl :

 $ curl "http://localhost:3000/notes/search?note=pick%20up%20some%20bread%20after%20work&tag=shopping" 
 [{"id":1,"note":"pick up some bread after work","tag":"shopping","createdAt":"2020-02-27T17:09:53.964Z","updatedAt":"2020-02-27T17:09:53.964Z"}] 

Читать сущности ИЛИ

Если мы пытаемся быть более расплывчатыми, мы можем использовать OR и искать заметки, которые соответствуют любому из заданных параметров. Измените /notes/search на:

 const Op = Sequelize.Op; 
 
 app.get('/notes/search', function(req, res) { 
 Note.findAll({ 
 where: { 
 tag: { 
 [Op.or]: [].concat(req.query.tag) 
 } 
 } 
 }).then(notes => res.json(notes)); 
 }); 

Здесь мы используем Sequelize.Op для реализации запроса OR Sequelize предоставляет несколько операторов на выбор, таких как Op.or , Op.and , Op.eq , Op.ne , Op.is , Op.not и т. Д. Они в основном используются для создания более сложных операций, таких как запросы с регулярным выражением. нить.

Обратите внимание, что мы используем req.query.tag в качестве аргумента для .findAll() . Sequelize ожидает здесь массив, поэтому мы заставляем tag быть массивом, используя [].concat() . В нашем тесте ниже мы передадим несколько аргументов в наш URL-адрес запроса:

 $ curl "http://localhost:3000/notes/search?tag=shopping&tag=work" 
 [{"id":1,"note":"pick up some bread after work","tag":"shopping","createdAt":"2020-02-27T17:11:27.518Z","updatedAt":"2020-02-27T17:11:27.518Z"},{"id":2,"note":"remember to write up meeting notes","tag":"work","createdAt":"2020-02-27T17:11:27.518Z","updatedAt":"2020-02-27T17:11:27.518Z"},{"id":3,"note":"learn how to use node orm","tag":"work","createdAt":"2020-02-27T17:11:27.518Z","updatedAt":"2020-02-27T17:11:27.518Z"}] 

При передаче одного и того же параметра запроса несколько раз, как это, он будет отображаться как массив в объекте req.query Итак, в приведенном выше примере req.query.tag - это ['shopping', 'work'] .

Чтение объектов LIMIT

Последнее, что мы рассмотрим в этом разделе, - это LIMIT . Допустим, мы хотели изменить предыдущий запрос так, чтобы он возвращал не более двух результатов. Мы сделаем это, добавив limit и присвоив ему положительное целое число:

 const Op = Sequelize.Op; 
 
 app.get('/notes/search', function(req, res) { 
 Note.findAll({ 
 limit: 2, 
 where: { 
 tag: { 
 [Op.or]: [].concat(req.query.tag) 
 } 
 } 
 }).then(notes => res.json(notes)); 
 }); 

Вы можете увидеть полный список функций запросов в документации Sequelize .

Вставка сущностей

Вставка сущностей намного проще, поскольку на самом деле нет двух способов выполнить эту операцию.

Давайте добавим новую конечную точку для добавления заметок:

 const bodyParser = require('body-parser'); 
 app.use(bodyParser.json()); 
 
 app.post('/notes', function(req, res) { 
 Note.create({ note: req.body.note, tag: req.body.tag }).then(function(note) { 
 res.json(note); 
 }); 
 }); 

Модуль body-parser необходим для того, чтобы конечная точка могла принимать и анализировать параметры JSON. Вам не нужно явно устанавливать body-parser , потому что он уже включен в Express.

Внутри маршрута мы используем метод .create() для вставки заметки в базу данных на основе переданных параметров.

Мы можем проверить это с помощью другого запроса на curl

 $ curl -d '{"note":"go the gym","tag":"health"}' -H "Content-Type: application/json" -X POST http://localhost:3000/notes 
 {"id":4,"note":"go the gym","tag":"health","updatedAt":"2020-02-27T17:13:42.281Z","createdAt":"2020-02-27T17:13:42.281Z"} 

Выполнение этого запроса приведет к созданию заметки в нашей базе данных и возвратит нам новый объект базы данных.

Обновление сущностей

Иногда нам нужно обновить уже существующие объекты. Для этого мы будем полагаться на метод .update() на результат метода .findByPk() :

 app.put('/notes/:id', function(req, res) { 
 Note.findByPk(req.params.id).then(function(note) { 
 note.update({ 
 note: req.body.note, 
 tag: req.body.tag 
 }).then((note) => { 
 res.json(note); 
 }); 
 }); 
 }); 

Метод .findByPk() также является унаследованным методом в нашем классе модели. Он ищет объект с заданным первичным ключом. По сути, с помощью этого метода легче возвращать отдельные объекты по их идентификатору, чем писать запрос SELECT WHERE

Учитывая возвращенную сущность, мы запускаем метод .update() чтобы фактически поместить новые значения на свои места. Давайте проверим это с помощью curl :

 $ curl -X PUT -H "Content-Type: application/json" -d '{"note":"pick up some milk after work","tag":"shopping"}' http://localhost:3000/notes/1 
 {"id":1,"note":"pick up some milk after work","tag":"shopping","createdAt":"2020-02-27T17:14:55.621Z","updatedAt":"2020-02-27T17:14:58.230Z"} 

Запуск этого запроса обновляет первую заметку новым содержимым и возвращает обновленный объект:

Удаление объектов

И, наконец, когда мы хотим удалить записи из нашей базы данных, мы используем метод .destroy() для результата метода .findByPk() :

 app.delete('/notes/:id', function(req, res) { 
 Note.findByPk(req.params.id).then(function(note) { 
 note.destroy(); 
 }).then((note) => { 
 res.sendStatus(200); 
 }); 
 }); 

Маршрут для .delete() похож на .update() . Мы используем .findByPk() чтобы найти конкретную заметку по идентификатору. Затем метод .destroy() удаляет заметку из базы данных.

Наконец, клиенту возвращается ответ 200 OK

Заключение

Object Relational Mapping (ORM) - это метод сопоставления программных объектов с таблицами базы данных. Sequelize - популярный и стабильный инструмент ORM, используемый вместе с Node.js. В этой статье мы обсудили, что такое ORM, как они работают и каковы преимущества их использования по сравнению с написанием необработанных запросов.

Обладая этими знаниями, мы приступили к написанию простого приложения Node.js / Express, которое использует Sequelize для сохранения Note в базе данных. Затем, используя унаследованные методы, мы выполнили операции CRUD с базой данных.

Не стесняйтесь проверить код на GitHub, если у вас возникли какие-либо проблемы, следуя этому руководству.

comments powered by Disqus