Улучшите свои приложения Node.js с помощью настраиваемых кодов ошибок.

В этой статье вы узнаете, как перезаписать промежуточное программное обеспечение для обработки ошибок по умолчанию, создав собственную функцию промежуточного программного обеспечения, которая будет централизовать ошибки, выдаваемые приложением, и выполнять соответствующую логику в соответствии с контекстом.

Встроенное промежуточное ПО для обработки ошибок

Инфраструктура Express поставляется со встроенным промежуточным программным обеспечением для обработки ошибок, которое отвечает за обнаружение любых ошибок во время выполнения, с которыми может столкнуться приложение, что предотвращает его сбой.

По умолчанию он регистрирует ошибку в консоли и отвечает клиенту HTTP 500 Internal Server Error, содержащим сообщение об ошибке, а также трассировку стека (если не в рабочей среде) в формате HTML.

{
  status: 500,
  statusText: 'Internal Server Error',
  data: '<!DOCTYPE html>\n' + '<html lang="en">\n' + '<head>\n' + '<meta
charset="utf-8">\n' + '<title>Error</title>\n' + '</head>\n' + '<body>\n' +
'<pre>Error: BROKEN<br> &nbsp; &nbsp;at /Users/rludosanu/Projects/test/
test.js:5:9<br> &nbsp; &nbsp;at Layer.handle [as handle_request] (/Users/
rludosanu/Projects/test/node_modules/express/lib/router/layer.js:95:5)<br>
&nbsp; &nbsp;at next (/Users/rludosanu/Projects/test/node_modules/express/
lib/router/route.js:144:13)<br> &nbsp; &nbsp;at Route.dispatch (/Users/
rludosanu/Projects/test/node_modules/express/lib/router/route.js:114:3)<br>
&nbsp; &nbsp;at Layer.handle [as handle_request] (/Users/rludosanu/
Projects/test/node_modules/express/lib/router/layer.js:95:5)<br> &nbsp;
&nbsp;at /Users/rludosanu/Projects/test/node_modules/express/lib/router/
index.js:284:15<br> &nbsp; &nbsp;at Function.process_params (/Users/
rludosanu/Projects/test/node_modules/express/lib/router/
index.js:346:12)<br> &nbsp; &nbsp;at next (/Users/rludosanu/Projects/test/
node_modules/express/lib/router/index.js:280:10)<br> &nbsp; &nbsp;at
expressInit (/Users/rludosanu/Projects/test/node_modules/express/lib/
middleware/init.js:40:5)<br> &nbsp; &nbsp;at Layer.handle [as
handle_request] (/Users/rludosanu/Projects/test/node_modules/express/lib/
router/layer.js:95:5)</pre>\n' + '</body>\n' + '</html>\n'
}

Хотя это полезно, когда приложение находится на ранней стадии разработки, этот обработчик по умолчанию показывает свои ограничения, когда речь идет о реальных API, которые должны быть способны обрабатывать несколько типов ошибок разными способами, например, отвечать клиенту с другим статусом. коды или записи этих ошибок в лог-файлы.

Базовое промежуточное ПО для обработки ошибок

Создание промежуточного программного обеспечения

В Express функция промежуточного программного обеспечения для обработки ошибок ничем не отличается от любого другого промежуточного программного обеспечения, за исключением того, что она принимает 4 аргумента вместо 3:

// File: error-handler.js

function errorHandler(err, req, res, next) {
  console.error(err);
  res.sendStatus(500);
}

module.exports = errorHandler;

Установка промежуточного программного обеспечения

Это промежуточное ПО должно быть смонтировано в приложении через метод app.use() в самом низу стека вызовов, после всех остальных объявлений:

// File: app.js

const express = require('express');
const app = express();

const errorHandler = require('./error-handler');

app.get('/health', (req, res) => { /* ... */ });

app.post('/login', (req, res) => { /* ... */ });

app.post('/signup', (req, res) => { /* ... */ });

app.use(errorHandler);

app.listen(3000);

Вызов промежуточного программного обеспечения

Чтобы передать ошибки этому промежуточному ПО, все, что нам нужно сделать, это вызвать параметр next() контроллера маршрута, который передаст запрос следующему компоненту стека:

// File: app.js

const express = require('express');
const app = express();

const errorHandler = require('./error-handler');

app.post('/login', (req, res, next) => {
  next(new Error('FAILED'));
});

app.use(errorHandler);

app.listen(3000);

Тестирование промежуточного программного обеспечения

Вы можете попробовать сами, запустив это приложение в новом окне терминала:

$ node app.js

И отправив следующий запрос на маршрут POST /login с помощью cURL:

$ curl -v -X POST 127.0.0.1:3000/login

Выдача пользовательских ошибок

