5 просунутих прийомів NodeJS з ExpressJS

5 просунутих прийомів NodeJS з ExpressJS
Переклад 7 хв. читання
16 серпня 2023

Кожен розробник прагне мати ефективну та надійну кодову базу, яку легко зрозуміти, модифікувати та розширювати. Переймаючи найкращі практики та вивчаючи передові методи, ми можемо розкрити справжній потенціал 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. Існує багато передових практик, які можуть значно покращити якість коду вашого проєкту. Не соромтеся вивчати ці методи та застосовувати їх для покращення вашої кодової бази.

Джерело: 5 Advanced NodeJS Techniques with ExpressJS
Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація