7

I have uploaded the file in my backend filesystem using multer

My server is node and client is react.

I'm having trouble downloading and displaying the saved file on the client react

Whenever I do res.download(file) it just throws an error as connection refused on client side.

My code is as follows:

UserToUploadMapping.js

const mongoose = require("mongoose");

const UserToUploadMapping = new mongoose.Schema({
  userId: {
      type:String,
      required:true
  },
  file: {
    type: Object,
    required: true,
  },
  date: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model("UserToUploadMapping", UserToUploadMapping);

uploadVideo.js

const router = require("express").Router();
const multer = require('multer');
const UserToUploadMapping = require('../models/UserToUploadMapping')

let nameFile = ''
const storage = multer.diskStorage({
    destination:'./Videos',
    filename:(req,file,cb) => {
        console.log(file)
        nameFile = file.originalname + " "+ Date.now()
        cb(null, nameFile)
    }
})

const upload = multer({storage:storage})

router.post('/upload', upload.single('video'), async (req,res,next) => {
    console.log("object")
    const saveMapping = new UserToUploadMapping({
        userId:'123',
        file:req.file,
    })

    await saveMapping.save()

    res.send("Video uploaded")
})

router.get('/download', async(req,res,next) => {
    const x = await UserToUploadMapping.find()
    // res.send(x)
    res.download(x[0].path)
})

module.exports = router;

CLIENT

const fetchVideo = async () => {
  
    const resp = await axios.get(
      "http://localhost:5000/api/user/video/download"
    );
    console.log(resp)
  };

  return (
    <>
      <NavContainer />
      <div className={classes.Post}>
        <Input
          type="file"
          onChange={(e) => uploadVideos(e.target.files)}
          accept="video/mp4"
        />
        {/* <Button onClick={(e) => submitHandler(e)}>Upload</Button> */}
        <video></video>
      </div>
    </>
  );

Error

enter image description here

Phil
  • 435
  • 2
  • 9
  • 28
  • 1
    Please, add a error message to the question. – quentino May 23 '21 at 14:58
  • 1
    ERR:Connection refused . That's all it says – Phil May 23 '21 at 17:32
  • 1
    It looks like no server on http://localhost:5000. How do you run express server? – quentino May 23 '21 at 18:02
  • 1
    @quentino the express server is working fine. Only the download route is failing .. probably because i am missing setting some configuration in axios ? – Phil May 23 '21 at 18:42
  • 1
    can u add server logs as well if u have any and also check if u are able to find the 'x' in the function successfully – Venkatesh A May 25 '21 at 17:06
  • 1
    What's the approximate file size you are trying to download? Can you try with a few bytes TXT file to see if it still gives you an error? I was having the same issue as you with large files, if it's this, I suggest you to switch from axios to StreamSaver.js. – Stan Loona May 25 '21 at 19:19
  • 1
    It seems the endpoint `http://localhost:5000/api/user/video/download` doesn't exist. How are you mounting the `router` to app in Express? – Maxim Orlov May 26 '21 at 16:33

5 Answers5

4

There is a few problems within the uploadVideo.js file :

  • to get the path from the data, you need to use x[0].file.path

(based on how you save the file in the database)

const saveMapping = new UserToUploadMapping({
        userId:'123',
        file:req.file,
    })
  • to avoid problems about where the file uploadVideo.js is and where we run the application, you should use an absolute path when saving files in the system.
  • (small problem) your filename function will give filenames like this video.mp4 1622180824748. I think this is better "video-1622181268053.mp4" (we have the correct file extension)

You can refer to this code

const router = require("express").Router();
const multer = require('multer');
const UserToUploadMapping = require('../models/UserToUploadMapping')
const path = require('path');
const uploadFolder = path.join(__dirname, "Videos"); // use a variable to hold the value of upload folder

const storage = multer.diskStorage({
    destination: uploadFolder, // use it when upload
    filename: (req, file, cb) => {
        // nameFile = file.originalname + " "+ Date.now() // --> give "video.mp4 1622180824748"
        let [filename, extension] = file.originalname.split('.');
        let nameFile = filename + "-" + Date.now() + "." + extension; // --> give "video-1622181268053.mp4"
        cb(null, nameFile)
    }
})

const upload = multer({ storage: storage })

router.post('/upload', upload.single('video'), async (req, res, next) => {
    const saveMapping = new UserToUploadMapping({
        userId: '123',
        file: req.file,
    })

    await saveMapping.save()

    res.send("Video uploaded")
})

router.get('/download', async (req, res, next) => {
    const video = await UserToUploadMapping.find({});
    res.download(video[0].file.path); // video[0].file.path is the absolute path to the file
})

module.exports = router;
Đăng Khoa Đinh
  • 5,038
  • 3
  • 15
  • 33
  • While this answer solves the issues with the back-end , The client side is still incomplete and i am facing issues.. pls answer the client side modifications to make this work and i will reward you the bounty – Phil May 28 '21 at 10:39
2

Your code indicates you are handling large files (videos). I would strongly recommend looking at separation of concerns, handling this as part of your other business logic is not recommended based on my experience. This can e.g. complicate firewall rules and DDOS protection when that is needed in the future.

As a minimum, move upload and download into its own server, e.g. 'files.yourappnamehere.com' so that you can handle the specifics separately from your business logic api.

If you run in the public cloud, I would strongly recommend looking at reusing blob upload/download functionality, letting your clients upload directly to blob storage and also handling downloads directly from blob storage, e.g. in Azure, AWS or GCP.

This will save you a lot of the implementation details of handling (very) large files, and also give "free" extensibility options such as events on file upload completion.

chrfrenning
  • 124
  • 2
  • Thank you for the insight.. however it's not a large file at all and this is just a project for learning purposes.. as I am new to handling video or image uploads , I wanted to try it out – Phil Jun 01 '21 at 15:43
1

You are running 2 apps Frontend and Backend with difference ports (3000, 5000) so browsers block cross domain requests. On Express you must enable CORS to allow request from FrontEnd Url (http://localhost:3000).

Dharman
  • 30,962
  • 25
  • 85
  • 135
Centaur
  • 87
  • 1
  • I mentioned in the question that only the download route is failing.. rest everything is working fine and yes i have enabled `cors` already – Phil May 24 '21 at 16:27
1

For the download route, try using window.location functions instead of using Axios.

Omer Haqqani
  • 41
  • 1
  • 9
  • 2
    Please provide a complete answer with code – Phil May 27 '21 at 10:18
  • @Phil you can use this to initiate download directly on client-side. `window.location.assign(ROUTE_FOR_DOWNLOAD);` You can find more solutions here : (https://stackoverflow.com/questions/1066452/easiest-way-to-open-a-download-window-without-navigating-away-from-the-page) – Omer Haqqani May 27 '21 at 12:41
1

It looks like you might have a typo in your get handler... you're referencing an element called 'path', but that's not declared in your schema

router.get('/download', async(req,res,next) => {
    const x = await UserToUploadMapping.find()
    // res.send(x)
    res.download(x[0].path)//<-Path Doesn't seem to be in the schema
})

Since you don't have a try/catch in that function, the resulting error could be bringing down your server, making it unavailable

You might also want to take a look at this for more detail on How to download files using axios

Sully
  • 395
  • 3
  • 7