Formidable
Почнемо із перевіреного ветерана. Установка пакета наступна
npm install formidable
Розглянемо найпростіший приклад для завантаження файлу. Мається на увазі, що ми вже
встановили всі пакети npm i
, які ми використовуємо. Спочатку підключимо ці
пакети
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const fs = require('fs').promises;
const formidable = require('docs/additional_materials/additional-work-with-files/formidable');
const app = express();
Після цього визначимо дві змінні для зберігання шляхів. У першій змінній
uploadDir
ми зберігаємо шлях де зберігається файл при початковому завантаженні,
другий storeImage
шлях де зберігається файл остаточно.
const uploadDir = path.join(process.cwd(), 'uploads');
const storeImage = path.join(process.cwd(), 'images');
Чому ми використовуємо два способи зберігання файлу?
Перша причина, що після завантаження нам знадобиться додаткова обробка файлу. Наприклад, зменшити або навпаки збільшити розмір зображення, зробити його квадратним і т.д. А значить, після обробки ми його перенесемо в нове місце.
Друга причина при завантаженні або обробці файлу може статися не врахована
нами помилка, плюс пакети часто зберігають спочатку файл під якимось тимчасовим
іменем без розширення і лише після остаточного завантаження ми можемо його
змінити. Таким чином на шляху uploadDir
у нас тимчасове сховище, і якщо
там будуть залишатися файли, отже, у нас відбуваються якісь помилки при завантаженні
або обробці та їх необхідно виправити, якщо це можливо.
Плюс підходу ще в тому, що залишкові файли можна буде спокійно видаляти з папки не турбуючись видалити щось потрібне.
Щоб обробити завантаження файлів, нам необхідно створити екземпляр formidable
const form = formidable(options);
І розпарсити дані методом form.parse
form.parse(req, (err, fields, files) => {
// ...
});
Метод викликає callback функцію, в яку передається три параметри, err
помилка якщо вона сталася, fields
це об'єкт який містить звичайні
поля форми та останнім йде параметр files
, який може бути масивом, якщо
дозволено мультизавантаження файлів, або об'єкт якщо файл завантажується один.
Нам хотілося б позбутися callback функції. Напишемо спеціальну обгортку, яка повертатиме проміс із результатами парсингу форми.
const parseFrom = (form, req) => {
return new Promise((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) {
return reject(err);
}
resolve({ fields, files });
});
});
};
Далі опишемо обробник маршруту /upload
для HTTP методу POST
app.post('/upload', async (req, res, next) => {
const form = formidable({ uploadDir, maxFileSize: 2 * 1024 * 1024 });
const { fields, files } = await parseFrom(form, req);
const { path: temporaryName, name } = files.picture;
const { description } = fields;
const fileName = path.join(storeImage, name);
try {
await fs.rename(temporaryName, fileName);
} catch (err) {
await fs.unlink(temporaryName);
return next(err);
}
res.json({ description, message: 'Файл успішно завантажено', status: 200 });
});
У ньому ми створюємо екземпляр formidable
const form = formidable({ uploadDir, maxFileSize: 2 * 1024 * 1024 });
де в опціях вказуємо,
uploadDir
шлях куди треба зберігати файлmaxFileSize
максимальний розмір файлів, що завантажуються 2Мб, що необхідно робити завжди - контролювати розмір файлів, що завантажуються
Далі ми парсимо форму за допомогою нашої функції parseFrom
і отримуємо текстові
поля fields
та об'єкт з файлом files
.
У нашому прикладі ми припускаємо, що ім'я поля з файлом дорівнює picture
. І за
допомогою деструктуризації ми отримуємо шлях де знаходиться файл temporaryName
і
його оригінальне ім'я name
const { path: temporaryName, name } = files.picture;
Потім даємо нове ім'я файлу, у нашому випадку ми залишаємо його тим самим, але хорошою практикою є прив'язувати нове ім'я до дати або рандомного хешу, щоб випадково однакові файли не перезаписали один одного.
const fileName = path.join(storeImage, name);
І переносимо файл у постійне сховище
try {
await fs.rename(temporaryName, fileName);
} catch (err) {
await fs.unlink(temporaryName);
return next(err);
}
Якщо сталася помилка перенесення, не забуваємо видаляти тимчасовий файл.
await fs.unlink(temporaryName);
Якщо все пройшло вдало, в кінці відправляємо відповідь у вигляді JSON
res.json({ description, message: 'Файл успішно завантажено', status: 200 });
Повний код нашої програми наступний
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const fs = require('fs').promises;
const formidable = require('docs/additional_materials/additional-work-with-files/formidable');
const app = express();
const uploadDir = path.join(process.cwd(), 'uploads');
const storeImage = path.join(process.cwd(), 'images');
const parseFrom = (form, req) => {
return new Promise((resolve, reject) => {
form.parse(req, (err, fields, files) => {
if (err) {
return reject(err);
}
resolve({ fields, files });
});
});
};
app.post('/upload', async (req, res, next) => {
const form = formidable({ uploadDir, maxFileSize: 2 * 1024 * 1024 });
const { fields, files } = await parseFrom(form, req);
const { path: temporaryName, name } = files.picture;
const { description } = fields;
const fileName = path.join(storeImage, name);
try {
await fs.rename(temporaryName, fileName);
} catch (err) {
await fs.unlink(temporaryName);
return next(err);
}
res.json({ description, message: 'Файл успішно завантажено', status: 200 });
});
const isAccessible = path => {
return fs
.access(path)
.then(() => true)
.catch(() => false);
};
const createFolderIsNotExist = async folder => {
if (!(await isAccessible(folder))) {
await fs.mkdir(folder);
}
};
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use((err, req, res, next) => {
res.status(err.status || 500);
res.json({ message: err.message, status: err.status });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, async () => {
createFolderIsNotExist(uploadDir);
createFolderIsNotExist(storeImage);
console.log(`Server running. Use on port:${PORT}`);
});
Тут є дві функції, що заслуговують на нашу увагу. Функція isAccessible
повертає логічне вираження залежно від того існує чи ні папка,
а функція createFolderIsNotExist
створює папку, якщо її не існує. І коли
стартує наша програма ми перевіряємо чи існують папки uploadDir
та
storeImage
і якщо їх немає створюємо.
app.listen(PORT, async () => {
createFolderIsNotExist(uploadDir);
createFolderIsNotExist(storeImage);
console.log(`Server running. Use on port:${PORT}`);
});
Давайте тепер спробуємо виконати завантаження файлу за допомогою Postman зімітувати
роботу форми у режимі multipart/form-data
Необхідно вибрати під час надсилання кодування form-data
, як зазначено на малюнку.
І головне завантажити файл для надсилання його на сервер. У цьому режимі ключ
необхідно перевести з текстового типу у файловий
Якщо все зробити правильно при надсиланні повідомлення, файл буде завантажено на сервер
і зберігається в папці images
, а від сервера ми отримаємо відповідь як на малюнку.
Повний код прикладу на Github Gist