I'm writing file upload API and have some troubles with mocking multer. I'm trying to test my endpoint with supertest.
it('load image', async () => {
await app
.post(`${apiImage}`)
.set('Authorization', 'abc123')
.attach('avatar', `${__dirname}/test.jpg`);
.expect(200);
});
Upload works as expected. But every time when I run test, new file being created. So, how to mock multer and does not create new file every time?
I had a middleware helper to wrap multer like this
// middleware/index.js
const multer = require('multer');
exports.multerUpload = () => multer({...});
Then use it in my routes like so
// routes.js
const { multerUpload } = require('path/to/middlewares');
app.post('/upload', multerUpload().any());
Then, in my tests, I can stub out multerUpload
// test.js
const middlewares = require('path/to/middlewares');
sinon.stub(middlewares, 'multerUpload').callsFake(
() => {
return {
any() {
return (req, res, next) => {
// You can do whatever you like to the request body here e.g
req.body = { title: req.query.title };
req.files = [{ location: 'sample.url', key: 'sample.key' }];
return next();
};
},
};
},
);
Mock Multer using Jest (Similar to above ans but using Jest)
App.js
Used multer as middleware
const app = express();
app.use()
app.use(multer({ dest: FILE_UPLOAD_PATH }).single('datafile'));
app.use('/api', apiRoutes);
apiRoute.js
router.post('/datasources', async function (req, res) {
...
});
apiRoute.test.js
const multer = require('multer');
jest.mock('multer');
multer.mockImplementation(() => {
return {
single() {
return (req, res, next) => {
req.body = { title: req.query.title };
req.file = [{ originalname: 'sample.name', mimetype: 'sample.type', path: 'sample.url' }];
return next();
};
},
};
});
.
.
.
Related
I want to upload a file using Multer in a specific directory in my NodeJs app defined by the frontend in vuejs.
It is my first application with multer
The backend code is :
...
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const storage = multer.diskStorage({
destination: (req, file, cb) => {
//req.body.idUser is undefined here
let destinationPath = path.join("upload_file", req.body.idUser);
if (!fs.existsSync(destinationPath)) {
fs.mkdirSync(destinationPath);
}
cb(null, destinationPath);
},
filename: (req, file, cb) => {
let newFileName = Date.now() + path.extname(file.originalname);
cb(null, newFileName);
},
});
const upload = multer({ storage });
app.post(
"/file/add",
upload.fields([{ name: "newfile" }, { name: "idUser" }]),
(req, res) => {
res.json({
response: "file uploaded",
});
},
);
...
And the frontend code is :
...
async sendDocument(event){
const file = event.target.files[0]
const form = new FormData()
form.append("newfile", file, file.name)
form.append("idUser", this.getIdUser)
const opts =
{
method: "POST",
body: form,
}
const url = "http://localhost:3000/file/add";
try{
await fetch(url, opts)
.then(function (response) {
return response.json();
})
.then(function (res) {
console.log(res)
});
}catch(err){
console.log(err)
}
},
...
I tried to debug step by step with console.log to check why req.body.idUser is not defined in storage and I need it to complete the destinationPath
If I replace req.body.idUser by static value like "toto", all work fine
In front, this.getIdUser is working fine. And req.body.idUser is working in app.post
Thanks for your help
Try appending the ID to the form first:
const form = new FormData()
form.append("idUser", this.getIdUser)
form.append("newfile", file, file.name)
I am new in ExpressJs and working on creating api for one of a dashboard created in reactjs. There is a form in a dashboard which is collecting some of information from the users like "title", "description" and "image". I have created an express server to collect that information and to save it into mongodb. For images What I have done is that, I am uploading image to Cloudinary and storing uploaded url and public_id into database.
So after following some of tutorials I have done something like this.
index.js
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const db = require("./db");
// Api router import goes here
const sectionTypesRouter = require("./routes/section-types-router");
const app = express();
const apiPort = 3000;
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(bodyParser.json());
db.on("error", console.error.bind(console, "MongoDB connection error:"));
app.get("/", (req, res) => {
res.send("Hello World!");
});
app.use("/api", sectionTypesRouter);
app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`));
Than, First I have create a file multer.js :
const multer = require("multer");
const storage = multer.diskStorage({
destination: "public/uploads",
filename: (req, file, cb) => {
cb(null, file.fieldname + "-" + Date.now());
},
});
const fileFilter = (req, file, cb) => {
if (file.mimetype === "image/jpeg" || file.mimetype === "image/png") {
cb(null, true);
} else {
//reject file
cb({ message: "Unsupported file format" }, false);
}
};
const upload = multer({
storage: storage,
fileFilter: fileFilter,
});
module.exports = upload;
Below is my api router section-type-router.js :
const express = require("express");
const upload = require("../utils/multer");
const SectionTypesCtrl = require("../controllers/section-types-ctrl");
const router = express.Router();
router.post(
"/section-type",
upload.single("image"),
SectionTypesCtrl.createSectionType
);
router.get("/section-types", SectionTypesCtrl.getSectionTypes);
module.exports = router;
This is the section-type-ctrl.js :
const SectionType = require("../models/section-type-model");
const fs = require("fs");
const path = require("path");
const cloudinaryUploader = require("../utils/cloudinaryUploader");
const createSectionType = async (req, res) => {
const body = req.body;
if (!body) {
return res.status(400).json({
success: false,
error: "Required parameter are missing",
});
}
cloudinaryUploader
.uploads(req.file.path, "Section-Types")
.then((result) => {
const sectionType = new SectionType({
title: body.title,
description: body.description,
image: {
url: result.url,
publicId: result.public_id,
},
});
sectionType
.save()
.then(() => {
return res.status(201).json({
success: true,
id: sectionType._id,
message: "Section type created!",
});
})
.catch((error) => {
return res.status(400).json({
error,
message: "Section type not created!",
});
});
})
.catch((error) => {
res.status(500).send({
message: "failure",
error,
});
});
};
module.exports = {
createSectionType,
};
And lastly this is cloudinaryUpload.js :
const cloudinary = require("../config/cloudinary");
exports.uploads = (file, folder) => {
return new Promise((resolve) => {
cloudinary.uploader.upload(
file,
{
resource_type: "auto",
folder: folder,
},
(err, result) => {
if (!err) {
resolve({
url: result.url,
public_id: result.public_id,
});
} else {
throw err;
}
}
);
}).catch((error) => {
throw error;
});
};
Now, everything is working properly. Images is uploading to the cloudinary and returned url and public_id is storing in database. But the problem is that image that I have uploaded is also upload on local directory public/uploads/. This will may create a storage issue while host a site. So Is there any best way to upload image directly to the cloudinary without creating a copy in local directory which also should work on production mode ?
In your example, the file is being stored to public/uploads on your server because you're telling multer to do so via multer.diskStorage
As #Molda's comment above says, you can avoid this by using the multer-storage-cloudinary package to have Multer store the file in Cloudinary automatically.
Another possibility is to change how you're using Multer so it doesn't store the file anywhere, then take the uploaded file while it's in memory and pass it to Cloudinary's SDK as a stream.
There's an example of this in this blog post on the Cloudinary site: https://cloudinary.com/blog/node_js_file_upload_to_a_local_server_or_to_the_cloud
In your case, you can stop using multer.diskStorage, in favour of just using multer() then use streamifier or another library to turn the uploaded file into a stream, and pass that to cloudinary.uploader.upload_stream()
I'm trying to upload an image in my server.
In the front-end I'm working with Angular.
The front-end is working fine, I only posted to show you how I'm passing the file to back-end!
component.html
<div fxLayout="column" fxLayoutAlign="center center">
<div>
<mat-form-field>
<ngx-mat-file-input placeholder="Only photos" [accept]="'.jpg, .jpeg, .png'" (change)="onChange($event)"></ngx-mat-file-input>
</mat-form-field>
</div>
<div>
<button mat-button (click)="onSubmit()">Send</button>
</div>
</div>
component.ts - functions
imagem: File;
constructor(private uploadService: UploadService) { }
onChange(event) {
this.imagem = event.target.files[0];
}
onSubmit() {
this.uploadService.upload(this.imagem);
}
upload.service.ts - functions
constructor(private http: HttpClient) { }
upload(file: File) {
const formData = new FormData();
formData.append('img', file, file.name);
this.http.post(environment.apiBaseUrl + '/upload', formData, {responseType: 'text'}).subscribe(
res => console.log('Done')
);
}
In the back-end I have this structure:
app.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const rtsIndex = require('./routes/index.router');
var app = express();
// middleware
app.use(bodyParser.json());
app.use(cors());
app.use('/api', rtsIndex);
// start server
app.listen(3000, () => console.log('Port: 3000'));
index.router.js
const express = require('express');
const router = express.Router();
const ctrlUpload = require('../controllers/upload.controller');
router.post('/upload', ctrlUpload.send);
module.exports = router;
upload.controller.js
const express = require('express');
const multer = require('multer');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, Date.now()+'-'+file.originalname);
}
});
const upload = multer({ storage });
module.exports.send = (req, res) => {
upload.single('img');
console.log(req.body, req.files);
res.send('ok');
}
I've tried to call the middleware inside the routing, but I don't think it's correctly and I didn't reach the goal. Algo, the upload is not one.
On server side I get: {} undefined as result, which probably means the multer is not treating the file.
On client side I get: Done.
So what am I doing wrong? And how can I make it works with this back end structure?
Express middlewares are designed to be installed at the routing level. Indeed, in the MVC model express programmers call controllers "routes" (personally I perefer to call them controllers instead of routes in my code). Separating controllers from routes (they both mean the same thing) doesn't really make sense when viewed from traditional MVC frameworks - but you can if you want.
To use multer as designed you need to do it in index.router.js:
index.router.js
const express = require('express');
const router = express.Router();
const multer = require('multer');
const ctrlUpload = require('../controllers/upload.controller');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
cb(null, Date.now()+'-'+file.originalname);
}
});
const upload = multer({ storage });
router.post('/upload', upload.single('img'), ctrlUpload.send);
module.exports = router;
Then you need to remove all the multer related code from upload.controller.js
You can however insist on doing it in upload.controller.js. The key here is to understand what middlewares are.
In Express, a middleware is a function with the prototype:
function (req, res, next) { // next is optional
// middleware logic
}
Yes, that's right. The code in your upload.controller.js file is a middleware. You are writing a middleware yourself that happens to be at the end of the middleware chain.
You see, Express only accepts middlewares. Express has nothing else. Routes are middlewares that happen to be at the end.
Express .use(), .get(), .post() and related methods accept an infinite number of arguments. The first is optionally a route specifier (but not necessary) and the rest of the arguments are middlewares. For example:
app.get('/foo',
(req, res, next) => {
// first middleware
next(); // next is what allows processing to continue
},
(req, res, next) => {
// second middleware
next();
},
(req, res, next) => {
res.send('hello'); // controller logic - a controller
// is just the last middleware
// Note: if you call next() instead of res.send() in a
// controller express will respond with a 500 internal
// server error status with whatever string you pass
// to next() as the error message.
}
);
Knowing this, we know what the function upload.single('img') returns. The function does not execute the middleware logic. Instead it returns the middleware function:
let middleware = upload.single('img');
// middleware is now a function with the prototype:
// (req, res, next) => {}
So to execute the middleware logic we have to call it (express would automatically call it as part of route processing, just like how it calls your controller function, but if we want to do it ourselves we can).
Here's what you need to do if you want to implement the middleware in upload.controller.js:
module.exports.send = (req, res, next) => {
upload.single('img')(req, res, () => {
// Remember, the middleware will call it's next function
// so we can inject our controller manually as the next()
console.log(req.body, req.files);
res.send('ok');
});
}
That's a lot to unpack. We can make the code easier to understand if we refactor it a little:
let middleware = upload.single('img');
module.exports.send = (req, res, next) => {
// Define the controller here to capture
// req and res in a closure:
let controller = () => {
console.log(req.body, req.files);
res.send('ok');
};
middleware(req, res, controller); // call the middleware with
// our controller as callback
}
But this is very non-standard and would be highly unexpected to an experienced Express.js programmer. I wouldn't do this even though it's possible. It also tightly couple the middleware with your controller completely negating the very flexible nature of Express middleware configuration system.
An example of a separated file of Multer Middleware based in the #slebetman answer
./middlewares/multer.js
const multer = require('multer')
const ErrorMessages = require('../constants/ErrorMessages')
function makeid (length) {
var result = ''
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
var charactersLength = characters.length
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result
}
const DIR = './uploads/'
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, DIR)
},
filename: (req, file, cb) => {
const fileName = file.originalname.toLowerCase().split(' ').join('-')
cb(null, makeid(16) + '_' + fileName)
}
})
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype === 'image/png' || file.mimetype === 'application/pdf') {
cb(null, true)
} else {
cb(null, false)
return cb(new Error('Only .png, .jpg, .mp4 and .jpeg format allowed!'))
}
}
})
module.exports.send = (req, res, next) => {
return upload.single('file')(req, res, () => {
// Remember, the middleware will call it's next function
// so we can inject our controller manually as the next()
if (!req.file) return res.json({ error: ErrorMessages.invalidFiletype })
next()
})
}
./routes.js
routes.post('/object', multer.send, ObjectController.createObject)
This avoids the status 500 for wrong filetype
Hope that helps someone :D
A working example of how you can use it in an expressjs handler
import multer from 'multer';
export default {
async upload(req: Request, res: Response, next: NextFunction) {
const middleware = upload.single('photo');
return middleware(req, res, () => {
try {
const file = req.file;
console.log('req.file', req.file);
if (!file) {
throw new ResourceValidationError('media-library', [
{
property: 'avatar',
constraints: {
isNotEmpty: 'avatar should not be empty',
},
},
]);
}
console.log('filename:', file.filename);
res.status(StatusCodes.OK).json({
status: { code: StatusCodes.OK, phrase: ReasonPhrases.OK },
});
} catch (error) {
next(error);
}
});
},
};
i want to read the csv data uploaded to backened.
for this i am sending the data via post from front end..
frontend code:
fileEvent(e) {
this.filedata = e.target.files;
if (this.filedata.length > 0) {
const file: File = this.filedata[0];
console.log(file);
const formData: FormData = new FormData();
formData.append('files', file, file.name);
this.http.post('myUrl', {file: formData}, this.options)
.subscribe((res) => {
});
}
}
screenshot of my file:
now on backened i have written route on api.js that directs me
to the controller i have created.
my api.js code:
router.post('/product/csvdata', function (req, res) {
productimport.importcsvProduct(req, res);
});
and finally on my controller i am consoling my data:
var product = {
importcsvProduct: function (req,res) {
console.log(req.body.file);
}
};
module.exports = product;
but i am getting empty {} in console..??
can anyone check whats wrong with this..??
You need to use a file handling middleware in this case, such as multer.
const express = require('express')
const multer = require('multer')
const upload = multer({ dest: 'uploads/' })
const app = express()
app.post('/profile', upload.single('csvdata'), function (req, res, next) {
// req.file is the `csvdata` file
// req.body will hold the text fields, if there were any
})
I have a small api I have built using Node.js and express.
I am trying to create a logger and I need log the request body AND response body.
app.use((req, res) => {
console.log(req);
res.on("finish", () => {
console.log(res);
});
});
"express": "^4.16.3",
However, i am not able to find the body in the req or res object. Please tell me how i can get them. thanks.
For res.body try the following snippet:
const endMiddleware = (req, res, next) => {
const defaultWrite = res.write;
const defaultEnd = res.end;
const chunks = [];
res.write = (...restArgs) => {
chunks.push(new Buffer(restArgs[0]));
defaultWrite.apply(res, restArgs);
};
res.end = (...restArgs) => {
if (restArgs[0]) {
chunks.push(new Buffer(restArgs[0]));
}
const body = Buffer.concat(chunks).toString('utf8');
console.log(body);
defaultEnd.apply(res, restArgs);
};
next();
};
app.use(endMiddleware)
// test
// HTTP GET /
res.status(200).send({ isAlive: true });
You need body-parser that will create body object for you in your request. To do that
npm install body-parser
var bodyParser = require('body-parser')//add this
app.use(bodyParser())//add this before any route or before using req.body
app.use((req, res) => {
console.log(req.body); // this is what you want
res.on("finish", () => {
console.log(res);
});
});
Ran into this problem but didn't like the solutions. An easy way is to simply wrap the original res.send or res.json with your logger.
Put this as middleware before your routes.
app.use(function responseLogger(req, res, next) {
const originalSendFunc = res.send.bind(res);
res.send = function(body) {
console.log(body); // do whatever here
return originalSendFunc(body);
};
next();
});
https://github.com/expressjs/express/blob/master/lib/response.js
res.send has signature of function(body) { return this; }
Here is a working example using the built in PassThrough stream. Remember to use the express.json() built in middleware to enable request body parsing.
After that, you need to intercept all writes to the response stream. Writes will happen on calling write or end, so replace those functions and capture the arguments in a separate stream.
Use res.on('finish', ...) to gather all the written data into a Buffer using Buffer.concat and print it.
const express = require('express');
const { PassThrough } = require('stream')
const app = express();
app.use(express.json());
app.use((req, res, next) => {
const defaultWrite = res.write.bind(res);
const defaultEnd = res.end.bind(res);
const ps = new PassThrough();
const chunks = [];
ps.on('data', data => chunks.push(data));
res.write = (...args) => {
ps.write(...args);
defaultWrite(...args);
}
res.end = (...args) => {
ps.end(...args);
defaultEnd(...args);
}
res.on('finish', () => {
console.log("req.body", req.body);
console.log("res.body", Buffer.concat(chunks).toString());
})
next();
})
app.use('/', (req, res) => {
res.send("Hello");
});
app.listen(3000);
install npm install body-parser
and use this snippet,
var express = require('express')
var bodyParser = require('body-parser')
var app = express()
// create application/json parser
var jsonParser = bodyParser.json()
to get json response
app.use(jsonParser, function (req, res) {
console.log(req.body); // or console.log(res.body);
})
There is ready made module https://www.npmjs.com/package/morgan-body
const express = require('express')
const morganBody = require("morgan-body")
const bodyParser = require("body-parser")
const app = express()
const port = 8888
// must parse body before morganBody as body will be logged
app.use(bodyParser.json());
// hook morganBody to express app
morganBody(app, {logAllReqHeader:true, maxBodyLength:5000});
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
Hi was looking for same as complete log of request and response as middleware in express js. Found the solution as well w
/*Added by vikram parihar for log */
const moment = require('moment');
const rfs = require("rotating-file-stream");
const geoip = require('geoip-lite');
const { PassThrough } = require('stream')
let path = require('path');
const accessLogStream = rfs.createStream('access.log', {
interval: '1M', // rotate daily
compress: true,
path: path.join(__dirname, '../../log')
});
module.exports = function (req, res, next) {
try {
let geo = geoip.lookup(req.ip);
let country = geo ? geo.country : "Unknown";
let region = geo ? geo.region : "Unknown";
let log = {
"time": moment().format('YYYY/MM/DD HH:mm:ss'),
"host": req.hostname,
"ip": req.ip,
"originalUrl": req.originalUrl,
"geo": {
"browser": req.headers["user-agent"],
"Language": req.headers["accept-language"],
"Country": country,
"Region": region,
},
"method": req.method,
"path": req.path,
"url": req.url,
"body": req.body,
"params": req.params,
"query": req.query,
"response": {
"body": res.body
}
};
const defaultWrite = res.write.bind(res);
const defaultEnd = res.end.bind(res);
const ps = new PassThrough();
const chunks = [];
ps.on('data', data => chunks.push(data));
res.write = (...args) => {
ps.write(...args);
defaultWrite(...args);
}
res.end = (...args) => {
ps.end(...args);
defaultEnd(...args);
}
res.on('finish', () => {
log.response.body = Buffer.concat(chunks).toString()
accessLogStream.write(JSON.stringify(log) + "\n");
})
} catch (error) {
console.log(error)
next(error)
}
next();
}