Chupurnov Valeriy
Chupurnov Valeriy
Front End Engineer

Как через JS отправить файл на сервер и сохранить его на Node JS + Express

/blog/uploads/images/1630535752971-image-image.png

Предположим у нас есть текстовое поле, и когда пользователь копирует туда изображение из буфера, вы хотим его отправить на сервер, сохранить в папку и показать ссылку на изображение в этом самом поле.

Оказалось задачка не из простых.

Клиентская часть

Тут относительно все просто, однако без факапов не обошлось

const textarea = document.querySelector('textarea');
textarea.addEventListener('paste', (e) => {
   if (e.clipboardData && e.clipboardData.items) {
    const items = e.clipboardData.items;
    
    try {
        for (let i = 0; i < items.length; i++) {
            if (items[i].type.indexOf('image') !== -1) {
                const blob = items[i].getAsFile();
                if (blob) {
                    sendFile(blob).then(resp => {
                        textarea.value = resp.filename;
                    });
                }
            }
        }
    } catch (e) {
        alert(e.message);
    }
    e.preventDefault()
 }
});

Ну и собственно функция sendFile:

async function sendFile(file) {
    const formdata = new FormData();
    formdata.append('image', file, 'image.png');

    const resp = await fetch('media/upload-image', {
        method: 'POST',
        credentials: 'include',
        headers: {
            Accept: 'application/json'
        },
        body: formdata
    }),
    respData = await resp.json();

    if (resp.status !== 200) {
        throw new Error(respData.message);
    }

    return respData;
}

Главное не подавайте в headers лишние заголовки, типа Content-Type. fetch поставит их сам.

Серверная часть

Тут нам на помощь придет либа multer.

npm install --save multer

Полностью настройку эндпоинтов приводить не буду. Это выходит за рамки статьи.

const express = require('express');
const path = require('path');
const multer = require('multer');
const app = express();

// Функция будет фильтровать файлы, чтобы быть уверенными что загружают только фото
const imageFilter = (req, file, cb) => {
    if (file.mimetype.startsWith('image')) {
        cb(null, true);
    } else {
        cb('Please upload only images.', false);
    }
};

// Создаем хранилище. Просто некая папка, в которую потом можно будет стучать из вне.
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, path.resolve(__dirname, '../../uploads/images'));
    },
    filename: (req, file, cb) => {
        cb(null, `${Date.now()}-image-${file.originalname}`);
    }
});

// И storage и fileFilter необязательны
const upload = multer({ storage: storage, fileFilter: imageFilter });

// Тут мы говорим что будем брать только один файл с поля `image`
app.use('/upload-image', upload.single('image'), (req, res) => {
   res.send(req.file); //  Тут все данные по файлу, он уже сохранен на диск
});

Ну или настройки проще

const express = require('express');
const multer = require('multer');
const upload = multer({dest: __dirname + '/uploads/images'});

const app = express();
const PORT = 3000;

app.use(express.static('public'));

app.post('/upload', upload.single('photo'), (req, res) => {
    if(req.file) {
        res.json(req.file);
    }
    else throw 'error';
});

app.listen(PORT, () => {
    console.log('Listening at ' + PORT );
});

Собственно это все. Решил написать статью, так как нормальной информации нигде нет.

Желаю удачи.