Multer: загрузка изображений в Node.js — пошаговое руководство
Есть три основных подхода к работе с загрузкой файлов в Node.js: сохранять изображения прямо на сервере, хранить бинарные или base64-данные в базе, либо использовать хранилище объектов (например, AWS S3) для управления изображениями. В этой статье мы подробно рассмотрим первый подход — хранение файлов на локальном сервере с помощью middleware Multer.
Зачем использовать Multer
Multer — middleware для Express, который упрощает обработку multipart/form-data (форм, содержащих файлы). Он строится поверх парсера busboy и эффективен при потоковой передаче данных из формы на диск или в память.
Ключевые преимущества Multer:
- Простая интеграция с Express.
- Поддержка сохранения на диск или в память.
- Встроенные опции для ограничения размера и фильтрации по типу.
Важно: Multer сам по себе не сканирует файлы на вредоносный код и не обеспечивает шифрование хранения — эти задачи нужно решать отдельно.
Что вы получите в итоге
- Рабочий пример проекта с app.js.
- Настройки storage и валидаторы (size, mime, extension).
- Маршруты для одиночной и множественной загрузки.
- Рекомендации по безопасности, тестированию и развёртыванию.
Шаг 1: Подготовка окружения разработки
Код из этого руководства можно разместить в любом репозитории. Здесь показан минимальный набор команд для создания проекта и установки зависимостей.
Создайте папку проекта и перейдите в неё:
mkdir multer-tutorial
cd multer-tutorialИнициализируйте npm:
npm init -yУстановите зависимости — Express и Multer:
npm install express multerСоздайте файл app.js в корне проекта и добавьте минимальную заготовку сервера:
// app.js
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.listen(port, ()=>{
console.log(`App is listening on port ${port}`);
});Совет: добавьте .gitignore для node_modules и папки с изображениями, если не хотите хранить файлы в репозитории.
Шаг 2: Конфигурация Multer — storage engine
Импортируйте multer и настройте storage engine, который управляет тем, куда сохраняются файлы и как они именуются.
const multer = require("multer");Multer предоставляет метод diskStorage, который принимает объект с параметрами destination и filename.
Добавьте в app.js следующий блок:
// Setting storage engine
const storageEngine = multer.diskStorage({
destination: "./images",
filename: (req, file, cb) => {
cb(null, `${Date.now()}--${file.originalname}`);
},
});
// initializing multer
const upload = multer({
storage: storageEngine,
});Пояснения:
- destination — путь в файловой системе относительно рабочей директории процесса Node.js; в примере — ./images.
- filename — функция, которая должна вызвать cb(error, filename). В примере используется метка времени Date.now() для получения уникального имени.
Рекомендуется заранее создать папку images и установить корректные права доступа:
mkdir images
chmod 755 imagesМожно программно создавать папку при старте приложения, если её нет:
const fs = require('fs');
const imagesDir = './images';
if (!fs.existsSync(imagesDir)) {
fs.mkdirSync(imagesDir, { recursive: true });
}Шаг 3: Правила валидации изображений
Важно ограничить типы и размеры загружаемых файлов, чтобы избежать переполнения диска и загрузки неподходящих файлов.
Пример добавления лимита размера (в байтах) и фильтра файлов:
const path = require("path");
const checkFileType = function (file, cb) {
// Разрешённые расширения
const fileTypes = /jpeg|jpg|png|gif|svg/;
// Проверка расширения файла
const extName = fileTypes.test(path.extname(file.originalname).toLowerCase());
// Проверка MIME-типа
const mimeType = fileTypes.test(file.mimetype);
if (mimeType && extName) {
return cb(null, true);
} else {
cb(new Error("Error: You can Only Upload Images!!"));
}
};
const upload = multer({
storage: storageEngine,
limits: { fileSize: 10000000 }, // 10 MB
fileFilter: (req, file, cb) => {
checkFileType(file, cb);
},
});Примечание: в примере выше limit установлен в 10 000 000 байт (примерно 10 МБ). Подбирайте лимит в зависимости от требований приложения.
Важно: использование только проверки расширения недостаточно — MIME-тип и, при необходимости, дополнительная проверка содержимого (проверка сигнатур файлов) помогут уменьшить риск загрузки неподходящих файлов.
Шаг 4: Multer как middleware в маршрутах Express
Multer предоставляет несколько методов для обработки файлов:
- upload.single(fieldname) — одиночный файл
- upload.array(fieldname, maxCount) — массив файлов с одинаковым именем поля
- upload.fields([{ name: ‘avatar’, maxCount: 1 }, { name: ‘gallery’, maxCount: 8 }]) — разные поля
- upload.any() — принимает файлы из любых полей (не рекомендуется без валидации)
Примеры маршрутов:
app.post("/single", upload.single("image"), (req, res) => {
if (req.file) {
res.send("Single file uploaded successfully");
} else {
res.status(400).send("Please upload a valid image");
}
});
app.post("/multiple", upload.array("images", 5), (req, res) => {
if (req.files) {
res.send("Multiple files uploaded successfully");
} else {
res.status(400).send("Please upload valid images");
}
});Пример HTML-формы для тестирования (одиночная загрузка):
И для множественной загрузки:
Обработка ошибок и ответы клиенту
Multer генерирует ошибки при превышении лимита размера (LIMIT_FILE_SIZE) и при фильтрации файлов. Рекомендуется централизованно обрабатывать ошибки middleware:
app.post('/single', (req, res, next) => {
upload.single('image')(req, res, function (err) {
if (err) {
// Обработка ошибок Multer
return res.status(400).send({ error: err.message });
}
if (!req.file) return res.status(400).send({ error: 'No file uploaded' });
res.send({ message: 'Uploaded', file: req.file.filename });
});
});Также можно добавить глобальный обработчик ошибок Express для отлова неявных ошибок.
Как отдавать загруженные файлы клиентам
Если вы хотите позволить клиентам просматривать загруженные изображения, можно использовать express.static:
app.use('/images', express.static('images'));После этого файл, сохранённый как ./images/1663080276614–example.jpg, будет доступен по URL http://
Важно: отключите индексирование директорий и убедитесь, что доступ предоставлен только к ожидаемым ресурсам.
Практические варианты использования и альтернативы
Когда хранить файлы на локальном диске подходит:
- Малые проекты или внутренняя система с одной нодой.
- Когда требования к быстрому доступу простые и у вас есть резервное копирование.
Когда лучше использовать облачное хранилище (S3, GCS и т. п.):
- Масштабирование на несколько экземпляров приложений.
- Требуется CDN, управление версионированием, долговременное хранение.
Когда хранить в базе данных — обычно не рекомендуется для больших бинарных файлов. Подходит для очень маленьких файлов и специфичных случаев, когда важна транзакционная согласованность между записями метаданных и бинарными данными.
Краткое сравнение (качественно):
- Локальный диск: просто, дешево, но ограничено в масштабировании и надёжности.
- S3/облако: масштабируется, интегрируется с CDN, требует дополнительных затрат и конфигурации.
- База данных: медленнее и тяжело масштабируется, но может упростить некоторые транзакции.
Безопасность и харднинг
Рекомендации при работе с загрузками:
- Ограничьте типы файлов и размер.
- Генерируйте безопасные имена файлов (не используйте оригинальные имена без проверки).
- Сканируйте загружаемые файлы антивирусом в критичных приложениях.
- Ограничьте права на папку (например, 750/755 по необходимости).
- Не храните исполняемые файлы в публичной директории.
- Используйте HTTPS для передачи файлов.
- Разграничивайте доступ к файлам через авторизацию и подписи URL, если это нужно.
Конфиденциальность и соответствие (GDPR и прочее)
Если загружаются файлы пользователей, учтите:
- Храните минимально необходимые метаданные и проработайте срок хранения контента.
- Реализуйте функционал удаления данных по запросу пользователя, если это требуется.
- Документируйте причину хранения и срок хранения (retention policy).
- Шифруйте резервные копии, содержащие пользовательские файлы.
Рольные чек-листы
Разделим ответственность между командами:
Разработчик:
- Настроил Multer и валидацию типов/размера.
- Обработал ошибки и вернул ясные сообщения клиенту.
- Написал unit/integration тесты для загрузки/валидации.
DevOps/Инфраструктура:
- Настроил резервное копирование папки images или указал облачное хранилище.
- Настроил права доступа и мониторинг дискового пространства.
Безопасность:
- Проверил и протестировал ограничения по типу и размеру.
- Добавил проверку сигнатур файлов и/или интеграцию с антивирусом при необходимости.
QA:
- Проверил позитивные и негативные сценарии (см. тесты ниже).
- Убедился, что ошибки возвращаются корректно и содержат понятные сообщения.
Критерии приёмки и тестовые сценарии
Критерии приёмки (acceptance):
- Можно загрузить одно изображение и получить успешный ответ.
- Можно загрузить несколько изображений (до указанного максимума).
- При попытке загрузить файл неподдерживаемого формата возвращается ошибка с кодом 400.
- При загрузке файла, превышающего лимит, возвращается ошибка LIMIT_FILE_SIZE.
- Загруженные файлы доступны по статическому маршруту (/images/… ) с корректными правами доступа.
Тестовые сценарии:
- Позитивный: загрузка JPEG 200 KB — статус 200, файл сохранён.
- Позитивный: множественная загрузка 3 PNG — статус 200, 3 файла сохранены.
- Негативный: загрузка .exe — статус 400, сообщение о неверном формате.
- Негативный: загрузка файла 50 MB (если лимит 10 MB) — ошибка LIMIT_FILE_SIZE.
- Интеграционный: доступ по URL к сохранённому файлу — возвращён корректный content-type и статус 200.
Сниппет: коллекция команд и методов Multer (cheat sheet)
- upload.single(‘avatar’) — один файл из поля avatar.
- upload.array(‘photos’, 5) — до 5 файлов из поля photos.
- upload.fields([{ name: ‘avatar’, maxCount: 1 }, { name: ‘gallery’, maxCount: 8 }]) — разные поля одновременно.
- upload.any() — принимает файлы из любых полей (не рекомендуется без валидации).
Проверка ошибок Multer в middleware:
app.use((err, req, res, next) => {
if (err instanceof multer.MulterError) {
// Ошибки Multer (например, LIMIT_FILE_SIZE)
return res.status(400).json({ error: err.code });
} else if (err) {
return res.status(500).json({ error: err.message });
}
next();
});Полный пример app.js (собранный)
// app.js
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const port = process.env.PORT || 3000;
// Создаём папку images, если её нет
const imagesDir = './images';
if (!fs.existsSync(imagesDir)) {
fs.mkdirSync(imagesDir, { recursive: true });
}
// Storage engine
const storageEngine = multer.diskStorage({
destination: imagesDir,
filename: (req, file, cb) => {
// Можно дополнительно санитизировать file.originalname
const safeName = file.originalname.replace(/[^a-zA-Z0-9.\-\_]/g, '_');
cb(null, `${Date.now()}--${safeName}`);
},
});
// check file type
const checkFileType = function (file, cb) {
const fileTypes = /jpeg|jpg|png|gif|svg/;
const extName = fileTypes.test(path.extname(file.originalname).toLowerCase());
const mimeType = fileTypes.test(file.mimetype);
if (mimeType && extName) {
return cb(null, true);
} else {
cb(new Error('Only image files are allowed'));
}
};
const upload = multer({
storage: storageEngine,
limits: { fileSize: 10 * 1024 * 1024 }, // 10 MB
fileFilter: (req, file, cb) => {
checkFileType(file, cb);
},
});
// Статическая раздача изображений
app.use('/images', express.static(imagesDir));
// Маршрут одиночной загрузки
app.post('/single', (req, res) => {
upload.single('image')(req, res, function (err) {
if (err) {
if (err instanceof multer.MulterError) {
return res.status(400).json({ error: err.code });
}
return res.status(400).json({ error: err.message });
}
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
res.json({ message: 'Uploaded', filename: req.file.filename });
});
});
// Маршрут множественной загрузки
app.post('/multiple', (req, res) => {
upload.array('images', 5)(req, res, function (err) {
if (err) {
if (err instanceof multer.MulterError) {
return res.status(400).json({ error: err.code });
}
return res.status(400).json({ error: err.message });
}
if (!req.files || req.files.length === 0) return res.status(400).json({ error: 'No files uploaded' });
res.json({ message: 'Uploaded', files: req.files.map(f => f.filename) });
});
});
// Глобальный обработчик ошибок
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
});
app.listen(port, () => {
console.log(`App is listening on port ${port}`);
});Когда Multer может не подойти
- Если приложение распределено на множество инстансов без общего хранилища — локальное сохранение неудобно.
- Если требуется высокодоступное, геораспределённое хранение и CDN — лучше S3/Cloud Storage.
- Если нужно атомарно хранить бинарные данные вместе с транзакциями БД — может подойти хранение в базе, но это редко оптимально.
Ментальные модели и эвристики
- Правило простоты: для небольших приложений сначала начните с Multer + локальное хранение; при росте архитектуры мигрируйте на облако.
- Принцип минимальных прав: папка с файлами должна иметь минимально необходимые права и не должна содержать исполняемых файлов.
- Отделяйте метаданные (в БД) и файлы (на диске/в S3). В БД храните путь/URL, тип, размер, uploaderId и срок хранения.
План миграции с локального хранилища на S3 (высокоуровнево)
- Добавьте хранение в S3 при загрузке: вместо diskStorage используйте потоковую отправку в S3.
- Продублируйте логику хранения — записывайте URL в вашу БД, пока не переключитесь полностью.
- Перенесите существующие файлы в S3 (bulk upload) и обновите записи в БД.
- После проверки отключите локальную раздачу и удалите локальные файлы.
Факто-бокс — ключевые настройки (для справки)
- fileSize: задаётся в байтах; 1 MB = 1 048 576 байт (приблизительно), в примерах часто используют 1 000 000 или 10 000 000 байт.
- maxCount для upload.array: ограничивает количество файлов в одной форме.
- Поля multer: storage, limits, fileFilter.
Короткое объявление для команды (100–200 слов)
В проект добавлен обработчик загрузки изображений на основе Multer. Он поддерживает одиночную и множественную загрузку, проверку типа (jpeg, jpg, png, gif, svg) и ограничение размера файлов (по умолчанию 10 МБ). Изображения сохраняются в ./images с безопасными именами, доступными по маршруту /images/
В конце: приёмка, тесты и план миграции помогут команде быстро внедрить и при необходимости масштабировать решение. Если нужно — могу дополнительно подготовить пример интеграции с AWS S3 или сценарии CI/CD для автоматической очистки старых изображений.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone