I am setting up a new server and want to support advanced upload function. Firstly I need to validate file (filetype, filesize, maxcount), and finally upload it to some destination. I tried something with koa-multer but I cannot get multer validation errors.
multer.js
const multer = require('koa-multer')
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './public/uploads/')
},
filename: function (req, file, cb) {
var fileFormat = (file.originalname).split('.')
cb(null, file.fieldname + '_' + Date.now() + '.' + fileFormat[fileFormat.length - 1])
}
})
const fileFilter = (req, file, cb) => {
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, true)
} else {
cb(new Error('This is not image file type'), false)
}
}
const upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 1,
files: 5
},
fileFilter: fileFilter
})
module.exports = upload
router.js
const Router = require('koa-router')
const multer = require('../middlewares/multer')
const auth = require('../middlewares/auth')
const controller = require('../controllers').userController
const schemas = require('../schemas/joi_schemas')
const validation = require('../middlewares/validation')
const router = new Router()
const BASE_URL = `/users`
router.post(BASE_URL, auth , validation(schemas.uPOST, 'body'), controller.
addUser)
router.put(`${BASE_URL}/:id`, auth , multer.single('logo')(ctx, (err) => {
if (err) {
ctx.body = {
success: false,
message: 'This is not image file'
}
}
}), controller.editUser)
router.delete(`${BASE_URL}/:id`, auth , controller.deleteUser)
module.exports = router.routes()
How can I solve upload problem this in best way for long term maintain of code?
The simplest approach for uploading files is the following (assume the form has a file upload field called avatar:
const Koa = require('koa')
const mime = require('mime-types')
const Router = require('koa-router')
const koaBody = require('koa-body')({multipart: true, uploadDir: '.'})
const router = new Router()
router.post('/register', koaBody, async ctx => {
try {
const {path, name, type} = ctx.request.files.avatar
const fileExtension = mime.extension(type)
console.log(`path: ${path}`)
console.log(`filename: ${name}`)
console.log(`type: ${type}`)
console.log(`fileExtension: ${fileExtension}`)
await fs.copy(path, `public/avatars/${name}`)
ctx.redirect('/')
} catch(err) {
console.log(`error ${err.message}`)
await ctx.render('error', {message: err.message})
}
})
Notice that this example uses Async Functions which allows for clean code with a standard try-catch block to handle exceptions.
koa middleware is like a nested callback, you should catch "next()" not after multer
router.put(`${BASE_URL}/:id`, auth , async (ctx, next) => {
try{
await next()
} catch(err) {
ctx.body = {
success: false,
message: 'This is not image file'
}
}
}, multer.single('logo'), controller.editUser)
but you do this, it will catch controller.editUser errors too which not been caught by controller itself.
You can use one of two options:
The first is by adding callback function to the end of the route.
const multer = require('#koa/multer')
//Options to limit file size and file extension
const upload = multer({
dest: '../avatars',
limits: {
fileSize: 1024*1024
},
fileFilter(ctx, file, cb) {
if (!file.originalname.match(/\.(jpg|jpeg|png)$/)) {
return cb(new Error('Please upload a World document'))
}
cb(undefined, true)
}
})
//The last callback should handle the error from multer
router
.post('/upload', upload.single('upload'), async (ctx) => {
ctx.status = 200
}, (error, ctx) => {
ctx.status = 400
ctx.body = error.message
})
})
The second option is by adding try/ catch before calling multer middleware:
router
.post('/upload', async (ctx, next) => {
try {
await next()
ctx.status = 200
} catch (error) {
ctx.status = 400
ctx.body = error.message
}
}, upload.single('upload'), async ctx => {ctx.status = 200})
In last case if exception will thrown in multer, it will be handled by try/catch in previous await next()
Related
Here I tried to build an utility function that receives sub folder path, an array of allowed mimetypes, maximum size of the file and the default error message for mimetype. But I am facing a problem when I am trying to upload a file with different mimetype like pdf or something, I can saw the error on the console but it's not sending as a response. But it's sending the response when file is larger than the allowed maximum size of the function.
const path = require('path');
const multer = require('multer');
const createError = require('http-errors');
function uploader(sub_folder, mimetype, max_size, error_msg) {
const upload_path = path.join(__dirname, `../Public/uploads/${sub_folder}`);
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, upload_path);
},
filename: (req, file, cb) => {
const extName = path.extname(file.originalname);
const file_name = file.originalname.toLowerCase().replace(extName, "").split(" ").join("-") + "-" + Date.now() + extName;
cb(null, file_name);
}
});
const upload = multer({
storage: storage,
limits: {
fileSize: max_size,
},
fileFilter: (req, file, cb) => {
if (mimetype.includes(file.mimetype)) {
cb(null, true);
} else {
cb(createError(error_msg));
}
}
});
return upload;
}
module.exports = {
uploader,
}
Here is I called the my uploader utility function and got the multer upload object.
const multer = require('multer');
const {uploader} = require('../../Uitilies/singUpload');
const avatarUploads = (req,res,next) => {
const upload = uploader(
"avater",
["image/png", "image/jpg", "image/jpeg"],
10000000,
"Only .jpg/.jpeg/.png file with 1MB size is allowed"
);
upload.any()(req,res, (err => {
if(err){
console.log(err.message);
res.status(500).json({
errors: {
avatar: {
msg: err.message,
}
}
});
}else{
next();
}
}));
}
module.exports = avatarUploads;
Here I tried to handle the multer error manually. Because I want to handle it myself.
Here I printed the err.message. The message always print on the console but when it's error of a mimetype then the response didn't send. I have also seen that when the err instanceof multer.MulterError is true then the response is sent but when it's false the response did not send.
my router is,
router.post('/', avatarUploads, adduserValidators, adduserValidatorsHandler, addUser);
Any Solution ?????
I am using multer with expressjs and I am trying to change the DIR to save images depending on the request to a dynamic destination, my code is working fine but always save images inside the post directory, I'm using this middleware with multi requests.
1- How can I make the directory dynamic! example: to save to ./public/products if req product & save to ./public/posts if req post
2- How to make sure that the file is uploaded to the directory with no errors in the controller! then save the path to the database!
3- Is this the best practice to use multer ! in middleware level!
multer middleware fileUpload.js
const multer = require("multer");
const mkdirp = require("mkdirp");
const fs = require("fs");
const getDirImage = () => {
// const DIR = './public/uploads/products';
return `./public/posts`;
};
let storage = multer.diskStorage({
destination: (req, file, cb) => {
console.log(req.params,'&&&&&&&&&&&&',file);
let DIR = getDirImage();
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 * 10 //upto 2 megabytes per file.
},
fileFilter: (req, file, cb) => {
if (file.mimetype == "image/png" || file.mimetype == "image/jpg" ||
file.mimetype == "image/jpeg") {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('File types allowed .jpeg, .jpg and .png!'));
}
}
});
module.exports = upload;
product.js route
const controller = require('../controllers/product.controller');
import { Router } from 'express'; //import from esm
import upload from '../middleware/fileUpload'
const router = Router();
router
.get('/', controller.list)
.post('/', upload.single('image'), controller.create)
.get('/:id', controller.getOne)
export default router;
and my create controller:
exports.create = async (req, res, next) => {
const { name, title, description,subtitle} = req.body;
if (!name || !title) {
return res.status(400).send({
message: 'Please provide a title and a name to create a product!',
});
}
try {
if (req.file) {
req.body.image = req.file.destination + '/' + req.file.filename;
}
const PRODUCT_MODEL = {
name: req.body.name,
title: req.body.title,
description: req.body.description,
image: req.body.image,
};
try {
const product = await Product.create(PRODUCT_MODEL);
console.log('product crerated');
return res.status(201).json(product);
} catch (error) {
console.log(error);
return res.status(500).send({
message: 'Something went wrong: ' + error,
});
}
} catch (error) {
return res.status(500).json(error);
}
};
Problem
How I received the image and JSON data from the postman in node js API? When I send the image from form-data and also send the JSON data only one received. If the image is shown in the request then JSON data not received and validation error occurred.
How I handle this case? Make separate API for image upload. What is the best way todo this
account.js
const express = require('express');
const multer = require('multer');
const { constants } = require('../helpers/contants');
const { commonValidation } = require('../validation/commonValidation');
const { validation } = require('../middleware/validation');
const crypto = require("crypto");
const {
updateProfile,
} = require('../controllers/account');
var storage = multer.diskStorage({
destination: function (req, file, callback) {
callback(null, `public/${constants.imageDir}`)
},
filename: async (req, file, callback) => {
var filetype = '';
if(file.mimetype === 'image/jpg') {
filetype = 'gif';
}
if(file.mimetype === 'image/png') {
filetype = 'png';
}
if(file.mimetype === 'image/jpeg') {
filetype = 'jpg';
}
callback(null, 'image-' + crypto.randomBytes(3).toString('hex') + '-' +
Date.now() + '.' + filetype);
}
})
var upload = multer({ storage: storage,fileFilter: (req, file, callback) => {
if (file.mimetype == "image/png" || file.mimetype == "image/jpg" ||
file.mimetype == "image/jpeg") {
callback(null, true);
} else {
callback(null, false);
return callback(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
} });
const router = express.Router({ mergeParams: true });
router.use(upload.single('image'));
router.use((req, res, next) => {
if (!Array.isArray(req.image)) {
req.image = []
}
req.body = req.body.request
if (req.file) {
//set the avatar in req to get the image name in updateProfile controller
req.body.avatar = req.file.filename;
req.image.push(req.file.filename)
}
return next()
})
router
.patch('/', [commonValidation('updateProfile')], validation, updateProfile)
module.exports = router;
commonValidation
exports.commonValidation = (method) => {
switch (method) {
case 'updateProfile': {
return [
check('name', 'Name is required').not().isEmpty().trim().escape(),
]
}
}
validation
const { validationResult } = require('express-validator');
module.exports.validation = (req, res, next) => {
const errors = validationResult(req)
if (errors.isEmpty()) {
return next()
}
// err.message = 'ValidationError';
const extractedErrors = []
errors.array({ onlyFirstError: true }).map((err) => extractedErrors.push({
[err.param]: err.msg }))
next(extractedErrors);
}
account
exports.updateProfile = asyncHandler(async (req, res, next) => {
if(typeof req.body.avatar !== 'undefined'){
const oldImage = await User.findById(req.user._id);
//oldImage.avatar exist then delete the previous image from directory
if(oldImage.avatar){
deleteImageFromFolder('images',oldImage.avatar)
}
}
const user = await User.findByIdAndUpdate(req.user._id, req.body, {
new: true,
runValidators: true
});
const profile = user.getProfile();
res.status(200).json({
success: true,
data: profile
});
});
postman
In this below image JSON data and image received but the validation error occurs
Trying to make a folder before uploading a file to it. However, there is a problem if I do not update the server on the node when I try to download the file again, it is added to the already created folder. If I update the server, it creates a new folder and uploads it as needed. Please see the code, how can I improve it?
const db = require('../db');
const fs = require('fs');
const path = require('path');
const uuidv1 = require('uuid/v1');
const multer = require('multer');
console.log(uuidv1())
let storage = multer.diskStorage({
destination: `./uploads/${uuidv1()}`,
filename: (req, file, cb) => {
cb(null, 'test1' + '-' + Date.now() + '.' + path.extname(file.originalname));
}
});
let upload = multer({storage});
module.exports = (router) => {
router.get('/get', (req, res) => {
db.connect.query('SELECT * FROM habalka',
{type: db.sequelize.QueryTypes.SELECT}
).then(result => {
res.json(result);
})
});
router.post('/post', upload.any(), (req, res) => {
res.json('test');
});
return router;
};
Your issue is that when You start Your app it generates new uuid (once - at app startup) and passes as string to diskStorage method.
But You want to generate that path every-time when You upload a file.
So here is the solution:
Multer has feature to dynamically generate both destination path and filename.
So You've to pass a function that will generate path and return it in callback.
Example after reading this manual:
let storage = multer.diskStorage({
// pass function that will generate destination path
destination: (req, file, cb) => {
// initial upload path
let destination = path.join(__dirname, 'uploads'); // ./uploads/
// if user logged in and You store user object in session
if (req.session && req.session.user && req.session.user.id) {
destination = path.join(destination, 'users', req.session.user.id, uuidv1()); // ./uploads/users/8/generated-uuid-here/
}
else {
destination = path.join(destination, 'files', uuidv1()); // ./uploads/files/generated-uuid-here/
}
cb(
null,
destination
);
},
// pass function that may generate unique filename if needed
filename: (req, file, cb) => {
cb(
null,
Date.now() + '.' + path.extname(file.originalname)
);
}
});
My final code is here and it works !Thanks
const db = require('../db');
const fs = require('fs');
const uuid = require('uuid');
const path = require('path');
const multer = require('multer');
const shell = require('shelljs');
console.log(uuid())
let storage = multer.diskStorage({
// pass function that will generate destination path
destination: (req, file, cb) => {
// initial upload path
let destination = path.join(__dirname, '../uploads'); // ./uploads/
let id = uuid();
shell.mkdir('-p', './uploads/' + id);
destination = path.join(destination, '', id); // ./uploads/files/generated-uuid-here/
console.log('dest', destination)
cb(
null,
destination
);
},
// pass function that may generate unique filename if needed
filename: (req, file, cb) => {
let ext = file.originalname.substring(file.originalname.lastIndexOf('.'), file.originalname.length);
callback(null,file.originalname.split('.').slice(0,-1).join('.') + '-'+Date.now() +ext);
}
});
var upload = multer({storage: storage})
module.exports = (router) => {
router.get('/get', (req, res) => {
db.connect.query('SELECT * FROM habalka',
{type: db.sequelize.QueryTypes.SELECT}
).then(result => {
res.json(result);
})
});
app.post('/uploads', function (req, res) {
upload(req, res, function (err) {
if (err) {
console.log(err);
return res.end("Something went wrong!");
}else{
let ext = req.file.originalname.substring(req.file.originalname.lastIndexOf('.'), req.file.originalname.length);
var files='./uploads/'+req.file.originalname.split('.').slice(0 ,-1).join('.')+'-'+Date.now()+ext
console.log(req.file.originalname);
collection.insertOne({files},function(err,result){
if(err){
console.log("Something is Wrong");
}else{
res.json(result._id);
console.log(result);
}
})
}
});
})
return router;
};
In your multer middleware, you can do something like:
import util from "util";
import multer from "multer";
export const maxSize = 20 * 1024 * 1024;
export const __upoads_folder='/volume1/server/dash_rental_server/uploads';
let uploadFile = multer({
storage: multer.diskStorage({
destination: (req, file, cb) => {
const fileName = req.params.name;
let directoryPath = __upoads_folder + "/";
if (req?.params?.folder) { directoryPath += req.params.folder + '/' };
if (req?.params?.category) { directoryPath += req?.params?.category + '/' };
cb(null, directoryPath);
},
filename: (req, file, cb) => {
console.log(file.originalname);
cb(null, file.originalname);
},
}),
limits: { fileSize: maxSize },
}).single("file");
export let uploadFileMiddleware = util.promisify(uploadFile);
Then, in your route:
router.post("/upload/:folder/:category", async (req, res) => {
try {
await uploadFileMiddleware(req, res);
if (req.file == undefined) {
return res.status(400).send({ message: "Please upload a file!" });
}
res.status(200).send({
message: "Uploaded the file successfully: " + req.file.originalname,
});
} catch (err) {
if (err.code == "LIMIT_FILE_SIZE") {
return res.status(500).send({
message: `File size cannot be larger than ${maxSize / 1024 / 1024} MB!`,
});
}
res.status(500).send({
message: `Could not upload the file: ${req.file.originalname}. ${err}`,
});
}
});
I have been seen this : NodeJS Multer validate fields before upload but is not work. i tried for make validate field before upload in a days and the result is not working. is it because mutler can't do this?
Iam using MERN : MongoDB,React,Express,Node
i want validate this data in Form-data : dataMurid before uploading the image.
is there another way to overcome this? Maybe using other library? and please give me exampale code.
Route
// #route buat edit/create murid baru
router.post('/datasiswa/create',(req, res,next) => {
upload(req,res,(err)=>{
let request = JSON.parse(req.body.newMurid);
// upload(req.body.data.data, res, (err) => {
// console.log(req.body.data.data);
// });
const { errors, isValid } = validateMuridInput(request);
// Check validation
if (!isValid) {
return res.status(400).json(errors);
}
const muridFields = {};
if (request.tempatLahir) muridFields.tempatLahir = request.tempatLahir;
if (request.jenisKelamin) muridFields.jenisKelamin = request.jenisKelamin;
if (request.nis) muridFields.nis = request.nis;
if (request.nama) muridFields.nama = request.nama;
if (request.tanggalLahir) muridFields.tanggalLahir = request.tanggalLahir;
if (request.namaAyah) muridFields.namaAyah = request.namaAyah;
if (request.namaIbu) muridFields.namaIbu = request.namaIbu;
if (request.noTelepon) muridFields.noTelepon = request.noTelepon;
if (request.hpSiswa) muridFields.hpSiswa = request.hpSiswa;
if (request.hpIbu) muridFields.hpIbu = request.hpIbu;
if (request.hpAyah) muridFields.hpAyah = request.hpAyah;
if (request.alamat) muridFields.alamat = request.alamat;
Murid.findOne({ nis: request.nis })
.then((murid) => {
if (murid) {
errors.nis = 'NIS ini sudah terdaftar';
return res.status(400).json(errors);
} else {
const newMurid = new Murid(muridFields);
newMurid.save()
.then((murid) => {
res.json(murid);
})
.catch((err) => {
console.log(err);
});
}
})
});
});
Upload func
const multer = require('multer');
const path = require('path');
// UPLOAD IMAGE
// Set Storage engine
const storage = multer.diskStorage({
destination: './public/uploads/',
filename: function (req, file, callback) {
callback(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname));
}
});
// Init upload
let upload = multer({
storage: storage,
limits:{fileSize:1000000}, //file size dalam bit
}).fields([{ name: 'fotoDisplay' }, { name: 'newMurid' }]);
With multer, in your req.file, you have all the fields about the file.
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
import fs from 'fs-extra'
router.post('/register', upload.single('avatar'), (req, res, next) => {
return fs.readFile(req.file.path)
.then(content => {
// The content of the file
})
}
req.file has the mimetype and so much more that you can check