Node.js - самый популярный JavaScript-фреймворк, когда речь идет о высокоскоростной разработке приложений. Node.js Профессионалы часто выбирают базу данных NoSQL, которая может не отставать от скорости Node.js при сохранении производительности приложения. MongoDB идеально подходит для такого рода требований, поскольку имеет очень быстрый цикл разработки и работает довольно эффективно. Таким образом, я представляю вам этот учебник по Node.js MongoDB, в котором я продемонстрирую, как легко вы можете разработать приложение с помощью Node.js с MongoDB с нуля.
Ниже приведены темы, которые я затрону в этом руководстве по Node.js по MongoDB:
- Что такое база данных NoSQL?
- Введение в MongoDB
- Node.js, демонстрация MongoDB
Итак, приступим.
Что такое база данных NoSQL?
Не только база данных SQL или более известная как база данных NoSQL - это подход к проектированию базы данных, который обеспечивает механизм для хранения и доступа к широкому спектру неструктурированных данных. Эти неструктурированные данные могут содержать смесь моделей данных, включая форматы «ключ-значение», документы, столбцы и графики и т. Д. База данных NoSQL особенно полезна для обработки огромных наборов распределенных данных. Эти базы данных имеют динамическую схему и не имеют определенного языка запросов, а также имеют очень мало связей или вообще отсутствуют. Но он хранит данные в виде коллекций и документов, которые позволяют быстро обновлять.
Существует список баз данных NoSQL, которые довольно активно используются в отрасли, некоторые из которых я перечислил ниже:
- MongoDB
- Hbase
- Кассандра
- Amazon SimpleDB
- Гипертаблица
В этой статье я сосредоточусь только на одной из этих баз данных, о которой вы уже могли догадаться, а именно MongoDB. Но прежде чем я покажу вам, как с ним работать, давайте познакомимся с его подробностями.
Введение в MongoDB
MongoDB - это нереляционная база данных с открытым исходным кодом, в которой данные хранятся в виде коллекций и документов. Этот тип баз данных сохраняет большинство функций, предлагая горизонтальную масштабируемость. Это облегчает работу разработчика, обеспечивая постоянство данных и повышая гибкость.
MongoDB хранит документы JSON в виде коллекций с динамическими схемами. Он хранит всю связанную информацию вместе, что увеличивает скорость обработки запросов. Таким образом, это также помогает сократить разрыв между хранилищами ключей и значений и реляционными базами данных.
Ниже я перечислил несколько наиболее интересных функций MongoDB:
- Индексирование: в нем используются индексы, которые помогают повысить эффективность поиска.
- Репликация: MongoDB распределяет данные по разным машинам.
- Специальные запросы: он поддерживает специальные запросы, индексируя документы BSON и используя уникальный язык запросов.
- Без схемы: он повышает гибкость данных и не требует сценария для изменения или обновления данных.
- Разделение: он использует сегментирование, которое упрощает развертывание очень больших наборов данных и обеспечивает операции с высокой пропускной способностью.
Теперь, когда вы знакомы с MongoDB, давайте продолжим эту статью и посмотрим, насколько просто установить MongoDB в системе.
Установка MongoDB
Шаг I. Загрузите последнюю версию сервера MongoDB с официального сайта: https://www.mongodb.com/download-center/community.
Шаг II. Затем нажмите вкладку «Сервер», как показано на снимке экрана ниже.
Шаг III. Если вы ищете какую-либо конкретную версию, вы можете выбрать ее из раскрывающегося списка или просто загрузить последнюю версию.
Шаг IV. Выберите свою ОС в раскрывающемся списке. Поскольку я работаю с Windows, я выберу 64-разрядную версию Windows.
Шаг V: Теперь выберите пакет как MSI.
Шаг VI. Наконец, нажмите «Загрузить», чтобы начать процесс загрузки.
Шаг VII. После загрузки дважды щелкните файл MSI, чтобы открыть его, и перейдите к мастеру установки.
Шаг VIII: Теперь, чтобы запустить сервер MongoDB, вы должны запустить файл .exe и назначить папку базы данных. Чтобы упростить работу, все, что вам нужно сделать, это записать несколько строк кода в файл блокнота и сохранить его с расширением .bat. Другими словами, вам просто нужно создать командный файл, который без проблем запустит сервер MongoDB. Чтобы создать тип пакетного файла в приведенном ниже коде:
cd C:Program FilesMongoDBServer.0in (MongoDB path) mongod.exe --dbpath F:MongoDBdata (database dump destination)
Теперь, когда вы хотите запустить сервер MongoDB, все, что вам нужно сделать, это дважды щелкнуть этот командный файл и открыть приложение MongoDB Compass.
Шаг IX. Затем вам необходимо запустить MongoDB Compass и согласиться с условиями его использования.
Шаг X: Теперь вам нужно указать конфигурации сервера и нажать «Подключиться».
Шаг XI. Затем нажмите «Создать базу данных».
Шаг XII. Теперь укажите подходящее имя для своей базы данных и коллекции и нажмите «Создать базу данных».
Думаю, теперь вы готовы приступить к практической части, так что без промедления давайте погрузимся в код.
Node.js, демонстрация MongoDB
Здесь я буду создавать приложение CRUD для управления курсом с помощью Node.js и Express.js и использовать MongoDB для хранения данных. В этом приложении я буду использовать детали курса, такие как имя, идентификатор, продолжительность и стоимость, в качестве входных данных. Для этого я создам несколько файлов представления, которые будут действовать как интерфейс. Затем для обработки данных мне также понадобится контроллер, который поможет в манипулировании данными. Наконец, мне понадобятся несколько файлов модели для хранения данных. По сути, я буду следовать шаблону MVC для разработки этого приложения. Итак, перейдем к разработке.
Наше приложение будет иметь следующую иерархию:
NodejsMongoDbDemo
- package.json
- script.js
- контроллеры
- courseController.js
- img
- logo.jpg
- модели
- course.model.js
- mongodb.js
- просмотры
- конечно
а. courseAddEdit.hbs
б. list.hbs
2. макеты
- mainLayout.hbs
Итак, давайте начнем разработку приложения с создания каталога для проекта. Когда вы закончите, откройте командную строку и перейдите в каталог вашего проекта. Теперь вам нужно настроить конфигурации проекта для этого, введите следующую команду и укажите необходимые данные:
npm init
Теперь вам нужно установить необходимые пакеты. Итак, в этом проекте я использую следующие пакеты:
- express.js: это веб-фреймворк.
- express-handlebars: это механизм шаблонов, помогающий создавать клиентские приложения.
- мангуст: помогает при взаимодействии с MongoDB.
- body-parser: Помогает преобразовать данные POST в тело запроса.
- nodemon: помогает автоматически перезапускать сервер при изменении кода.
Чтобы установить эти пакеты, введите следующую команду:
npm i --s express express-handlebars mongoose body-parser
Поскольку я хочу установить nodemon таким образом, чтобы он мог получить доступ к любому файлу в каталоге, я буду устанавливать его с помощью глобальной команды:
npm i -g nodemon
После того, как вы закончите установку с пакетами, ваш окончательный файл JSON должен выглядеть следующим образом:
package.json
{ "name": "samplenodemongo", "version": "1.0.0", "description": "Edureka demo on how to build a Node.js application with MongoDB", "main": "script.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "Edureka", "license": "ISC", "dependencies": { "body-parser": "^1.19.0", "express": "^4.16.4", "express-handlebars": "^3.0.2", "mongoose": "^5.5.6", "nodemon": "^1.19.0" } }
Как видите, в разделе зависимостей успешно перечислены все установленные пакеты. Итак, давайте теперь создадим базу данных, которую мы будем использовать в этой демонстрации. Для этого запустите командный файл и откройте приложение MongoDB. Теперь создайте новую базу данных и укажите имя коллекции. В своем приложении я буду использовать «EdurekaCoursesDB» в качестве имени базы данных и «курсы» в качестве коллекции.
Теперь вернитесь в редактор кода, где мы будем создавать файлы для установления связи между Node.js и MongoDB. Для этого, во-первых, вам нужно создать папку внутри каталога проекта и назвать ее «модель». Внутри этой папки создайте файл javascript с именем «mongodb.js» и введите следующий код:
mongodb.js
const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost:27017/EdurekaCoursesDB', {useNewUrlParser: true}, (err) => { if (!err) { console.log('Successfully Established Connection with MongoDB') } else { console.log('Failed to Establish Connection with MongoDB with Error: '+ err) } }); //Connecting Node and MongoDB require('./course.model');
Теперь вам нужно определить схему базы данных вашего курса. Для этого создайте новый файл JS в папке модели и назовите его «course.model.js». Итак, я использую четыре поля в своем объекте курса. Я использую четыре поля: имя, идентификатор, продолжительность и плату. Чтобы создать этот файл, введите приведенный ниже код.
course.model.js
const mongoose = require('mongoose'); //Attributes of the Course object var courseSchema = new mongoose.Schema({ courseName: { type: String, required: 'This field is required!' }, courseId: { type: String }, courseDuration: { type: String }, courseFee: { type: String } }); mongoose.model('Course', courseSchema);
Теперь вам нужно создать корневой файл с именем «script.js». Этот файл является точкой входа в это приложение и будет содержать все пути подключения в нем. Вам нужно быть очень осторожным при указании путей в этом файле, так как это может привести к ошибке или сбою приложения. Наряду с этим, он также отвечает за вызов сервера и установку соединения. Чтобы создать этот файл, введите следующий код:
script.js
require('./models/mongodb'); //Import the necessary packages const express = require('express'); var app = express(); const path = require('path'); const exphb = require('express-handlebars'); const bodyparser = require('body-parser'); const courseController = require('./controllers/courseController'); app.use(bodyparser.urlencoded({ extended: true })); //Create a welcome message and direct them to the main page app.get('/', (req, res) => { res.send(' <h2 style="font-family: Malgun Gothic; color: midnightblue ">Welcome to Edureka Node.js MongoDB Tutorial!!</h2> Click Here to go to <b> <a href="/course">Course Page</a> </b>'); }); app.use(bodyparser.json()); //Configuring Express middleware for the handlebars app.set('views', path.join(__dirname, '/views/')); app.engine('hbs', exphb({ extname: 'hbs', defaultLayout: 'mainLayout', layoutDir: __dirname + 'views/layouts/' })); app.set('view engine', 'hbs'); //Establish the server connection //PORT ENVIRONMENT VARIABLE const port = process.env.PORT || 8080; app.listen(port, () => console.log(`Listening on port ${port}..`)); //Set the Controller path which will be responding the user actions app.use('/course', courseController);
Затем, чтобы обрабатывать запросы пользователей, вам необходимо создать файл маршрутизатора. Для этого сначала создайте папку и назовите ее «контроллер», а в этой папке создайте файл с именем «courseController.js». В этом файле мы будем иметь дело с операциями CRUD, связанными с сотрудником. Ниже приведен код для создания этого файла:
courseController.js
//Import the dependencies const express = require('express'); const mongoose = require('mongoose'); //Creating a Router var router = express.Router(); //Link const Course = mongoose.model('Course'); //Router Controller for READ request router.get('/',(req, res) => { res.render("course/courseAddEdit", { viewTitle: "Insert a New Course for Edureka" }); }); //Router Controller for UPDATE request router.post('/', (req,res) => { if (req.body._id == '') insertIntoMongoDB(req, res); else updateIntoMongoDB(req, res); }); //Creating function to insert data into MongoDB function insertIntoMongoDB(req,res) { var course = new Course(); course.courseName = req.body.courseName; course.courseId = req.body.courseId; course.courseDuration = req.body.courseDuration; course.courseFee = req.body.courseFee; course.save((err, doc) => { if (!err) res.redirect('course/list'); else console.log('Error during record insertion : ' + err); }); } //Creating a function to update data in MongoDB function updateIntoMongoDB(req, res) { Course.findOneAndUpdate({ _id: req.body._id }, req.body, { new: true }, (err, doc) => { if (!err) { res.redirect('course/list'); } else { if (err.name == 'ValidationError') { handleValidationError(err, req.body); res.render("course/courseAddEdit", { //Retaining value to be displayed in the child view viewTitle: 'Update Course Details', employee: req.body }); } else console.log('Error during updating the record: ' + err); } }); } //Router to retrieve the complete list of available courses router.get('/list', (req,res) => { Course.find((err, docs) => { if(!err){ res.render("course/list", { list: docs }); } else { console.log('Failed to retrieve the Course List: '+ err); } }); }); //Creating a function to implement input validations function handleValidationError(err, body) { for (field in err.errors) { switch (err.errors[field].path) { case 'courseName': body['courseNameError'] = err.errors[field].message; break; default: break; } } } //Router to update a course using it's ID router.get('/:id', (req, res) => { Course.findById(req.params.id, (err, doc) => { if (!err) { res.render("course/courseAddEdit", { viewTitle: "Update Course Details", course: doc }); } }); }); //Router Controller for DELETE request router.get('/delete/:id', (req, res) => { Course.findByIdAndRemove(req.params.id, (err, doc) => { if (!err) { res.redirect('/course/list'); } else { console.log('Failed to Delete Course Details: ' + err); } }); }); module.exports = router;//Import the dependencies const express = require('express'); const mongoose = require('mongoose'); //Creating a Router var router = express.Router(); //Link const Course = mongoose.model('Course'); //Router Controller for READ request router.get('/',(req, res) => { res.render("course/courseAddEdit", { viewTitle: "Insert a New Course for Edureka" }); }); //Router Controller for UPDATE request router.post('/', (req,res) => { if (req.body._id == '') insertIntoMongoDB(req, res); else updateIntoMongoDB(req, res); }); //Creating function to insert data into MongoDB function insertIntoMongoDB(req,res) { var course = new Course(); course.courseName = req.body.courseName; course.courseId = req.body.courseId; course.courseDuration = req.body.courseDuration; course.courseFee = req.body.courseFee; course.save((err, doc) => { if (!err) res.redirect('course/list'); else console.log('Error during record insertion : ' + err); }); } //Creating a function to update data in MongoDB function updateIntoMongoDB(req, res) { Course.findOneAndUpdate({ _id: req.body._id }, req.body, { new: true }, (err, doc) => { if (!err) { res.redirect('course/list'); } else { if (err.name == 'ValidationError') { handleValidationError(err, req.body); res.render("course/courseAddEdit", { //Retaining value to be displayed in the child view viewTitle: 'Update Course Details', employee: req.body }); } else console.log('Error during updating the record: ' + err); } }); } //Router to retrieve the complete list of available courses router.get('/list', (req,res) => { Course.find((err, docs) => { if(!err){ res.render("course/list", { list: docs }); } else { console.log('Failed to retrieve the Course List: '+ err); } }); }); //Creating a function to implement input validations function handleValidationError(err, body) { for (field in err.errors) { switch (err.errors[field].path) { case 'courseName': body['courseNameError'] = err.errors[field].message; break; default: break; } } } //Router to update a course using it's ID router.get('/:id', (req, res) => { Course.findById(req.params.id, (err, doc) => { if (!err) { res.render("course/courseAddEdit", { viewTitle: "Update Course Details", course: doc }); } }); }); //Router Controller for DELETE request router.get('/delete/:id', (req, res) => { Course.findByIdAndRemove(req.params.id, (err, doc) => { if (!err) { res.redirect('/course/list'); } else { console.log('Failed to Delete Course Details: ' + err); } }); }); module.exports = router;//Import the dependencies const express = require('express'); const mongoose = require('mongoose'); //Creating a Router var router = express.Router(); //Link const Course = mongoose.model('Course'); //Router Controller for READ request router.get('/',(req, res) => { res.render("course/courseAddEdit", { viewTitle: "Insert a New Course for Edureka" }); }); //Router Controller for UPDATE request router.post('/', (req,res) => { if (req.body._id == '') insertIntoMongoDB(req, res); else updateIntoMongoDB(req, res); }); //Creating function to insert data into MongoDB function insertIntoMongoDB(req,res) { var course = new Course(); course.courseName = req.body.courseName; course.courseId = req.body.courseId; course.courseDuration = req.body.courseDuration; course.courseFee = req.body.courseFee; course.save((err, doc) => { if (!err) res.redirect('course/list'); else console.log('Error during record insertion : ' + err); }); } //Creating a function to update data in MongoDB function updateIntoMongoDB(req, res) { Course.findOneAndUpdate({ _id: req.body._id }, req.body, { new: true }, (err, doc) => { if (!err) { res.redirect('course/list'); } else { if (err.name == 'ValidationError') { handleValidationError(err, req.body); res.render("course/courseAddEdit", { //Retaining value to be displayed in the child view viewTitle: 'Update Course Details', employee: req.body }); } else console.log('Error during updating the record: ' + err); } }); } //Router to retrieve the complete list of available courses router.get('/list', (req,res) => { Course.find((err, docs) => { if(!err){ res.render("course/list", { list: docs }); } else { console.log('Failed to retrieve the Course List: '+ err); } }); }); //Creating a function to implement input validations function handleValidationError(err, body) { for (field in err.errors) { switch (err.errors[field].path) { case 'courseName': body['courseNameError'] = err.errors[field].message; break; default: break; } } } //Router to update a course using it's ID router.get('/:id', (req, res) => { Course.findById(req.params.id, (err, doc) => { if (!err) { res.render("course/courseAddEdit", { viewTitle: "Update Course Details", course: doc }); } }); }); //Router Controller for DELETE request router.get('/delete/:id', (req, res) => { Course.findByIdAndRemove(req.params.id, (err, doc) => { if (!err) { res.redirect('/course/list'); } else { console.log('Failed to Delete Course Details: ' + err); } }); }); module.exports = router;
Теперь, когда мы закончили с бэкэнд-файлами, следующим шагом будет создание представлений. Для этого сначала нужно создать оболочку для дочерних представлений. Но перед этим создайте папку с названием «просмотры». Внутри этой папки создайте еще две папки с именами «курс» и «макеты» соответственно с расширением .hbs. Теперь перейдите в папку «Layouts» и создайте оболочку с именем «mainLayout.hbs». Этот файл будет содержать базовый скелет приложения, который также будет отражен в дочерних представлениях. В этот файл я также вставляю изображение, поэтому для этого я создам локальную папку с именем img и сохраню свое изображение внутри.
Чтобы создать этот файл, введите следующие коды:
mainLayout.hbs
<!DOCTYPE html> <html> <head> <title>Edureka Node.js MongoDB Demo</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous"> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fajax.googleapis.com%2Fajax%2Flibs%2Fjquery%2F3.2.1%2Fjquery.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" /> <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20src%3D%22https%3A%2F%2Fmaxcdn.bootstrapcdn.com%2Fbootstrap%2F3.3.7%2Fjs%2Fbootstrap.min.js%22%3E%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="<script>" title="<script>" /> </head> <body class = "bg-info"> <div align="center"> <!-- Inserting the image --> <img src="/static/edurekaLogo.png" alt="Edureka Logo"> </div> <div class="row"> <div class="col-md-6 offset-md-3" style="background-color: #fff; margin-top: 40px; padding:20px;"> <!-- retrieving HTML String from the child Views --> {{{body}}} </div> </div> </body> </html>
Наконец, внутри папки курса мы создадим два дочерних представления, одно из которых будет использоваться для добавления или обновления курса, а второе представление отобразит полный список доступных курсов. Давайте сначала сосредоточимся на первом представлении, то есть courseAddEdit.hbs, которое будет выглядеть, как на скриншоте ниже.
Итак, позвольте мне теперь показать, как построить это представление.
Как видно на скриншоте, страница будет содержать четыре поля ввода и две кнопки. Одна кнопка будет отправлять данные, введенные пользователем в базу данных, а вторая кнопка отобразит полный список курсов, доступных в базе данных. Чтобы убедиться, что введенные данные верны, вам также необходимо добавить некоторые проверки. После этого вы сможете увидеть это представление, используя URL-адрес "/ course". Ниже приведен код, вам нужно будет создать файл courseAddEdit.hbs:
courseAddEdit.hbs
<!-- Obtaining value from the course controller --> <h3>{{viewTitle}}</h3> <form action="/course" method="POST"> <input type="hidden" name="_id" value="{{course._id}}"> <div class = "form-group"> <label>Course Name</label> <input type="text" maxlength="100" class="form-control" name="courseName" placeholder="Course Name" value="{{course.courseName}}"> <div class="text-danger"> {{course.courseNameError}}</div> </div> <div class = "form-group"> <label>Course ID</label> <input type="number" min='10000' max='99999' class="form-control" name="courseId" placeholder="Course Id" value="{{course.courseId}} " required> </div> <div class = "form-row"> <div class = "form-group col-md-6"> <label>Course Duration</label> <input type="number" min='10' max='99' class="form-control" name="courseDuration" placeholder="Course Duration (Hrs)" "{{course.courseDuration}} " required> </div> <div class = "form-group col-md-6"> <label>Course Fee</label> <input type="number" min='100' max="100000" class="form-control" name="courseFee" placeholder="Course Fee (USD)" "{{course.courseFee}} " required> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-info"><i class="fa fa-database"></i> Submit</button> <a class="btn btn-secondary" href="/course/list"><i class="fa fa-list-alt"></i> View All Courses</a> </div> </form>
Теперь позвольте мне показать вам следующее представление, то есть list.hbs, которое извлечет полный список доступных курсов из базы данных и отобразит их на вашем экране:
В этом представлении я использую таблицу для отображения списка курсов. В этой таблице будет пять столбцов, в первых четырех будут отображаться сведения о курсе, а в последнем столбце вы сможете редактировать / удалять запись непосредственно из интерфейса приложения. Контроллеры этих функций уже созданы в файле script.js. Итак, осталось только добавить представление и для этого создать файл list.hbs и ввести код, написанный ниже.
list.hbs
<div> <a class="btn btn-secondary" href="/course"><i class="fa fa-plus"></i> Create New</a> <h3 align="center">Edureka's Course List</h3> </div> <table class="table table-striped"> <thead> <tr> <th>Course Name</th> <th>Course Id</th> <th>Course Duration(Hrs)</th> <th>Course Fee(USD)</th> <th></th> </tr> </thead> <tbody> {{#each list}} <tr align="center"> <td>{{this.courseName}}</td> <td>{{this.courseId}}</td> <td>{{this.courseDuration}}</td> <td>{{this.courseFee}}</td> <td> <a href="/course/{{this._id}}"> Edit </a> <a href="/course/delete/{{this._id}}" onclick="return confirm('Are you sure to delete this record ?');"> Delete </a> </td> </tr> {{/each}} </tbody> </table>
На этом мы завершаем кодирование. Теперь пора протестировать наше приложение. Для этого откройте командную строку и перейдите в папку проекта или, если вы используете IDE, откройте терминал и введите следующую команду, чтобы запустить сервер.
nodemon script.js
Теперь вы можете запустить свое приложение в любом браузере по адресу http: // localhost: 8080.
После того, как вы добавили свои собственные данные, вы можете вернуться в MongoDB и проверить, были ли добавлены данные туда или нет. Если вы обратитесь к приведенному ниже снимку экрана, вы увидите, что все мои данные были успешно добавлены. Это означает, что мой MongoDB подключен и отлично работает с моим API Node.js.
На этом мы подошли к концу этого руководства по Node.js по MongoDB. Надеюсь, мне удалось прояснить концепции и помочь вам понять, как именно MongoDB работает с Node.js. Если вы хотите ознакомиться с другими статьями о самых популярных технологиях на рынке, таких как искусственный интеллект, Python, этический взлом, посетите официальный сайт Edureka.
Обязательно обратите внимание на другие статьи в этой серии, которые объяснят различные другие аспекты Nodejs.
2. Создайте приложение CRUD с использованием Node.js и MySQL
Первоначально опубликовано на https://www.edureka.co 14 мая 2019 г.