node.jsangularfile-uploadmultermulter-gridfs-storage

Cancel File Upload: Multer, MongoDB


I can't seem to find any up-to-date answers on how to cancel a file upload using Mongo, NodeJS & Angular. I've only come across some tuttorials on how to delete a file but that is NOT what I am looking for. I want to be able to cancel the file uploading process by clicking a button on my front-end.

I am storing my files directly to the MongoDB in chuncks using the Mongoose, Multer & GridFSBucket packages. I know that I can stop a file's uploading process on the front-end by unsubscribing from the subsribable responsible for the upload in the front-end, but the upload process keeps going in the back-end when I unsubscribe** (Yes, I have double and triple checked. All the chunks keep getting uploaded untill the file is fully uploaded.)

Here is my Angular code:

  ngOnInit(): void {
    // Upload the file.
    this.sub = this.mediaService.addFile(this.formData).subscribe((event: HttpEvent<any>) => {
      console.log(event);
      switch (event.type) {
        case HttpEventType.Sent:
          console.log('Request has been made!');
          break;
        case HttpEventType.ResponseHeader:
          console.log('Response header has been received!');
          break;
        case HttpEventType.UploadProgress:
          // Update the upload progress!
          this.progress = Math.round(event.loaded / event.total * 100);
          console.log(`Uploading! ${this.progress}%`);
          break;
        case HttpEventType.Response:
          console.log('File successfully uploaded!', event.body);
          this.body = 'File successfully uploaded!';
      }
    },
    err => {
      this.progress = 0;
      this.body = 'Could not upload the file!';
    });
  }


  **CANCEL THE UPLOAD**
  cancel() {
    // Unsubscribe from the upload method.
    this.sub.unsubscribe();
  }

Here is my NodeJS (Express) code:

...

// Configure a strategy for uploading files.
const multerUpload = multer({ 
    // Set the storage strategy.
    storage: storage,
    // Set the size limits for uploading a file to 120MB.
    limits: 1024 * 1024 * 120,
    // Set the file filter.
    fileFilter: fileFilter
}); 

// Add new media to the database.
router.post('/add', [multerUpload.single('file')], async (req, res)=>{
    return res.status(200).send();
});

What is the right way to cancel the upload without leaving any chuncks in the database?


Solution

  • So I have been trying to get to the bottom of this for 2 days now and I believe I have found a satisfying solution:

    First, in order to cancel the file upload and delete any chunks that have already been uploaded to MongoDB, you need to adjust the fileFilter in your multer configuration in such a way to detect if the request has been aborted and the upload stream has ended. Then reject the upload by throwing an error using fileFilter's callback:

    // Adjust what files can be stored.
    const fileFilter = function(req, file, callback){
        console.log('The file being filtered', file)
    
        req.on('aborted', () => {
            file.stream.on('end', () => {
                console.log('Cancel the upload')
                callback(new Error('Cancel.'), false);
            });
            file.stream.emit('end');
        })
    }
    

    NOTE THAT: When canceling a file upload, you must wait for the changes to show up on your database. The chunks that have already been sent to the database will first have to be uploaded before the canceled file gets deleted from the database. This might take a while depending on your internet speed and the bytes that were sent before canceling the upload.

    Finally, you might want to set up a route in your backend to delete any chunks from files that have not been fully uploaded to the database (due to some error that might have occured during the upload). In order to do that you'll need to fetch the all file IDs from your .chunks collection (by following the method specified on this link) and separate the IDs of the files whose chunks have been partially uploaded to the database from the IDs of the files that have been fully uploaded. Then you'll need to call GridFSBucket's delete() method on those IDs in order to get rid of the redundant chunks. This step is purely optional and for database maintenance reasons.