画像をmongodbにアップロード


フロントで画像をサーバに保存し、そのパスをデータベースに保存し、サーバに格納されている画像のアドレスをデータベースから取得し、画像ラベルのアドレスに挿入する方法について説明します.

FRONT


App.js

import logo from './logo.svg';
import './App.css';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import FilesUpload from './components/FilesUpload';

function App() {
  return (
    <div className="App">
      <FilesUpload/>
    </div>
  );
}

export default App;

src/components/FilesUpload

import React, { useEffect, useState } from "react";
import axios from "axios";
function FilesUpload() {
  const [file, setFile] = useState();
  const [list, setList] = useState();

  const onFileChange = (e) => {
    setFile({ profileImg: e.target.files[0] });
  };

  const onSubmit = async (e) => {
    e.preventDefault();
    const formData = new FormData();
    formData.append("profileImg", file.profileImg);
    //보통 form 태그에서 데이터를 전송할때는 json 형식을 이용하지만,
    //이미지나 파일등을 업로드 할때에는 formData 객체를 이용한다.
    //append() 메서드를 통해 빈 FormData 객체에 key-value 쌍을 추가해준다.
    console.log(formData);

    axios
      .post("http://localhost:4000/api/user-profile", formData, {})
    // form-data, x-www-form-urlencoded 등의 파일을 보낼 때에는 헤더를 추가해줘야 한다.
    // 위의 형식은 { headers: { 'Content-Type': 'multipart/form-data' } } 이다.
    // 하지만 headers 의 기본 Content-Type 이 multipart/form-data 이므로 생략 가능하다.
      .then((res) => {
        console.log(res);
      });
  };

  useEffect(() => {
    async function fetchData() {
      // You can await here
      const response = await axios.get("http://localhost:4000/api");
      // db 에 저장된 (서버에 저장되어 있는) 이미지의 경로를 가져온다.
      setList(response.data.users);
      // ...
    }
    fetchData();
  }, []);

  return (
    <div className="container">
      <div className="row">
        <form onSubmit={onSubmit}>
          <div className="form-group">
            <input type="file" onChange={onFileChange} />
          </div>
          <div className="form-group">
            <button className="btn btn-primary" type="submit">
              Upload
            </button>
          </div>
        </form>
      </div>
      {list &&
        list.map((image, index) => (
          <img key={index} src={`${image.profileImg}`} />
        ))}
    </div>
  );
}

export default FilesUpload;

BACK


models/User.js

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
    _id: mongoose.Schema.Types.ObjectId,
    profileImg: {
        type: String
    }
}, {
    collection: 'users'
})
module.exports = mongoose.model('User', userSchema)

index.js

let express = require('express'),
    mongoose = require('mongoose'),
    cors = require('cors'),
    bodyParser = require('body-parser');
const api = require('../backend/routes/user.routes')
// MongoDB Configuration
mongoose
  .connect('...')
  .then((x) => {
    console.log(`Connected to Mongo! Database name: "${x.connections[0].name}"`)
  })
  .catch((err) => {
    console.error('Error connecting to mongo', err.reason)
  })
const app = express();
app.use(bodyParser.json());
// req.body 는 body-parser 를 사용하지 전까지 default 값으로 undefined 가 설정되어 있다.
// body-parser 를 이용하여 req.body 데이터를 사용자가 원하는 형태로 parsing 하여 이용할 수 있다
app.use(bodyParser.urlencoded({
    extended: false
}));
// true 를 주면 qs 모듈을 사용하는 것으로, 따로 npm install 을 해야한다.
// 따라서 node.js 에 기본적으로 내장된 query-string 모듈을 사용하려면 false 를 줘야한다.
app.use(cors());
app.use('/public', express.static('public'));
// 정적(static) 파일을 손쉽게 제공하기 위해 사용한다. express.static 을 사용하지 않으면,
// 정적 파일이 존재하는 path 로 접근하기 위한 코드가 번거롭고 복잡하게 된다.
// static 의 인자로 디렉토리명을 전달하며, 해당 디렉토리 경로의 데이터들은 
// 웹브라우저의 요청에 따라 서비스를 제공할 수 있다.
// 해당 디렉토리에 접근할때에도 해당 경로를 static 경로로 지정해야 한다.
app.use('/api', api)
// "/api" 에 접근하는 모든 것들을 user.routes 에게 위임한다
const port = process.env.PORT || 4000;
const server = app.listen(port, () => {
    console.log('Connected to port ' + port)
})
app.use((req, res, next) => {
    // Error goes via `next()` method
    setImmediate(() => {
        next(new Error('Something went wrong'));
    });
});
app.use(function (err, req, res, next) {
    console.error(err.message);
    if (!err.statusCode) err.statusCode = 500;
    res.status(err.statusCode).send(err.message);
});

routes/user.routes.js

let express = require('express'),
    multer = require('multer'),
    mongoose = require('mongoose'),
    uuidv4 = require('uuid/v4'),
    //네트워크 상에서 고유성이 보장되는 id 를 만들기 위한 표준 규약, 버전 4 는 랜덤 생성.
    router = express.Router();

const DIR = './public/';
const storage = multer.diskStorage({
    destination: (req, file, cb) => {
        cb(null, DIR)
    },//file 을 받아와서 DIR 경로에 저장한다.
    filename: (req, file, cb) => {// 저장할 파일의 이름을 설정한다.
        const fileName = file.originalname.toLowerCase().split(' ').join('-');
        cb(null, uuidv4() + '-' + fileName)
      // (uuidv4 O) 7c7c98c7-1d46-4305-ba3c-f2dc305e16b0-통지서
      // (uuidv4 X) 통지서
    }
});

var upload = multer({
    storage: storage,
    fileFilter: (req, file, cb) => {// 말 그대로 fileFilter
        if(file.mimetype == "image/png" 
           || file.mimetype == "image/jpg" 
           || file.mimetype == "image/jpeg"){
            cb(null, true);
        } else {
            cb(null, false);
            return cb(new Error('Only .png .jpg and .jpeg format allowed!'));
        }
    }
});

let User = require('../models/User');
router.post('/user-profile', upload.single('profileImg'), (req, res, next) => {
  // upload.single('profileImg') 에서 profileImg 는 formData 의 key 를 말한다.
  // 따라서 "profileImg" key 의 value 값을 서버의 지정된 폴더에 저장한다.
    const url = req.protocol + '://' + req.get('host')
    // req.protocol => http or https
    // req.get('host') => (현재) localhost:4000
    const user = new User({
        _id: new mongoose.Types.ObjectId(),
        name: req.body.name,
        profileImg: url + '/public/' + req.file.filename 
    });
    
    user.save().then(result => {
        res.status(201).json({
            message: 'User registerd successfuly!',
            userCreated: {
                _id: result._id,
                profileImg: result.profileImg
            }
        })
    }).catch(err => {
        console.log(err);
        res.status(500).json({
            error: err
        })
    })
})

router.get('/', (req, res, next) => {
    User.find().then(data => {
        res.status(200).json({
            message: "User list retrieved successfully!",
            users: data
        })
    })
});

module.exports = router;
テスト結果は以下の通りです.

[注意]
react-file-upload-tutorial
https://www.positronx.io/react-file-upload-tutorial-with-node-express-and-multer/
static file service
https://askforyou.tistory.com/6