Как реализовать систему загрузки изображений с помощью Express и React

В этой статье мы узнаем, как загружать изображения на сервер с помощью multer и сохранять их в облачном сервисе, таком как cloudinary. Если вы просто хотите сохранить изображение на самом сервере, в этом посте оно тоже будет. Мы будем использовать React в качестве интерфейса для загрузки наших изображений и отображения нашего изображения в карусели.

Как видите, наше окончательное приложение будет выглядеть так:

Вам необходимо иметь базовое представление о Node.js, Express.js, mongoose, React.js, React Bootstrap, чтобы следить за этим постом.

Введение в Multer

Multer - это промежуточное программное обеспечение, которое помогает нам загружать файлы на сервер. Промежуточное ПО - это функция, которая помогает нам выполнять определенные задачи.

А теперь приступим.

В рабочем каталоге напишите следующую команду:

npm int -y

Флаг -y здесь помогает установить все значения по умолчанию.

Теперь давайте установим несколько пакетов.

npm i cloudinary dotenv express mongoose multer multer-storage-cloudinary

Мы установили довольно много пакетов. Я думаю, вы понимаете варианты использования большинства пакетов и по ходу узнаете о вариантах использования других пакетов.

Теперь создайте файл с именем index.js внутри основного каталога проекта. Это должно сделать нашу структуру папок такой:

Теперь напишите следующие строки кода в файле index.js:

require("dotenv").config();
const express = require("express");
const mongoose = require("mongoose");
const appRoute = require("./routes/appRoute");
const app = express();
const PORT = process.env.PORT || 5000;
app.use("/api", appRoute);
mongoose
.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("connected to mongo database"))
.catch((error) => console.errror(error));
app.listen(PORT, () => console.log(`listening on port : ${PORT}`));

В приведенном выше коде мы импортировали и инициализировали dotenv. Затем мы импортировали express, mongoose, appRoute. Не беспокойтесь о appRoute. Мы скоро над этим поработаем. После этого мы инициализировали наш app. Затем мы создали конечную точку /api/, которая использует appRoute в качестве промежуточного программного обеспечения. Затем мы подключились к нашей базе данных mongoDB. После этого мы запустили наш сервер на PORT. Понятно, правда?

Вы можете видеть в коде, который мы указали mongoose для подключения к URL-адресу базы данных через process.env.MONGO_URI. Но мы не сказали, каким должен быть URI. Для этого создайте файл .env в нашем основном каталоге проекта. Затем откройте этот файл и напишите следующее:

MONGO_URI=      //enter the mongo URL here

Вам нужно ввести MONGO_URI там, как указано выше.

Теперь, когда вы ввели MONGO_URI, вам нужно создать папку с именем routes в вашем основном рабочем каталоге. После этого создайте файл с именем appRoute.js внутри каталога маршрутов. В результате структура папок должна выглядеть так:

Теперь откройте файл appRoute.js и напишите следующие строки кода:

const express = require("express");
const upload = require("../services/upload");
const { uploadImage, getImages } = require("../controller/appController");
const router = express.Router();

// /api/images
router.get("/images", getImages);
// /api/upload
router.post("/upload", upload.single("picture"), uploadImage);
module.exports = router;

Как видите, в файле appRoute.js есть импорт. Мы импортировали express для создания роутера. Затем мы импортировали upload, промежуточное ПО, которое мы вскоре создадим и которое поможет нам загружать файлы. Затем вы можете видеть, что мы импортировали getImages и uploadImage. Это контроллеры, которые мы создадим в ближайшее время. После этого мы создали две конечные точки /api/images и /api/upload. В этих конечных точках мы поставили контроллеры. Обратите внимание, что в конечной точке /api/upload мы использовали upload промежуточное ПО. Не беспокойтесь о создании upload промежуточного программного обеспечения. Но имейте в виду, что upload предложит нам функцию загрузки файлов. upload предоставляет нам .single параметр, который помогает нам загрузить один файл. Параметр single принимает в качестве аргумента имя поля формы. В нашем случае это "picture".

Что, если бы мы хотели загрузить несколько файлов? В этом случае нам нужно использовать .array вместо .single. array принимает два аргумента. Первый аргумент - это имя поля, а второй аргумент - максимальное количество файлов, которые могут быть загружены. Посмотрим на код: upload.array("picture", 12). Здесь "picture" - это имя поля формы, а 12 - максимальное количество изображений, которые можно загрузить. Если вы хотите погрузиться глубже, вы можете ознакомиться с документацией мультера.

