File Upload With Multer/GridFS MERN Still Showing Fakepath On Upload And Showing No Errors - file-upload

I'm working with Multer/GridFS for the first time while using the MERN stack. I'm not seeing any errors, but for some reason, when uploading an image, the MongoDB Collection still shows fakepath.
Relevant Code From Server.js File
const express = require("express");
const connectDB = require("./config/db");
const multer = require("multer");
const GridFsStorage = require("multer-gridfs-storage");
const Grid = require("gridfs-stream");
const methodOverride = require("method-override");
const path = require("path");
const crypto = require("crypto");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const config = require("config");
const db = config.get("mongoURI");
const app = express();
// Init gfs
let gfs;
// Connect Database
connectDB("open", () => {
// Initialize Stream
gfs = Grid(connectDB().db, mongoose.mongo);
gfs.collection("profiles");
});
// Create Storage Engine
const storage = new GridFsStorage({
db: connectDB(),
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,
// Should Match Collection Name
bucketName: "profiles"
};
resolve(fileInfo);
});
});
}
});
const upload = multer({ storage });
module.exports = upload;
Relevant Code From Route File
// #route POST api/profile
// #description Create Or Update Current User's Profile
// #access Private
router.post(
"/",
[
auth,
upload.single("profileImage"),
[
(check("archetype", "Archetype Is Required.")
.not()
.isEmpty(),
check("profileImage", "Profile Picture Is Required.")
.not()
.isEmpty())
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
Relevant Code From Form Input File
<form
onSubmit={e => onSubmit(e)}
className='form'
enctype='multipart/form-data'
>
<h3>Basic Profile</h3>
<div className='grid-2'>
<div className='form-group'>
<label>Profile Picture</label>
<label htmlFor='file-upload-profile' className='choose-file'>
Upload Image File
</label>
<input
name='profileImage'
id='file-upload-profile'
type='file'
accept='file_extension|image/*|media_type'
// value={profileImage}
onChange={e => onChange(e)}
/>
MongoDB Shows The Following In Profile Collection:
profileImage:"C:\fakepath\Test Image.jpg"
Thoughts
As I said, there are no errors showing in the terminal or console. The file input name is profileImage, which is what I use in the POST request.

Related

expressJS is preventing me to post a resource

I'm trying to build a mini app in express, the "database" I'm using is a local array object file, I can retrieve resources from this "database" but for some reason I'm not able to post (push) a new object to this object array. This is how the code looks like:
server.js:
const express = require('express');
const app = express();
const userRouter = require('./routes/user.js');
const port = process.env.PORT || 3000;
app.use(express.json());
app.use(express.text());
app.use('/user', userRouter);
app.listen(3000, () => console.log(`listening at ${port}`));
user.js:
const express = require('express');
const BBDD = require('./BBDD.js');
const userRouter = express.Router();
userRouter.get('/:guid', (req, res, next) => {
const { guid } = req.params;
const user = BBDD.find(user => user.guid === guid);
if (!user) res.status(404).send()
res.send(user);
next();
});
userRouter.post('/', (req, res, next) => {
let user = {};
user.name = req.body.name;
user.id = req.body.id;
BBDD.push(user);
next();
});
module.exports = userRouter;
And this is my local "database" file I want to perform logical CRUD operations:
BBDD.js
const BBDD = [{
index: 0,
guid: "1",
name: "Goku"
},
{
index: 1,
guid: "2",
name: "Vegeta"
},
];
module.exports = BBDD;
this is how I try to post a new resource, and this is the error I get:
It seems to be in order, but it won't work and can't find the bug.
Remove the next and send a response .express is having trouble finding the next matching handler because there is none

Change multer destination folder based on the request

I am using express Multer middleware to save images in my application, I am using the same middleware to save the users, posts, and products images, the problem is all the images saved to the same directory "./public/uploads/", and what I want is to save each request to a specific folder,
For example:
The posts images to "./public/uploads/posts"
The products images to "./public/uploads/products"
I couldn't find a way to pass a variable from the routes to the middleware to change the destination dynamically
Could you please assist with this?
thanks in advance.
Here is my code:
Multer middleware file (multer.js)
const multer = require("multer");
const fs = require("fs");
let configDIR = "`./public/uploads/";
let storage = multer.diskStorage({
destination: (req, file, cb) => {
console.log('req.query.name', req.query.name)
let DIR = configDIR;
if (!fs.existsSync(DIR)) {
fs.mkdirSync(DIR, { recursive: true });
}
cb(null, DIR);
},
filename: (req, file, cb) => {
const fileName = "overDress" + Date.now() + "" +
file.originalname.toLowerCase().split(' ').join('-');
cb(null, fileName)
},
});
const upload =
multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 //up to 1 megabytes per file.
},
}).single("image");
module.exports = {
upload,
};
One of my routes, the post route:
const controller = require('../controllers/post.controller');
import { Router } from 'express';
const router = Router();
import {upload} from '../middleware/multer'
let ImageFolder = 'posts'
router
.post('/',
(req, res, next) => {
upload(req, res, (err) => {
if (err)
return res.send({status:false, message: 'Invalid Image', error: err })
console.log('File Saved with no errors')
next()
}
)
export default router;

Express router's mock post handler doesn't work as expected

I have a documents router which has router.post('/mine', [auth, uploadFile], async (req, res) => { ... }) route handler. The actual implementation of this route handler is below.
documents.js router
const createError = require('./../helpers/createError');
const auth = require('./../middlewares/auth');
const uploadFile = require('./../middlewares/uploadFile');
const express = require('express');
const router = express.Router();
router.post('/mine', [auth, uploadFile], async (req, res) => {
try {
let user = await User.findById(req.user._id);
let leftDiskSpace = await user.leftDiskSpace();
if(leftDiskSpace < 0) {
await accessAndRemoveFile(req.file.path);
res.status(403).send(createError('Your plan\'s disk space is exceeded.', 403));
} else {
let document = new Document({
filename: req.file.filename,
path: `/uploads/${req.user.username}/${req.file.filename}`,
size: req.file.size
});
document = await document.save();
user.documents.push(document._id);
user = await user.save();
res.send(document);
}
} catch(ex) {
res.status(500).send(createError(ex.message, 500));
}
});
module.exports = router;
I'm currently writing integration tests using Jest and Supertest. My current documents.test.js test file is below:
documents.test.js test file
const request = require('supertest');
const { Document } = require('../../../models/document');
const { User } = require('../../../models/user');
const fs = require('fs');
const path = require('path');
let server;
describe('/api/documents', () => {
beforeEach(() => { server = require('../../../bin/www'); });
afterEach(async () => {
let pathToTestFolder = path.join(process.cwd(), config.get('diskStorage.destination'), 'user');
// Remove test uploads folder for next tests
await fs.promises.access(pathToTestFolder)
.then(() => fs.promises.rm(pathToTestFolder, { recursive: true }))
.catch((err) => { return; });
// Remove all users and documents written in test database
await User.deleteMany({});
await Document.deleteMany({});
server.close();
});
describe('POST /mine', () => {
it('should call user.leftDiskSpace method once', async () => {
let user = new User({
username: 'user',
password: '1234'
});
user = await user.save();
let token = user.generateAuthToken();
let file = path.join(process.cwd(), 'tests', 'integration', 'files', 'test.json');
let documentsRouter = require('../../../routes/documents');
let errorToThrow = new Error('An error occured...');
user.leftDiskSpace = jest.fn().mockRejectedValue(errorToThrow);
let mockReq = { user: user };
let mockRes = {};
documentsRouter.post = jest.fn();
documentsRouter.post.mockImplementation((path, callback) => {
if(path === '/mine') {
console.warn('called');
callback(mockReq, mockRes);
}
});
const res = await request(server)
.post('/api/documents/mine')
.set('x-auth-token', token)
.attach('document', file);
expect(documentsRouter.post).toHaveBeenCalled();
expect(user.leftDiskSpace).toHaveBeenCalled();
});
});
});
I create mock post router handler for documents.js router. As you can see from mockImplementation for this route handler, it checks if the path is equal to '/mine' (which is my supertest endpoint), then calls console.warn('called'); and callback. When I run this test file, I can not see any yellow warning message with body 'called'. And also when POST request endpoint /api/documents/mine the server doesn't trigger my mock function documentsRouter.post. It has never been called. So I think the server's documents router is not getting replaced with my mock post route handler. It still uses original post route handler to respond my POST request. What should I do to test if my mock documentsRouter.post function have been called?
Note that my User model has a custom method for checking left disk space of user. I also tried to mock that mongoose custom method but It also doesn't work.

Why when I upload file with apollo-server the file is uploaded but the file is 0kb?

I tried to solve the problem but I don't understand why the file is uploaded but his size is 0Kb.
I see this code in the tutorial but he works on that tutorial but, is not worked for me
const { ApolloServer, gql } = require('apollo-server');
const path = require('path');
const fs = require('fs');
const typeDefs = gql`
type File {
url: String!
}
type Query {
hello: String!
}
type Mutation {
fileUpload(file: Upload!): File!
}
`;
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
Mutation: {
fileUpload: async (_, { file }) => {
const { createReadStream, filename, mimetype, encoding } = await file;
const stream = createReadStream();
const pathName = path.join(__dirname, `/public/images/${filename}`);
await stream.pipe(fs.createWriteStream(pathName));
return {
url: `http://localhost:4000/images/${filename}`,
};
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen().then(({ url }) => {
console.log(`๐Ÿš€ Server ready at ${url}`);
});
then when I upload the file, it is uploaded, but the file is 0kb
like this
What is happening is the resolver is returning before the file has uploaded, causing the server to respond before the client has finished uploading. You need to promisify and await the file upload stream events in the resolver.
Here is an example:
https://github.com/jaydenseric/apollo-upload-examples/blob/c456f86b58ead10ea45137628f0a98951f63e239/api/server.js#L40-L41
In your case:
const resolvers = {
Query: {
hello: () => "Hello world!",
},
Mutation: {
fileUpload: async (_, { file }) => {
const { createReadStream, filename } = await file;
const stream = createReadStream();
const path = path.join(__dirname, `/public/images/${filename}`);
// Store the file in the filesystem.
await new Promise((resolve, reject) => {
// Create a stream to which the upload will be written.
const writeStream = createWriteStream(path);
// When the upload is fully written, resolve the promise.
writeStream.on("finish", resolve);
// If there's an error writing the file, remove the partially written
// file and reject the promise.
writeStream.on("error", (error) => {
unlink(path, () => {
reject(error);
});
});
// In Node.js <= v13, errors are not automatically propagated between
// piped streams. If there is an error receiving the upload, destroy the
// write stream with the corresponding error.
stream.on("error", (error) => writeStream.destroy(error));
// Pipe the upload into the write stream.
stream.pipe(writeStream);
});
return {
url: `http://localhost:4000/images/${filename}`,
};
},
},
};
Note that itโ€™s probably not a good idea to use the filename like that to store the uploaded files, as future uploads with the same filename will overwrite earlier ones. I'm not really sure what will happen if two files with the same name are uploaded at the same time by two clients.

multer file upload - how to get a value from multer in route?

I'm uploading a file using multer with Express.
I'd like access a value from multer's storage object inside the route.
How can I do that?
Multer configuration (right now I only know how to log the key):
const aws = require("aws-sdk");
const multer = require("multer");
const multerS3 = require("multer-s3");
function configureUpload () {
const s3 = new aws.S3({...my credentials...});
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.S3_BUCKET_NAME,
metadata: (req, file, cb) => cb(null, { fieldName: file.fieldname }),
key: (req, file, cb) => {
const key = `${new Date().toISOString()}-${file.originalname}`;
return cb(console.log("KEY: ", key), key); // The string I need to access in route
},
}),
});
return upload;
}
The route:
const express = require("express");
const Person = require("../../../db/models/person");
const configureUpload = require("../../../configureUpload ");
const router = express.Router();
// Saving to MongoDB with mongoose
router.post("/", configureUpload ().any(), async (req, res) => {
Person.create({
...req.body,
files: [] // I want to add the string in multer.storage.key to this array
})
.then((person) => {
...
})
.catch((err) => {
...
});
});
module.exports = router;
This an exapmle of what Tarique Akhtar Ansari already said. Adding your key to the req object so that you can access the key's value in your controller/route like so:
const aws = require("aws-sdk");
const multer = require("multer");
const multerS3 = require("multer-s3");
function configureUpload () {
const s3 = new aws.S3({...my credentials...});
const upload = multer({
storage: multerS3({
s3: s3,
bucket: process.env.S3_BUCKET_NAME,
metadata: (req, file, cb) => {cb(null, { fieldName: file.fieldname })},
key: (req, file, cb) => {
const key = `${new Date().toISOString()}-${file.originalname}`;
req.key = key; // added the key to req object
// return cb(console.log("KEY: ", key), key); // The string I need to access in route
},
}),
});
return upload;
}
Accessing the value of the key inside your controller or route
const express = require("express");
const Person = require("../../../db/models/person");
const configureUpload = require("../../../configureUpload ");
const router = express.Router();
// Saving to MongoDB with mongoose
router.post("/", configureUpload ().any(), async (req, res) => {
console.log('here is the value your key', req.key); // it's that simple.
Person.create({
...req.body,
files: [] // I want to add the string in multer.storage.key to this array
})
.then((person) => {
...
})
.catch((err) => {
...
});
});
module.exports = router;
you can simply add req.key = keyValue
then you can access in next route using req.key name
or you can also access req.file or req.files object in route
In express everything is a middleware so you can easily pass and access in next middleware