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

2 года назад·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. Это позволит поддерживать чистоту кода с меньшим количеством избыточного кода. Все, что нам нужно сделать, это выбросить ошибку и передать сообщение. Эта реализация добавляет гибкость для добавления дополнительных классов ошибок для вашего приложения по мере необходимости.

Как загружать файлы в React с помощью NodeJS и Express

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

В этой статье рассмотрим как загрузить файлы в React использованием Express в качестве бэкенда.

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

Во-первых, мы создаем POST API с использованием NodeJS и Express, который поможет нам загружать файлы (изображения, pdf и т. д.) на внутренний сервер.

Настройка серверного проекта

Давайте настроим бэкенд проект NodeJS, выполнив следующие команды одну за другой.
mkdir fileupload
cd fileupload
npm init -y

Установка пакетов

Теперь нам нужно установить четыре пакета: express, express-fileupload, cors и nodemon. Выполните приведенную ниже команду, чтобы установить пакеты.
npm i express express-fileupload cors nodemon
Теперь откройте папку fileupload в вашем любимом редакторе кода и создайте новый файл с именем server.js. Добавьте следующий код в файл server.js.
// server.js

const express = require('express');
const fileUpload = require('express-fileupload');
const cors = require('cors')

const app = express();

// middleware
app.use(express.static('public')); // для доступа к файлам в папке public
app.use(cors());
app.use(fileUpload());

// API для загрузки файлов
app.post('/upload', (req, res) => {
  if (!req.files) {
    return res.status(500).send({ msg: "file is not found" })
  }
   
  const myFile = req.files.file;
 
  // метод mv() помещает файл в папку public
  myFile.mv(`${__dirname}/public/${myFile.name}`,
    function (err) {
      if (err) {
        console.log(err)
        return res.status(500).send({ msg: "Error occurred" });
      }
      // возвращаем ответ с именем файла и его расположением
      return res.send({name: myFile.name, path: `/${myFile.name}`});
  });
})


app.listen(4500, () => {
  console.log('server is running at port 4500');
})
В приведенном выше коде мы сначала импортировали три пакета: express, express-fileupload и cors, а затем создали приложение express вызвав функцию express() Наш маршрут POST API - /upload. Мы помещаем файлы в общую папку public. Для этого нам нужно создать папку public внутри нашего бэкенд-проекта.

Добавление скриптов

Чтобы запускать и перезапускать сервер мы используем nodemon. Откройте файл package.json и добавьте следующий код в scripts.
 "server": "nodemon server.js"
Теперь запустите сервер, выполнив команду npm run server в терминале.

Создание приложения React

Давайте создадим новое React приложение, выполнив следующую команду.
npx create-react-app react-fileupload
Теперь измените текущий рабочий каталог, выполнив приведенную ниже команду.
cd react-fileupload

Установка библиотеки Axios

Нам также необходимо установить клиентскую библиотеку axios, которая используется для выполнения http-запросов.
npm i axios

Создание компонента загрузки файлов

Откройте папку react-fileupload в своем любимом редакторе кода и создайте новый файл с именем fileupload.js внутри папки src. Теперь добавьте следующий код.
// fileupload.js

import React, { useRef, useState } from 'react';
import axios from 'axios';

function FileUpload() {

  // для хранения загруженного файла
  const [file, setFile] = useState('');
  
  // для хранения ответа от бекенда
  const [data, getFile] = useState({ name: "", path: "" });

  const [progress, setProgess] = useState(0); // progessbar
  const el = useRef(); // для доступа к инпуту

  const handleChange = (e) => {
    setProgess(0)
    const file = e.target.files[0]; // доступ к файлу
    console.log(file);
    setFile(file); // сохранение файла
  }

  const uploadFile = () => {
    const formData = new FormData();
    formData.append('file', file); // добавление файла
    axios.post('http://localhost:4500/upload', formData, {
      onUploadProgress: (ProgressEvent) => {
        let progress = Math.round(
          ProgressEvent.loaded / ProgressEvent.total * 100
        ) + '%';
        setProgess(progress);
      }
    }).then(res => {
      console.log(res);
      getFile({
        name: res.data.name,
        path: 'http://localhost:4500' + res.data.path
      })
    }).catch(err => console.log(err))
  }

  return (
    <div>
      <div className="file-upload">
        <input type="file" ref={el} onChange={handleChange} />
        <div className="progessBar" style={{ width: progress }}>
          {progress}
        </div>
        <button onClick={uploadFile} className="upbutton">
          Upload
        </button>
        <hr />
        {/* для показа полученного изображения */}
        {data.path && <img src={data.path} alt={data.name} />}
      </div>
    </div>
  );
}

export default FileUpload;
В приведенном выше коде мы использовали хуки react для управления состоянием, и у нас есть две функции: handleChange и uploadFile. handleChange вызывается после того, как пользователь выбрал файл. Функция uploadFile() используется для загрузки файла в наш /upload api. Существует также индикатор выполнения, который показывает прогресс загрузки файла на сервер, а также мы отображаем изображение после получения ответа от сервера.

Добавление стилей css

Добавьте следующие стили в файл App.css.
.App {
    margin: 2rem auto;
    max-width: 800px;
}

img {
  width: 500px;
  height: 500px;
  object-fit: contain;
}

.progessBar {
  height: 1rem;
  width: 0%;
  background-color: rgb(68, 212, 231);
  color: white;
  padding:2px
}

.file-upload {
  box-shadow: 2px 2px 2px 2px #ccc;
  padding: 2rem;
  display: flex;
  flex-direction: column;

  justify-content: space-between;
  font-size: 1rem;
}

input , div , button{
  margin-top: 2rem;
}

.upbutton {
   width: 5rem;
   padding: .5rem;
   background-color: #2767e9;
   color: aliceblue;
   font-size: 1rem;
   cursor: pointer;
}
Теперь импортируйте компонент FileUpload в файл App.js.
// App.js

import React from 'react';
import FileUpload from './fileupload';
import './App.css';

function App() {
  return (
    <div className="App">
      <FileUpload />
    </div >
  );
}

export default App;
Запустите приложение React, запустив npm start.