У backend розробці прийнято не писати весь код в одному файлі, а розділяти його на блоки в залежності від функціонала. Реалізують це у вигляді MVC-архітектури (модель-вид-контроллер). Тобто розділяють бізнес-логіку і презентацію.
Модель - це блоки роботи з базами даних і різні серверні обробки
Вид - блоки, які комунікують із зовнішнім світом. (повернення json, route, html тощо). Тобто те, що віддаємо назовні.
Контролер - забезпечує взаємодію моделі та виду, які не комунікують напряму між собою.
Проведемо рефакторинг за визначеною архітектурою код розглянутий .
А також саму функцію контроллера (назвемо її, наприклад createUser):
controllers/userController.js
exports.createUser = async (req, res) => {
try {
const { name, year, email } = req.body;
// тут має бути валідація даних, які прийшли
//створюємо обʼєкт нового користувача, генеруємо для нього унікальний id
const newUser = { name, year, email, id: uuid() };
// зберігаємо користувача в базу даних (usersdb.json)
const usersDB = await fs.readFile("./usersdb.json");
const users = JSON.parse(usersDB);
users.push(newUser);
await fs.writeFile("./usersdb.json", JSON.stringify(users));
//надсилаємо відповідь на фронтенд
res.status(201).json({
msg: "User created",
user: newUser,
});
} catch (err) {
console.log(err);
res.sendStatus(500);
}
};
В index.js імпортуємо контролери й підключаємо до роуту
index.js
const userController = require("./controllers/userController");
// some code
app.post("/users", userController.createUser);
Після кожного невеликого рефакторингу не забуваємо перевіряти чи не порушився функціонал.
За аналогією виносимо всі інші контролери також у файл userController.js. Назви їм дамо, наприклад такі: getAllUsers, getOneUser, updateUser, deleteUser.
Нижче розгорни, щоб побачити весь код цього файлу.
Як ми побачили вище, в index.js лишилися обробники роутів користувача. Але сервер може обробляти запити не тільки користувача, але й інші запити й згодом їх перелік стане довгим. Видно, що спільним для них є базовий запит users. У express передбачена можливість винесення однотипних роутів завдяки Router. Підключаємо і створюємо екземпляр роута:
Переносимо всі роути з index.js до userRoutes.js і замість app вказуємо router. Крім того прибираємо базовий урл users, бо його ми вкажемо в index.js. І експортуємо наш роутер:
Після цього нам потрібно в index.js заімпортувати роути повʼязані з користувачами і вказати який роут оброблятиме запити за users (синтаксис як у middleware):
Проміжні функції (middleware) також доречно виносити в окремі файли. Наприклад ми стоврювали в index.js кастомний middleware отримання обʼєкту користувача. Давайте його винесемо в окремий файл userMiddlewares.js. Порядок такий само як і при роботі з контроллерами. Підключаємо у файлі необхідні модулі і створюємо функцію та експортуємо її.
У роуті ми перераховуємо по порядку всі middleware, а останнім вказуємо контроллер обробки запиту. Примітно, що цю проміжну функцію треба використовувати тільки там де в маршруті присутній id:
РЕМАРКА: Припустимо у нас є в системі кроистувач, який обробляється не за правилами за запитом маршруту users/bad, у разі, якшо ми його пропигемо вкінці, то він проігнорується. Тому кастомні маршрути, щоб вони опрацювалися, потрібно прописувати перд тими роутами в яких обробка іде за подібною схемою з динамічним параметром у запиті.
Кастомний обробник помилок
Можна написати клас специфічної обробки помилок.
Для цього створимо на базі класу Error наш клас AppError. Він прийматиме статус помилки і повідомлення:
Для наочності використання цього класу застосуємо його в нашому кастомному middleware. Підключаємо:
middlewares/userMiddlewares.js
const { AppError } = require("../utils");
Пропишемо всередині якусь перевірку. наприклад на довдину id, щоб вона була не короштою за 10 символів, інакше згенеруємо помилку:
middlewares/userMiddlewares.js
if (userId.length < 10) {
throw new AppError(400, "Invalid Id...");
}
В такому разі помилку буде опрацьовувати глобальний хендлер помилки. Для цього потрібно просто замість повернення статусу помилки в секції case(err) {} передати естафету функції next і як параметр передати їй помилку.
middlewares/userMiddlewares.js
catch (err) {
console.log(err);
next(err);
// замість цього: res.sendStatus(500);
}
Як бачимо у наших проміжних обробниках і контроллерах постійно повторюється конструкція try{}catch(){}. Підхід Don't repeate yourself каже, що цей код треба покращити.
Напишемо в utils в файлі catchAsync.js функцію-обгортку, яка прийматиме нашу функцію і повертатиме таку саму функцію, а у разі виникнення помилки передаватиме на обробник помилок нашу еррорку.