Теперь, когда мы написали код в appRoute.js, давайте создадим папку с именем services. Затем создайте файл с именем upload.js внутри папки служб. Наша структура папок должна выглядеть так:

Теперь, когда мы создали файл upload.js, давайте напишем код.

Прежде чем перейти к этой части, давайте создадим учетную запись Cloudinary. После создания учетной записи проверьте свою панель управления. Вы увидите примерно такой раздел:

А теперь стой там. Нам нужно скопировать некоторые значения в наш файл .env. Давайте откроем файл .env и напишем следующее:

MONGO_URI=          // Mongodb URL
CLOUD_NAME=         // Clodudinary cloud name
API_KEY=            // Cloudinary API Key
API_SECRET=         // Cloudinary API Secret

В файле нам просто нужно ввести их соответствующие значения, как указано в комментарии выше.

Теперь перейдем к файлу upload.js. Затем напишите следующие строки кода:

const multer = require("multer");
const cloudinary = require("cloudinary").v2;
const { CloudinaryStorage } = require("multer-storage-cloudinary");

cloudinary.config({
  cloud_name: process.env.CLOUD_NAME,
  api_key: process.env.API_KEY,
  api_secret: process.env.API_SECRET,
});

const storage = new CloudinaryStorage({
  cloudinary: cloudinary,
  folder: "app",
  allowedFormats: ["jpg", "png", "jpeg"],
  transformation: [{ width: 500, height: 500, crop: "limit" }],
});
const upload = multer({ storage: storage });
module.exports = upload;

Теперь поговорим о приведенных выше строках кода.

Мы импортировали multer, cloudinary, CloudinaryStorage из multer, cloudinary и multer-storage-cloudinary соответственно. Это довольно ясно, правда?

После этого мы использовали cloudinary.config для настройки нашей учетной записи Cloudinary. cloudinary.config принимает cloud_name, api_key и api_secret. Это довольно ясно из самого названия, не так ли?

А теперь давайте посмотрим на CloudinaryStorage. Прежде чем говорить о задействованном коде, давайте поговорим о том, что делает CloudinaryStorage. Это помогает нам создать вариант хранения, который требуется Multer для загрузки файла в определенное место назначения.

Теперь давайте поговорим о коде, задействованном в CloudinaryStorage. Требуется опция cloudinary. Он также принимает параметр folder, который является именем папки в нашем облаке Cloudinary. Вы можете называть его как хотите. После этого вы можете увидеть allowedFormat, который принимает массив допустимых типов форматов файлов. В нашем случае это PNG и jpeg / jpg. Тогда вы можете увидеть transformation. Это помогает нам преобразовать файлы. Это функция, предлагаемая Cloudinary. Если вы хотите узнать об этом, вы можете проверить документацию Cloudinary.

Теперь поговорим о multer(). Вы можете видеть, что multer принимает объект. Этот объект занимает storage. Помните, этот storage был создан из CloudinaryStorage.

После этого мы экспортировали промежуточное ПО upload.

А теперь давайте отложим на некоторое время и посмотрим, что бы мы сделали иначе, если бы мы загрузили наш файл на наш собственный сервер, а не в облако. Если вы уже знаете о загрузке и сохранении файлов на сервер, вы можете пропустить этот раздел.

Теперь взгляните на код ниже:

const multer = require("multer");
const path = require("path");
let storage = multer.diskStorage({
destination: (req, file, cb) => {
  cb(null, "uploads");
},
  filename: (req, file, cb) => {
  cb(null,
  `${file.fieldname}-${Date.now()}-${Math.random() * 1000}${path.extname(file.originalname)}`
 );
 },
  });
const fileFilter = (req, file, cb) => {
  if (
  file.mimetype === "image/png" ||
  file.mimetype === "image/jpg" ||
  file.mimetype === "image/jpeg"
  ) {
  cb(null, true);
  } else {
  cb(new Error("File format should be PNG,JPG,JPEG"), false);
  }
};
const upload = multer({ storage: storage, fileFilter: fileFilter });
module.exports = upload;

