Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Загрузка файлов

Файлы из формы приходят в особом формате multipart/form-data, который встроенные middleware Express не разбирают. Для этого есть пакет multer: он сохраняет файл на диск и кладёт сведения о нём в req.file. Добавим к статье блога обложку.

Установка

npm install multer

Настройка

Создайте файл src/middleware/upload.js. В нём описывается, куда и под каким именем сохранять файлы:

// src/middleware/upload.js
import path from 'node:path';
import crypto from 'node:crypto';
import multer from 'multer';
 
const storage = multer.diskStorage({
  destination: 'public/uploads',
  filename(req, file, callback) {
    const extension = path.extname(file.originalname).toLowerCase();
    callback(null, `${crypto.randomUUID()}${extension}`);
  },
});
 
export const upload = multer({
  storage,
  limits: { fileSize: 2 * 1024 * 1024 },
  fileFilter(req, file, callback) {
    const allowed = ['image/jpeg', 'image/png', 'image/webp'];
    callback(null, allowed.includes(file.mimetype));
  },
});

Файлы попадут в public/uploads/ под случайными именами, размер ограничен 2 МБ, принимаются только картинки JPEG, PNG и WebP. Папку public/uploads создайте заранее.

Форма

В форме views/posts/new.ejs нужны атрибут enctype и поле типа file:

<form method="post" action="/posts" enctype="multipart/form-data">
  <input name="title">
  <textarea name="body"></textarea>
  <input type="file" name="cover" accept="image/*">
  <button>Опубликовать</button>
</form>

Без enctype="multipart/form-data" браузер отправит только имя файла, а не его содержимое.

Маршрут

Middleware upload.single('cover') ставится перед обработчиком. Аргумент — значение name у поля файла:

// src/routes/posts.js
import { upload } from '../middleware/upload.js';
 
router.post('/', upload.single('cover'), postsController.store);

Обработчик

Multer разбирает и текстовые поля формы, поэтому req.body работает как прежде. Сведения о файле лежат в req.file:

// src/controllers/postsController.js
export function store(req, res) {
  const post = {
    id: posts.length + 1,
    title: req.body.title,
    body: req.body.body,
    cover: req.file ? `/uploads/${req.file.filename}` : null,
  };
  posts.push(post);
 
  res.redirect(`/posts/${post.id}`);
}

Если файл не выбран или не прошёл фильтр, req.file будет undefined. На странице статьи обложку выводят так: <img src="<%= post.cover %>" alt=""> внутри условия if (post.cover).

Поле req.fileЧто содержит
filenameИмя сохранённого файла
originalnameИмя файла на компьютере посетителя
mimetypeТип файла, например image/png
sizeРазмер в байтах

Слишком большой файл

Если файл больше лимита, multer передаст в обработчик ошибок ошибку с кодом LIMIT_FILE_SIZE. Её можно показать посетителю понятным текстом:

// src/app.js
import multer from 'multer';
 
app.use((error, req, res, next) => {
  if (error instanceof multer.MulterError) {
    return res.status(400).send('Файл слишком большой: не больше 2 МБ');
  }
  next(error);
});

Этот обработчик ставят перед общим обработчиком ошибок; остальные ошибки он передаёт дальше.

Частая ошибка

Не сохраняйте файл под именем, которое прислал посетитель. Имя ../../src/app.js или совпадающее с чужим файлом может перезаписать важные данные. Генерируйте имя на сервере, как в настройке выше, а оригинальное имя, если нужно, храните отдельно в данных статьи.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.