Обработка ошибок в Express.js

год назад·4 мин. на чтение

Пишем middleware для Express.js для обработки ошибок.

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

Предусловия

  • Установленный NodeJS
  • Знание NodeJS и Express.js
  • Знание того, как работает middleware в Express.js

Настройка проекта

Создадим базовое приложение Express.js с одним эндпоинтом. Этот эндпоинт (или ручка) будет методом POST, который принимает два входных параметра title и author.
const express = require('express');
const bodyParser = require('body-parser');

const app = express();
const port = 3000;

app.use(bodyParser.json());

app.post('/post', async (req, res) => {
  const { title, author } = req.body;

  if (!title || !author) {
    return res.status(400).json({
      status: 'error',
      message: 'Missing required fields: title or author'
    });
  }

  try {
    const post = await db.post.insert({ title, author });
    res.json(post);
  } catch (error) {
    return res.status(500).json({
      status: 'error',
      message: 'Internal Server Error'
    });
  }
});

app.listen(port, () =>
  console.log(`app is listening at http://localhost:${port}`)
);
Мы проверяем, существуют ли title и author, если нет, мы выбрасываем ошибку 400 и отправляем обратно JSON со статусом и сообщением. Если title и author существуют, приложение все равно будет аварийно завершать работу, потому что db не определена, и наш блок try/catch поймает его и отправит обратно ошибку 500 и JSON со статусом и сообщением. Со временем, по мере роста количества эндпоинтов и проверок, ввод res.status(4xx).json({ some: JSON }) каждый раз может быстро стать громоздким, а также создать большую избыточность кода. Почему бы не сделать что-то вроде throw new BadRequest('message')? Давайте посмотрим, как мы можем это реализовать.

Создание утилит для ошибок

Теперь создадим функции, которую мы можем использовать для генерации ошибок. Создадим новую папку /utils и файл errors.js.
// /utils/errors.js

class GeneralError extends Error {
  constructor(message) {
    super();
    this.message = message;
  }

  getCode() {
    if (this instanceof BadRequest) {
      return 400;
    } if (this instanceof NotFound) {
      return 404;
    }
    return 500;
  }
}

class BadRequest extends GeneralError { }
class NotFound extends GeneralError { }

module.exports = {
  GeneralError,
  BadRequest,
  NotFound
};
Этот файл определяет, какие ошибки мы можем выбросить в нашем приложении. Класс GeneralError расширяет Error и используется для получения наших сообщений и кодов состояния. Здесь у нас есть BadRequest и NotFound, которые расширяют GeneralError. Мы также указываем их коды ошибок в блоке getCode в GeneralError. Для простоты этой демонстрации у нас будут толькоBadRequest и NotFound. Если вы хотите добавить другие типы ошибок, все, что вам нужно сделать, это создать новый класс, который расширяет GeneralError и обновить его код состояния внутри блока getCode.

Создание middleware для обработки ошибок

Теперь мы сосредоточимся на реализации express middleware для обработки ошибок в нашем приложении. Создадим новый файл /middleware/handleErrors.js.
// /middleware/handleErrors.js

const { GeneralError } = require('../utils/errors');

const handleErrors = (err, req, res, next) => {
  if (err instanceof GeneralError) {
    return res.status(err.getCode()).json({
      status: 'error',
      message: err.message
    });
  }

  return res.status(500).json({
    status: 'error',
    message: err.message
  });
}


module.exports = handleErrors;
Примечание: middleware для обработки ошибок принимает 4 аргумента (ошибка в качестве первого аргумента), а не 3 аргумента для обычного middleware . Функция middleware handleErrors проверяет, является ли переданная ошибка экземпляром GeneralError. Если это так, мы возвращаем код состояния и тело JSON со статусом и сообщением.

Использование middleware для обработки ошибок

Давайте обновим наше приложение и эндпоинт, чтобы использовать наш недавно созданный middleware для обработки ошибок. Middleware для обработки ошибок должно быть помещено последним, после всех других middleware и маршрутов, чтобы оно функционировало должным образом.
const express = require('express');
const bodyParser = require('body-parser');
const handleErrors = require('./middleware/handleErrors');
const { BadRequest } = require('./utils/errors');

const app = express();
const port = 3000;

app.use(bodyParser.json());

app.post('/post', async (req, res, next) => {
  const { title, author } = req.body;
  
  try {
    if (!title || !author) {
      throw new BadRequest('Missing required fields: title or author');  // строка 16
    }
    const post = await db.post.insert({ title, author });
    res.json(post);
  } catch (err) {
    next(err)
  }
});

app.use(handleErrors); // строка 25

app.listen(port, () =>
  console.log(`app is listening at http://localhost:${port}`)
);
Во-первых, мы импортируем handleErrors и регистрируем его как middleware, как показано в строке 25. Мы также импортируем BadReqest и обновляем строку 16, чтобы выбросить новый BadRequest, если title и author отсутствуют. Наконец, мы добавляем next в качестве третьего аргумента в наш обработчик маршрута. Затем мы обновляем блок catch, чтобы передать ошибки в next чтобы наш обработчик ошибок мог обработать его.

Тестирование middleware для обработки ошибок

