Как реализовать систему загрузки изображений с помощью 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
.
На этом мы завершили разработку проекта. Теперь вы можете опробовать этот проект.