Мы не будем использовать этот код для работы над этим проектом. Но вы определенно можете попробовать это в своем следующем проекте. Вы также можете попробовать этот код для загрузки файлов в этот проект. Но вам необходимо внести некоторые изменения в контроллер, над которыми мы скоро будем работать. Я не буду здесь об этом говорить. Вы можете легко понять это после работы с контроллерами для загрузки файлов в Cloudinary.

Теперь давайте подробнее рассмотрим приведенный выше код. Мы импортировали multer и path. Здесь path предлагается компанией Node.

В коде вы можете увидеть multer.diskStorage, который принимает параметры destination и file. Эта destination опция помогает нам указать Multer, где должны храниться наши файлы. Помните, что destination имеет три аргумента: req, file и cb. cb принимает два аргумента. Второй аргумент принимает имя папки, в которой должен храниться загруженный файл. В нашем случае это папка «загрузки». Если мы не укажем destination, файлы будут загружены во временную папку нашей операционной системы. Здесь также следует отметить одну вещь: вы должны создать папку загрузки в основном каталоге проекта.

Теперь давайте посмотрим на параметр filename. Как следует из названия, filename помогает нам изменить имена файлов. Если бы мы не использовали параметр filename, Multer присвоил бы файлу случайное имя.

Теперь поговорим о коде. Функция filename принимает значения req, file и cb - аналогично destination. Затем вы можете увидеть, что мы реализовали cb, который принимает два аргумента. Первый - null, а второй - имя файла. Здесь вы можете видеть, что мы использовали обратные кавычки (`). Вы, очевидно, знаете об использовании `. Вы также можете видеть, что мы создали шаблон для имени файла: <file's fieldname>-<current date>-<random number>.<file extension>. Чтобы добиться этого шаблона, мы добавили имя поля с помощью file.fieldname, где file - аргумент. Затем мы добавили текущую дату с помощью Date.now(). После этого мы добавили случайное число на Math.random()*1000. После этого мы добавили расширение файла с помощью path.extname. extname принимает имя файла и возвращает расширение.

После этого мы создали файл fileFilter. Функция fileFilter - фильтровать типы файлов, которые могут быть загружены. В функции fileFilter мы реализовали mimetype проверку. Если он соответствует mimetype, мы реализовали обратный вызов без возврата ошибок следующим образом: cb(null,true). Если mimetype не соответствует, мы выдаем новую ошибку с помощью cb.

После этого мы добавили опции storage и fileFilter в multer. Этот процесс прост, правда?

Благодаря этому вы сможете загружать файлы на сервер. Теперь, когда вы узнали, как загружать файлы на сервер, давайте вернемся к нашей реальной задаче.

Теперь давайте создадим каталог с именем controller в нашем основном каталоге проекта. Затем создайте файл с именем appController.js внутри каталога контроллера. Это должно сделать нашу структуру папок такой:

Теперь в файле appController.js напишите следующие строки кода:

const Image = require("../models/Image");
const getImages = async (req, res) => {
  try {
    let images = await Image.find({}, " -__v");
    return res.status(200).json({ images, msg: "image info fetched"    });
  } catch (error) {
    console.error(error);
      return res.status(500).json({ error: "some error occured" });
    }
    };
const uploadImage = async (req, res) => {
  try {
    if (req.file && req.file.path) {
      const image = new Image({
  description: req.body.desc,
      url: req.file.path,
});
await image.save();
return res.status(200).json({ msg: "image successfully saved" });
} else {
  console.log(req.file);
  return res.status(422).json({ error: "invalid" });
    }
    } catch (error) {
  console.error(error);
 return res.status(500).json({ error: "some error occured" });
  }
};
module.exports = {
  getImages,
  uploadImage,
};

Вы можете видеть, что в приведенном выше коде мы импортировали модель Image. Не беспокойся об этом. Мы будем создавать это в ближайшее время.

Теперь давайте посмотрим на функцию getImages. Эта функция вызывается, когда пользователь делает запрос на получение маршрута /api/images; вы, очевидно, знаете об этом. В этой функции мы отправляем нашему пользователю массив изображений. Если произойдет какая-либо ошибка сервера, мы отправим нашему пользователю код состояния 500. Я не думаю, что для этого нужны какие-либо дополнительные объяснения, кроме этого.

Теперь давайте посмотрим на функцию uploadImage. Вы, очевидно, знаете, что эта функция вызывается, когда пользователь отправляет почтовый запрос на маршрут /api/upload. В коде мы проверили, существуют ли req.file и req.file.path. Если они существуют, мы написали код для создания нового экземпляра модели Image и затем ответили пользователю кодом состояния 200 и сообщением. Если их нет, мы реализовали код, чтобы отправить пользователю код состояния 422 и сообщение об ошибке. Если во время процесса возникнет какая-либо ошибка, мы ответим пользователю кодом состояния 500 и сообщением об ошибке. Все просто, правда?

После этого мы экспортировали uploadImage и getImages функцию.

Теперь, когда мы завершили реализацию нашего контроллера, давайте создадим папку с именем models в нашем рабочем каталоге. Затем создайте файл с именем Image.js. После этого напишите следующие строки кода:

const mongoose = require("mongoose");
const { Schema } = mongoose;
const Image = new Schema({
url: { type: String },
description: { type: String },
});
module.exports = mongoose.model("image", Image);

Думаю, здесь все довольно ясно. У нас есть модель изображения с url и description, оба имеют String тип данных.

На этом мы, наконец, завершили бэкэнд проекта. Теперь займемся фронтендом нашего проекта.

Давайте откроем командную строку в нашем основном каталоге проекта и напишем следующую команду:

npx create-react-app views

После завершения установки наша файловая структура должна выглядеть так:

Теперь давайте установим нужные нам пакеты в наш фронтенд-раздел. Во-первых, давайте изменим рабочий каталог на представления в нашем терминале. Для этого нам нужно написать следующие строки команды:

cd views

Теперь давайте установим нужные нам пакеты. Для этого нам нужно написать следующие строки команды:

npm i axios bootstrap react-bootstrap

Нам нужны axios для выполнения запросов, bootstrap и react-bootstrap для стилизации.

Теперь нам нужно добавить прокси в наш раздел внешнего интерфейса. Для этого откройте файл package.json из каталога представлений. Затем в конце файла добавьте эту строку:

"proxy": "http://localhost:5000/"

Мы делаем это, чтобы предотвратить ошибки CORS при выполнении запросов к бэкэнду.

Теперь откройте файл App.js в каталоге views / src. Затем напишите следующие строки кода внутри файла:

import React from "react";
import "bootstrap/dist/css/bootstrap.css";
import Upload from "./component/Upload";
import ImageCarousel from "./component/ImageCarousel";
function App() {
  return (
    <div className="container">
    <ImageCarousel />
    <Upload />
    </div>
    );
}
export default App;

В файл мы импортировали React. Мы также импортировали файл CSS из начальной загрузки. Мы также импортировали два компонента Upload и ImageCarousel. Затем мы визуализировали компонент.

Теперь перейдем к работе над этими компонентами. Для этого давайте создадим каталог под названием component внутри каталога views / src. Теперь создайте два файла с именами ImageCarousel.js и Upload.js.

Давайте откроем файл Upload.js и напишем следующие строки кода:

Здесь мы создали кнопку и модальное окно. Я не буду объяснять создание модального окна в этой статье. Если вы хотите узнать об этом, вы можете ознакомиться с этой частью документации react-bootstrap. Вы можете видеть, что у нас есть форма внутри модального окна. Он содержит два поля: описание и поле файла. Мы написали код для изменения состояния при изменении поля формы.

Вы можете видеть в поле изображения, которое мы вызвали функцию fileData() в строке 80. Эта функция возвращает имя изображения. Теперь давайте взглянем на функцию onSubmit в строке 15. В этой функции мы создали новый экземпляр FormData. FromData возвращает нас с Content-Type: multipart/form-data. Если вы хотите узнать больше о FormData, перейдите по этой ссылке на javascript.info. Затем мы добавили значение description и файл изображения к formData. После этого мы написали код для реализации POST-запроса на URL/api/upload.

Теперь, когда мы реализовали модель загрузки, поработаем над каруселью изображений. В файле ImageCarousel.js напишите следующие строки кода:

Мы импортировали Carousel из реактивного загрузчика. Я не буду говорить о Carousel в этой статье. Если вы хотите узнать о нем больше, вы можете просто следовать этой части документации. Поговорим о getImages функции. В этой функции мы написали код для получения запроса на URL /api/images. Эта функция вызывается при монтировании компонента. После этого мы добавили изображение внутри Carousel.Item и описание изображения внутри Carousel.Caption.

На этом мы завершили разработку проекта. Теперь вы можете опробовать этот проект.