Создание нового класса ошибок

Чтобы иметь возможность выдавать пользовательские ошибки, мы собираемся создать новый класс ошибок с именем CustomError, который наследуется от встроенного класса Error, где конструктор будет принимать в качестве аргумента константу code, представляющую тип пользовательской ошибки, вызванной приложением. . Например, INVALID_EMAIL для недействительного адреса электронной почты или USER_NOT_FOUND для пары адрес электронной почты и пароль, которой нет в базе данных.

// File: custom-error.js

class CustomError extends Error {
  constructor(code) {
    super();
    this.code = code;
  }
}

module.exports = CustomError;

Определение пользовательских кодов ошибок

Теперь мы собираемся создать объект, который сопоставляет пользовательские коды ошибок с действительными статусами HTTP и удобочитаемыми сообщениями.

// File: error-codes.js

module.exports = {
  INVALID_EMAIL_OR_PASSWORD: {
    statusCode: 400, // Bad Request
    message: 'Invalid email address or password',
  },
  USER_NOT_FOUND: {
    statusCode: 404, // Not Found
    message: 'User not found',
  },
  INTERNAL_ERROR: {
    statusCode: 500, // Internal Server Error
    message: 'Internal Server Error',
  },
};

Улучшение промежуточного ПО для обработки ошибок

Теперь, когда у нас есть класс CustomError и список пользовательских ошибок, мы можем улучшить наше промежуточное ПО для обработки ошибок, чтобы оно:

  1. Анализирует ошибку code, содержащуюся в объекте err.
  2. Сопоставляет ошибку code со списком пользовательских ошибок.
  3. Отправляет ответ клиенту с соответствующим кодом состояния и сообщением об ошибке или стандартной внутренней ошибкой сервера HTTP 500.
// File: error-handler.js

const errorCodes = require('./error-codes');

function errorHandler(err, req, res, next) {
  const code = (err && err.code) || null;
  const error = errorCodes[code] || errorCodes['INTERNAL_ERROR'];

  return res
    .status(error.statusCode)
    .json({ message: error.message });
};

module.exports = errorHandler;

Полный пример

Чтобы проиллюстрировать это, мы собираемся создать простое приложение, реализующее маршрут POST /login, позволяющий пользователям аутентифицироваться с помощью адреса электронной почты и пароля.

Приложение

Когда запрос достигает маршрута POST /login, его полезная нагрузка преобразуется в объект JavaScript с помощью встроенного промежуточного программного обеспечения express.json() и перенаправляется в функцию loginController().

// File: app.js

const express = require('express');
const app = express();

const loginController = require('./login-controller');
const errorHandler = require('./error-handler.js');

app.post('/login', express.json(), loginController);

app.use(errorHandler);

app.listen(3000);

Контроллер

loginController() вызывает loginService() с проанализированными данными, содержащимися в свойстве req.body. Если аутентификация прошла успешно, он отвечает клиенту HTTP 200, содержащим строку токена доступа. В противном случае он перенаправляет любую возникшую ошибку промежуточному программному обеспечению для обработки ошибок с помощью функции next().

// File: login-controller.js

const loginService = require('./login-service');

function loginController(req, res, next) {
  try {
    const token = loginService(req.body);
    res.json({ token });
  } catch(error) {
    next(error);
  }
}

module.exports = loginController;

Обслуживание

loginService() проверяет данные, отправленные loginController(), и выдает CustomError, если email или password недействительны или не соответствуют ожидаемым значениям. В противном случае возвращается строка, представляющая токен доступа.

// File: login-service.js

const CustomError = require('./custom-error');

function loginService(data) {
  const { email, password } = data;
  const token = 'access_token';

  if (!email || !password) {
    throw new CustomError('INVALID_EMAIL_OR_PASSWORD');
  }

  if (email !== '[email protected]' || password !== 'helloworld') {
    throw new CustomError('USER_NOT_FOUND');
  }

  return token;
}

module.exports = loginService;

Тестирование приложения

Теперь вы можете запустить это приложение в новом окне терминала:

$ node app.js

И убедитесь, что мы получаем HTTP 200, содержащий токен доступа, когда адрес электронной почты и пароль совпадают:

$ curl -v -X POST \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"helloworld"}' \
127.0.0.1:3000/login

Что мы можем получить HTTP 404, когда электронная почта не совпадает:

$ curl -v -X POST \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"helloworld"}' \
127.0.0.1:3000/login

То, что мы можем HTTP 400, когда электронная почта отсутствует:

$ curl -v -X POST \
-H 'Content-Type: application/json' \
-d '{"email":"","password":"helloworld"}' \
127.0.0.1:3000/login

Поднимите свои навыки бэкенда и Node.js на новый уровень на learnbackend.dev