Підключення Mongoose у проекті
Давайте тепер розберемо REST API додаток та підключення до нього Mongoose. Структура нашої програми буде наступною
├── server.js
├── .env
├── controller
│ └── index.js
├── package.json
├── service
│ ├── schemas
│ | └── task.js
│ └── index.js
└── api
└── index.js
З самим додатком ви можете ознайомитись:
API доступно по цьому URL: https://nodebook-api-mongoose.glitch.me/api/tasks. І ви знову можете за допомогою Postman виконати усі CRUD операції
Роботу з базою даних ми виділяємо в окремий сервіс та поміщаємо його папку
service
, файл index.js
. Визначаємо схеми для документів у папці schemas
.
Роути у нас залишаться як і раніше у папці api
, а ось логіку роботи програми
ми перенесемо до папки controller
. Давайте тепер поговоримо докладніше про
кожний модуль програми
Головний файл та підключення до БД
Файл сервера server.js
. Підключаємо необхідні модулі та створюємо екземпляр
програми.
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
require('dotenv').config();
const app = express();
Підключаємо парсер JSON і дозволяємо кросдоменні запити до нашої програми
через проміжне ПЗ cors
// parse application/json
app.use(express.json());
// cors
app.use(cors());
Підключаємо роути для нашого API, а також обробку помилки 404
та помилок
сервера 500
const routerApi = require('./api');
app.use('/api', routerApi);
app.use((_, res, __) => {
res.status(404).json({
status: 'error',
code: 404,
message: 'Use api on routes: /api/tasks',
data: 'Not found',
});
});
app.use((err, _, res, __) => {
console.log(err.stack);
res.status(500).json({
status: 'fail',
code: 500,
message: err.message,
data: 'Internal Server Error',
});
});
Підключаємось до сервера MongoDB за допомогою mongoose.connect
. Цей метод
повертає проміс, і коли він вирішиться ми стартуємо наш сервер app.listen
.
Якщо ж під час підключення до бази даних сталася помилка, то стартувати сервер немає
ніякої причини і ми виводимо в консоль просто повідомлення про помилку.
const PORT = process.env.PORT || 3000;
const uriDb = process.env.DB_HOST;
const connection = mongoose.connect(uriDb, {
promiseLibrary: global.Promise,
useCreateIndex: true,
useUnifiedTopology: true,
useFindAndModify: false,
});
connection
.then(() => {
app.listen(PORT, function () {
console.log(`Server running. Use our API on port: ${PORT}`);
});
})
.catch(err =>
console.log(`Server not running. Error message: ${err.message}`),
);
Роуты
Файл роутингу api/index.js
у нас став лаконічним та чистим
const express = require('express');
const router = express.Router();
const ctrlTask = require('../controller');
router.get('/tasks', ctrlTask.get);
router.get('/tasks/:id', ctrlTask.getById);
router.post('/tasks', ctrlTask.create);
router.put('/tasks/:id', ctrlTask.update);
router.patch('/tasks/:id/status', ctrlTask.updateStatus);
router.delete('/tasks/:id', ctrlTask.remove);
module.exports = router;
Ми імпортуємо контролер і для кожного маршруту та необхідного методу HTTP
викликаємо відповідний метод контролера ctrlTask
. При подальшій роботі над
додатком тут можуть з'явитися проміжні ПЗ для авторизації необхідних
маршрутів, для валідації даних, що передаються, та інші допоміжні функції.
Контролери
Тут ми помістимо основну логіку роботи нашої програми. Фактично контролер у нашому випадку якийсь диспетчер, який приймає запит на обробку маршруту від роуту, звертається до бази даних через сервіс та з отриманим результатом формує відповідь програми.
Отримати список усіх завдань достатній простий обробник
const get = async (req, res, next) => {
try {
const results = await service.getAlltasks();
res.json({
status: 'success',
code: 200,
data: {
tasks: results,
},
});
} catch (e) {
console.error(e);
next(e);
}
};
Ми звертаємось до сервісу та запитуємо у бази всі поточні завдання нашого TODO листа
const results = await service.getAlltasks();
Після відправляємо отриманий результат клієнту
res.json({
status: 'success',
code: 200,
data: {
tasks: results,
},
});
У разі помилки виконання ми надсилаємо її далі next(err)
, для оброблювача
помилок у файлі server.js
Контролер для обробки запиту задачі по id
схожий на попередній, але має
важливу відмінність. У випадку, якщо сервіс нам нічого не повернув з бази
даних, ми повертаємо відповідь 404
- нічого не знайдено.
const getById = async (req, res, next) => {
const { id } = req.params;
try {
const result = await service.getTaskById(id);
if (result) {
res.json({
status: 'success',
code: 200,
data: { task: result },
});
} else {
res.status(404).json({
status: 'error',
code: 404,
message: `Not found task id: ${id}`,
data: 'Not Found',
});
}
} catch (e) {
console.error(e);
next(e);
}
};
Так само ми робимо для контролерів оновлення завдання та видалення - якщо
нічого не знайдено повертаємо помилку 404
Сервіс роботи з БД
Сервіс для виконання операцій над базою даних теж досить лаконічний
const Task = require('./schemas/task');
const getAlltasks = async () => {
return Task.find();
};
const getTaskById = id => {
return Task.findOne({ _id: id });
};
const createTask = ({ title, text }) => {
return Task.create({ title, text });
};
const updateTask = (id, fields) => {
return Task.findByIdAndUpdate({ _id: id }, fields, { new: true });
};
const removeTask = id => {
return Task.findByIdAndRemove({ _id: id });
};
module.exports = {
getAlltasks,
getTaskById,
createTask,
updateTask,
removeTask,
};
У нас є п'ять функцій, які виконують всі основні операції для нашого простого API
Отримати всі завдання, ми використовуємо метод find
, який викликаємо у моделі та
повертаємо результат у контролер
const getAlltasks = async () => {
return Task.find();
};
Отримати конкретне завдання з id
. Ми викликаємо метод findOne
, який знаходить
нам єдиний результат за умовою { _id: id }
. Якщо метод нічого не знайде,
то буде повернено значення null
const getTaskById = id => {
return Task.findOne({ _id: id });
};
Створення нового завдання. Викликаємо у моделі метод create
const createTask = ({ title, text }) => {
return Task.create({ title, text });
};
Оновлюємо завдання методом findByIdAndUpdate
, першим параметром передаємо умову
пошуку - збіг за id
, а другим об'єкт з полями, які необхідно
оновити. Третій параметр вказує, що метод має повернути вже оновлений
документ
const updateTask = (id, fields) => {
return Task.findByIdAndUpdate({ _id: id }, fields, { new: true });
};
І остання операція видаляє завдання з бази даних. Використовуємо метод Mongoose
findByIdAndRemove
, якому передаємо id
завдання, а метод знаходить та видаляє
його з бази даних.
const removeTask = id => {
return Task.findByIdAndRemove({ _id: id });
};
Схема
Останнім, що залишилося нам розглянути цей файл створення схеми для нашої колекції завдань.
const {Schema, model} = require('mongoose');
const task = new Schema(
{
title: {
type: String,
minlength: 2,
maxlength: 70,
},
text: {
type: String,
minlength: 3,
maxlength: 170,
},
isDone: {
type: Boolean,
default: false,
},
},
{ versionKey: false, timestamps: true },
);
const Task = model('task', task);
module.exports = Task;
У принципі, тут нам уже все зрозуміло, ми створюємо схему з трьома полями title
,
text
, isDone
. Визначаємо тип значень, що зберігаються, і накладаємо обмеження. З
нового тут з'явився параметр із опціями при створенні схеми.
{ versionKey: false, timestamps: true }
Дані опції, відключають версіонування документів установкою значення якості
versionKey
в false
. Mongoose за замовчуванням додає версіонування -
параметр __v
, який вказує на версію зміненого документа. Здебільшого це
потрібно для документів зі складною структурою, а оскільки структура нашої схеми
плоска версіонування ми відключаємо. Друга опція включає в нашу схему дві
додаткові властивості, час створення документа createdAt
та час оновлення
updatedAt
. Причому Mongoose автоматично встановлюватиме ці поля при
створенні та змінювати поле updatedAt
при кожному оновленні документа, що
погодьтеся дуже зручно.
У результаті типовий документ у нашій колекції tasks має виглядати так:
{
"isDone": false,
"_id": "5f8e3067975b9d23a0dbd270",
"title": "My work",
"text": "Pain and pain!",
"createdAt": "2020-10-20T00:33:43.492Z",
"updatedAt": "2020-10-20T00:43:16.961Z"
}
Ми розібрали простий додаток REST API та підключення бібліотеки Mongoose у нашому проекті