Кожен розробник прагне мати ефективну та надійну кодову базу, яку легко зрозуміти, модифікувати та розширювати. Переймаючи найкращі практики та вивчаючи передові методи, ми можемо розкрити справжній потенціал NodeJS та значно покращити якість наших проєктів. У цьому блозі ми зосередимося на п'яти передових техніках NodeJS з використанням ExpressJS. Отже, пристебніть ремені безпеки та приготуйтеся до їх вивчення.
1. Додавання middleware
Замість того, щоб додавати middleware до кожного маршруту, додайте його на початку списку маршрутів, використовуючи метод use. Таким чином, будь-які маршрути, визначені під цим middleware, будуть автоматично проходити через нього, перш ніж потрапляти до відповідних обробників маршрутів.
❌ Уникайте використання middleware на кожному маршруті
const route = express.Router();
const {login} = require("../controllers/auth");
route.get('/login', login)
// isAuthenticated - цей проміжний модуль перевіряє, чи
// ви автентифіковані чи ні
route.get('/products', isAuthenticated, fetchAllProducts);
route.get('/product/:id', isAuthenticated, getProductById)
✅ Краще зробіть так
// Маршрут без використання middleware
route.get('/login', login)
// Функція middleware: isAuthenticated
// Це буде застосовано до всіх маршрутів, визначених після цієї точки
route.use(isAuthenticated);
// Маршрути, які будуть автоматично перевіряти middleware
route.get('/products', fetchAllProducts);
route.get('/product/:id', getProductById);
Такий підхід допомагає зберегти код організованим і уникнути повторного використання middleware для кожного маршруту окремо.
2. Глобальна обробка помилок
Замість того, щоб структурувати відповідь на помилку на кожному контролері, ми можемо використовувати глобальну обробку помилок NodeJS. По-перше, створіть власний клас AppError
, похідний від вбудованого класу Error
. Цей кастомний клас дозволяє вам налаштувати об'єкт помилки за допомогою додаткових властивостей, таких як statusCode
та status
.
// Custom Error class
module.exports = class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = statusCode < 500 ? "error" : "fail";
Error.captureStackTrace(this, this.constructor);
}
};
Після того як ви створили кастомний клас помилки, додайте в кореневий файл з маршрутами глобальний middleware-оброблювач помилок. Цей обробник приймає чотири параметри (err
, req
, res
, next
) і обробляє помилки в усьому застосунку. Всередині глобального обробника помилок ви форматуєте відповідь про помилку на основі statusCode
об'єкта помилки, його стану і властивостей повідомлення. Ви можете налаштувати цей формат відповіді відповідно до ваших потреб. Крім того, для середовищ розробки включено опцію стеку.
// Express setup
const express = require('express');
const app = express();
app.use('/', (req, res) => {
res.status(200).json({ message: "it works" });
});
app.use('*', (req, res) => {
res.status(404).json({
message: `Can't find ${req.originalUrl} this route`,
});
});
// 👇 add a global error handler after all the routes.
app.use((err, req, res, next) => {
err.status = err.status || "fail";
err.statusCode = err.statusCode || 500;
res.status(err.statusCode).json({
status: err.status,
message: transformMessage(err.message),
stack: process.env.NODE_ENV === "development" ? err.stack : undefined,
});
});
Після його додавання ви можете згенерувати помилку за допомогою next(new AppError(message, statusCode))
. Функція next автоматично передає помилку до middleware глобального обробника помилок.
// inside controllers
// route.get('/login', login);
exports.login = async (req, res, next) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email }).select("+password +lastLoginAt");
if (!user || !(await user.correctPassword(password, user.password))) {
// 👇 like this
return next(new AppError("Invalid Email / Password / Method", 404));
}
// Custom logic for generating a token
const token = 'generated_token';
res.status(200).json({ token });
} catch(error) {
next(error
}
});
Загалом, такий підхід спрощує обробку помилок, централізуючи її в одному місці, що полегшує підтримку та налаштування відповідей з помилками у вашому застосунку.
3. Використання спеціальної функції Try-Catch
Замість того, щоб вручну обгортати кожну функцію контролера блоком try-catch
, ми можемо використовувати спеціальну функцію, для досягнення тієї ж мети.
// ❌ Avoid this
// Using try-catch block each controllers
exports.login = async (req, res, next) => {
try {
// logic here
} catch(error) {
res.status(400).json({ message: 'You error message'}
}
});
Функція tryCatchFn
приймає на вхід функцію (fn)
і повертає нову функцію, яка обгортає вихідну функцію блоком try-catch
. Якщо в обгорнутій функції виникає помилка, вона перехоплюється за допомогою методу catch, і помилка передається до наступної функції, яка обробляється глобальним обробником помилок.
// ✅ Instead, do this
const tryCatchFn = (fn) => {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
}
// To use this custom function, you can wrap your controller
// functions with tryCatchFn:
exports.login = tryCatchFn(async (req, res, next) => {
// logic here
});
Обертаючи функції контролера за допомогою tryCatchFn
, ви гарантуєте, що будь-які помилки, які виникають у цих функціях, будуть автоматично перехоплені та передані до глобального обробника помилок, усуваючи необхідність додавати блоки try-catch
окремо.
Такий підхід допомагає централізувати обробку помилок у чистіший та лаконічніший спосіб, роблячи ваш код зручнішим для супроводу та зменшуючи кількість повторюваного коду обробки помилок.
4. Розділіть головний файл на дві частини.
При розробці застосунків на NodeJS з використанням Express зазвичай створюється головний файл, який містить усю бізнес-логіку, визначення маршрутів та налаштування сервера. Однак, в міру зростання проєкту, управління та підтримка єдиного файлу, який обробляє все, може стати складною задачею.
Один з рекомендованих способів розв'язання цієї проблеми та підтримки чистоти й впорядкованості коду - розділити головний файл на дві частини: одну для маршрутів, а іншу для налаштування або конфігурації сервера. Ось приклад:
// app.js
const express = require('express');
const app = express();
/* Middlewares */
app.get('/', (req, res) => {
res.status(200).json({ message: "it works" });
})
app.use(/* Global Error Handler */);
module.exports = app;
// server.js
const app = require('./app');
const port = process.env.PORT || 5001;
app.listen(port, () => console.log('Server running at', port));
5. Відокремлення маршрутів від контролерів
Для досягнення більш організованої та модульної кодової бази я рекомендую відокремлювати маршрути від контролерів. Ця практика допомагає підтримувати чіткий розподіл завдань і покращує читабельність та ремонтопридатність коду. Ось приклад, який демонструє розділення маршрутів і контролерів.
// ❌ Avoid this
const route = express.Router();
route.get('/login', tryCatchFn(req, res, next) => {
// logic here
}))
// ✅ Do this
const route = express.Router();
const {login} = require("../controllers/auth");
route.get('/login', login);
Висновок
У цій статті ми обговорили різні передові методи написання чистого і легкого в обслуговуванні коду на NodeJS. Існує багато передових практик, які можуть значно покращити якість коду вашого проєкту. Не соромтеся вивчати ці методи та застосовувати їх для покращення вашої кодової бази.
Ще немає коментарів