Для тестирования middleware используем Postman. Сначала мы делаем POST-запрос без body. Мы получаем ошибку 400 со статусом и сообщением в формате JSON.
{
  "status": "error",
  "message": "Missing required fields: title or author"
}
Теперь давайте сделаем еще один запрос POST и передадим title и author. На этот раз мы получаем ошибку 500 со статусом и сообщением в JSON.
{
  "status": "error",
  "message": "db is not defined"
}
Простое и чистое решение для обработки ошибок в приложении Express.js.

Итоги

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

Как запустить бэкэнд на Node JS

год назад·3 мин. на чтение

В этой статье напишем простой бэкэнд на NodeJS. Этот сервер будет полезен для разработки веб-приложений.

Необходимые условия

Перед созданием бэкэнда на node js нужно, чтобы были установлены все необходимые библиотеки, IDE и ПО, а именно:
  • NodeJS и npm. Их можно скачать с официального сайта nodejs.org. npm установится автоматически вместе с NodeJS.
  • Предпочитаемый IDE, например, Visual Studio Code.
  • Опционально, установить git для удобной работы с кодом.

О приложении

В этой статье напишем небольшой REST API сервер, который будет отдавать список дел по GET запросу. Структура папок будет выглядеть следующим образом.
my-app/

Создание бэкэнда на NodeJS

Запустим команду в папке my-app/ для инициализации проекта:
npm init -y
Эта команда создаст файл package.json. Ключ -y заполнит все поля в package.json значениями по умолчанию. Файл package.json содержит информацию о проекте - название, версия, описание, список зависимостей, скрипты для запуска, сборки и тестирования. Все поля можно будет изменить и вручную после его создания. После инициализации package.json будет выглядеть следующим образом.
{
  "name": "my-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
Для создания сервера будем использовать express. Установим его с помощью команды:
npm install express
Создадим файл index.js. В нем будет находится код для запуска сервера. Если порт не задан в переменных среды, то будет использован порт 3010.
// my-app/index.js

const express = require('express');
 
const PORT = process.env.PORT || 3010;
const app = express();
 
app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  next();
});
 
app.listen(PORT, () => {
  console.log(`Server listening on ${PORT}`);
});

Запуск сервера

В поле scripts файла my-app/package.json добавим команду для запуска сервера. В результате сможем запускать наш сервер с помощью команды npm start.
...
"scripts": {
  "start": "node ./index.js"
},
...
Из папки my-app/ запустим команду npm start. Если ошибок нет, получим сообщение, что сервер прослушивает порт 3010.
PS C:\tutorials-coding\nodejs-backend\my-app> npm start

> my-app@1.0.0 start
> node ./index.js

Server listening on 3010

Создание API

API это интерфейс, с помощью которого приложение будет общаться с веб-сервером, т.е. запрашивать, изменять или удалять данные. Мы сможем получать эти данные, например, в веб-приложении на React. О том как, делать API запросы к серверу из React приложения, можно прочитать в этой статье. В нашем случае мы создадим API для получения списка дел в формате JSON. Создадим файл /my-app/todo-items.json c объектами todo. Этот массив будем отдавать по запросу /api/todo-items.
[
  {
    "id": 1,
    "text": "Изучить Node JS",
    "done": true
  },
 {
    "id": 2,
    "text": "Изучить JavaScript",
    "done": true
  },
  {
    "id": 3,
    "text": "Изучить React JS",
    "done": true
  },
  {
    "id": 4,
    "text": "Написать приложение",
    "done": false
  }
]
Далее добавим код для создания эндпоинта /api/todo-items. Веб-приложение сможет отправлять на него GET запрос.
// my-app/index.js

// ...

const todoItems = require('./todo-items.json');
app.get('/api/todo-items', (req, res) => {
 res.json({ data: todoItems });
});

app.listen(PORT, () => {
 console.log(`Server listening on ${PORT}`);
});
Для того чтобы изменения применились нужно перезапустить NodeJS сервер. Для остановки скрипта в терминале, в котором запущен npm start, нужно нажать Ctrl + C (Command + C). Далее снова запускаем npm start. Для проверки эндпоинта, вставим в адресную строку браузера http://localhost:3010/api/todo-items. В результате получим такой ответ. Ответ от Node JS сервера

Отправка HTTP запроса к серверу

К этому моменту у нас уже есть рабочий сервер, который умеет принимать запросы и отдавать данные. Для проверки работы сервера вне веб-приложения можно воспользоваться такими программами как Postman или Insomnia.

Автоматический перезапуск сервера после обновления кода

Ранее для того чтобы изменения исходного кода сервера вступили в силу, мы перезапускали сервер. Можно ускорить процесс разработки и автоматизировать эту часть с помощью инструмента nodemon. nodemon будет следить за файлами в каталоге, в котором был запущен nodemon, и если какие-либо файлы изменятся, он автоматически перезапустит ваше node js приложение. Установим nodemon как зависимость для разработки.
npm install --save-dev nodemon
Добавим новый скрипт dev в my-app/package.json.
...
"scripts": {
  "dev": "nodemon ./index.js"
},
...
Далее запустим сервер следующим образом.
npm run dev
Теперь, после изменений в коде, увидим, что сервер перезапускается сам. Исходный код