Загрузка файлов: Multer
Multer — middleware для Express, обрабатывающий multipart/form-data запросы, что необходимо для загрузки файлов через HTTP-формы или API.
Зачем нужно
Express не умеет парсить multipart/form-data из коробки. express.json и express.urlencoded обрабатывают только JSON и URL-encoded данные. Multer добавляет поддержку загрузки файлов: принимает их в память (memoryStorage) или сохраняет на диск (diskStorage), валидирует тип и размер.
Где используется
- Загрузка аватаров и изображений профиля
- Загрузка документов (PDF, Word) в CRM/ERP
- Импорт данных через CSV/Excel
- Загрузка медиа (видео, аудио) в медиасервис
Основной контент
Установка
npm install multer
diskStorage — сохранение на диск
const express = require('express');
const multer = require('multer');
const path = require('path');
const crypto = require('crypto');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, 'uploads')); // папка должна существовать
},
filename: (req, file, cb) => {
// Уникальное имя файла: hash + оригинальное расширение
const uniqueSuffix = crypto.randomBytes(8).toString('hex');
const ext = path.extname(file.originalname);
cb(null, `${uniqueSuffix}${ext}`);
}
});
const fileFilter = (req, file, cb) => {
const allowed = ['image/jpeg', 'image/png', 'image/webp'];
if (allowed.includes(file.mimetype)) {
cb(null, true); // принять файл
} else {
cb(new Error('Only JPEG, PNG and WebP images are allowed'), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: {
fileSize: 5 * 1024 * 1024, // 5 MB
files: 1
}
});
Маршруты загрузки
const router = express.Router;
// Один файл — поле 'avatar'
router.post('/avatar',
upload.single('avatar'),
(req, res) => {
// req.file: { fieldname, originalname, encoding, mimetype, filename, path, size }
if (!req.file) return res.status(400).json({ error: 'File required' });
res.json({
filename: req.file.filename,
url: `/uploads/${req.file.filename}`,
size: req.file.size
});
}
);
// Несколько файлов — поле 'photos', максимум 5
router.post('/gallery',
upload.array('photos', 5),
(req, res) => {
// req.files: массив объектов
const urls = req.files.map(f => `/uploads/${f.filename}`);
res.json({ urls });
}
);
// Несколько полей с разными именами
const cpUpload = upload.fields([
{ name: 'avatar', maxCount: 1 },
{ name: 'documents', maxCount: 5 }
]);
router.post('/profile', cpUpload, (req, res) => {
const avatar = req.files['avatar']?.[0];
const docs = req.files['documents'] || ;
res.json({ avatar: avatar?.filename, docs: docs.map(d => d.filename) });
});
memoryStorage — файл в Buffer
// Для загрузки в S3, CloudStorage без сохранения на диск
const uploadToMemory = multer({
storage: multer.memoryStorage,
limits: { fileSize: 10 * 1024 * 1024 }
});
router.post('/upload-s3',
uploadToMemory.single('file'),
async (req, res, next) => {
try {
// req.file.buffer — Buffer с содержимым файла
const url = await S3Service.upload(req.file.buffer, req.file.originalname, req.file.mimetype);
res.json({ url });
} catch (err) { next(err); }
}
);
Обработка ошибок Multer
// Multer бросает MulterError при превышении лимитов
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
if (err.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({ error: 'File too large. Max 5MB.' });
}
if (err.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({ error: 'Too many files.' });
}
return res.status(400).json({ error: err.message });
}
next(err);
});
Частые ошибки
- Хранить оригинальное имя файла — пользователи могут загрузить
../../config.js; всегда генерировать уникальное имя черезcrypto.randomBytes - Не валидировать MIME-тип — проверять
file.mimetype, а не расширение (расширение можно подделать) - Не ограничивать размер — без
limits.fileSizeможно загрузить произвольно большой файл и заполнить диск - Раздавать uploads без ограничений — использовать виртуальный путь
/filesвместо/uploads, чтобы скрыть структуру директорий
Связанные темы
- _MOC Node.js
- Express -- Раздача статики
- Статические файлы
- Node.js -- безопасность (Helmet, rate-limit)