0

I am currently developing a MEAN stack web app which should allow users to upload their files to the database. I've been trying to use multer + gridfs-stream as well as multer-gridfs-storage but to no avail. This is what I currently have.

// user.component.html

<form [formGroup]="fileUpload">
  <input type="file" id="myFile" formControlName="file">
  <button (click)="upload()">upload</button>
</form>

// user.component.ts

ngOnInit(){  

this.fileUpload = this.formBuilder.group({
      file: ['', Validators.required]
    });
}

upload() {
    this.usersService.upload(this.fileUpload.value.file).subscribe(result => {
      console.log(result);
    })
  }

// user.service.ts (this is the code that connects my client-side to the server-side)

  upload(file) {
    return this.http.post('./api/upload', file)
  }

// api.js

const express = require('express');
const router = express.Router();
const MongoClient = require('mongodb').MongoClient;
const crypto = require('crypto');

const GridFsStorage = require('multer-gridfs-storage');
const Grid = require('gridfs-stream');
const path = require('path');
const multer = require('multer');
const url = "my_mongo_uri";

var db;

//init gfs
let gfs;

MongoClient.connect(url: url,
{ useNewUrlParser: true }, { useUnifiedTopology: true }, (err, database) => {
    if (err) return console.log(err)
    db = database.db('insurance_app');
    gfs = Grid(db, MongoClient);
    gfs.collection('uploads');
});



// Create a storage object with a given config
const storage = new GridFsStorage({url, 
file: (req, file) => {
    return new Promise((resolve, reject) => {
        crypto.randomBytes(16, (err, buf) => {
            if (err) {
                return reject(err);
            }
            const filename = buf.toString('hex') + path.extname(file.originalname);
            const fileInfo = {
                filename: filename,
                bucketName: 'uploads'
            };
            resolve(fileInfo);
        });
    });
}});

const upload = multer({ storage });



// the endpoint that I am calling
router.post('/upload', upload.single('file'), (req, res) => {
    res.json({file: req.file})
    console.log({file: req.file})
})

But whenever I run my code, my console.log from the terminal will return me {file: undefined}. I've provided the code as I deem it is the closest I've ever been to a working solution.

Brio Tech
  • 75
  • 1
  • 10

2 Answers2

0

Your upload service function should be

upload(file) {
  const data = new FormData();
  data.append('file', file);
  return this.http.post('./api/upload', data);
}

When you write upload.single('file') it means the server expects to receive a request with a field named 'file' and the value of that field is an actual file.

That is exactly what happens when you write data.append('file', file); where the first parameter is the field name and the second is the value (in this case a file) that you will send.

Angular knows that FormData values must be encoded as multipart/form-data which is the only format multer knows how to process as stated in the library description:

Node.js middleware for handling multipart/form-data

devconcept
  • 3,665
  • 1
  • 26
  • 40
  • Hey @devconcept, thanks for the reply. Although after changing my service.ts, my results still gave me a `{file: undefined}`. Any thoughts? Maybe I've written my `api.js` wrong especially the part where I initialized `gfs` as I was unsure during my implementation. Also, I've been reading your documentation on multer-gridfs-storage and all I can say is that it has been concise and easy to read! – Brio Tech Aug 18 '20 at 15:53
  • Moreover, I've updated my question above to provide more data about my `api.js` file. – Brio Tech Aug 18 '20 at 15:54
  • If you write `const storage = new GridFsStorage({url,...` a new connection will be created automatically. It means that whatever you write in the `gsf` part is unrelated to the file storage. Your code should work updating the angular service. Why are you using `this.http.post('./api/upload'...`? Should it be `this.http.post('http://myapiendpoint/api/upload'` or something like that? – devconcept Aug 18 '20 at 17:11
  • Unfortunately, uploading to the database still does not work. Also, I am using `this.http.post('./api/upload'...` because I've already pre defined `api` to route directly to my server. – Brio Tech Aug 18 '20 at 17:43
  • @BrioTech In your `user.component` in the `upload` method the `this.fileUpload.value.file` is of type [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File)? Use the console and check the resulting value – devconcept Aug 18 '20 at 18:53
  • it returns me `undefined` in the console. – Brio Tech Aug 18 '20 at 19:21
  • @BrioTech That is the problem. You are sending `undefined` in the first place because your form is not reading the value correctly. – devconcept Aug 18 '20 at 19:28
  • sorry my bad. It actually returns me `C:\fakepath\insuranceLogo.png` – Brio Tech Aug 18 '20 at 19:37
  • @BrioTech if you send a string where multer expects a file you won't get a `req.file` object. Print in the console the `req.body` on the server, it should be an object with a `file` property. – devconcept Aug 18 '20 at 19:41
  • after doing a `console.log(req.body)`, this is my result `{ data: [Object: null prototype] { file: 'undefined' } }` – Brio Tech Aug 18 '20 at 19:50
  • @BrioTech Apparently file inputs are not compatible with reactive forms. You would have to search for a component that is compatible. There are many libraries especially if you are using material – devconcept Aug 18 '20 at 21:43
0

{file: undefined} is being printed to the console because you're printing console.log({file: req.file} instead of console.log({file: req.body.file}). This will give you the file name you're looking for.

Ruli
  • 2,592
  • 12
  • 30
  • 40
IGJ
  • 75
  • 1
  • 10