I am trying to upload multiple files to my server using multer. Even though I can read the req.files array, but can't access the buffer property of them. I tried console.logging them, which only results in undefined.
This is my html (ejs) code:
<form method="post" action="/send" enctype="multipart/form-data">
<div class="row">
<div class="col">
<input type="file" multiple class="form-control" name="files" />
</div>
<div class="col text-end">
<input type="submit" class="btn btn-primary w-100" />
</div>
</div>
</form>
The route:
const express = require("express");
const router = express.Router();
const multer = require("multer");
const upload = multer({ dest: "uploads/" });
const indexController = require("../controllers/index.controller");
router.post("/send", upload.array("files", 5), indexController.send);
module.exports = router;
... the controller:
exports.send = async (req, res) => {
...
console.log(req.files); // [ { fieldname: 'files', ..., size: 1576 } ]
console.log(req.files.map((f) => f.buffer)); // [ undefined ]
...
}
How do I read the .buffer property of each file, when there are multiple? Any help is appreciated.
I am using memoryStorage and still cant figure this out. I wont get the buffer file in the Multers filter option, only at the controllers level. I tried this code but I only get these fields (no buffer)
// RESPONSE (buffer is missing)
{
fieldname: 'formImages',
originalname: 'file-test.jpeg',
encoding: '7bit',
mimetype: 'image/jpeg'
}
// CODE:
const multer = require("multer");
const sharp = require("sharp");
const sharpFilter = async (req, file, cb) => {
const locationPath = `${process.env.UPLOAD_FOLDER + "/" + file.originalname}`;
await sharp(file)
.resize({
width: 2000,
height: 2000,
fit: "cover",
})
.toFile(locationPath);
cb(null, true);
};
exports.upload = multer({
storage: multer.memoryStorage(),
fileFilter: sharpFilter,
});
Related
const express = require('express');
const multer = require('multer');
const uuid = require('uuid');
const server = express();
server.use(express.static('client/'));
const upload = multer({
storage: multer.diskStorage({
destination: function (req, file, callback) {
callback(null, 'temp/upload/');
},
filename: function (req, file, callback) {
callback(null, uuid.v4().concat('.').concat(file.mimetype.split('/')[1]));
}
}),
fileFilter: function (req, file, callback) {
const whitelist = ['image/jpeg', 'image/png'];
if (whitelist.includes(file.mimetype)) {
callback(null, true);
} else {
callback(null, false);
}
},
}).single('image');
server.post('/api/dummy', (req, res) => {
upload(req, res, async function (error) {
if (error || req.file === undefined) return res.sendStatus(400);
// Doing something, if there is no error.
})
});
server.listen(80);
As you can see, multer expects a file with field name image. Now, if I send a file with correct field name, it gives expected result both from browser and Thunderclient.
Now, I tried to change the field name of the file to anything other than image. Still, Thunderclient works, giving expected result. But browser not getting any response for big images i.e. - 7MB. But surprisingly getting expected result in browser too if the file size is relatively low i.e. - 250KB.
This is the client.
<!DOCTYPE html>
<html>
<body>
<form action="/api/dummy" method="post" enctype="multipart/form-data">
<label for="name">Name</label>
<input type="text" name="name" id="name">
<br>
<label for="logo">Logo</label>
<input type="file" name="logo" id="logo">
<br>
<input type="submit" value="submit">
</form>
</body>
</html>
I have tried with this two random images -
with this image, the browser did not get any response but
with this image, the browser did get the response back.
I'm trying to upload a file from webpage to my backend but nothing happends. There is what i did:
Here's the form from the html file:
<form action="/api/bulk" method="POST" enctype="multipart/form-data">
<div style="width: 200px">
<input
type="file"
id="user_group_logo"
class="custom-file-input"
accept=".xlsx, .xls, .csv"
name="file"
/>
<label id="user_group_label" for="user_group_logo">
<i class="fas fa-upload"></i> Choose a file...
</label>
<button class="btn btn-primary" type="submit">Upload</button>
<div class="text-center"></div>
<div class="text-center mt-2"></div>
</div>
</form>
here's the routing:
router.route('/api/bulk').post(modelController.postBulk);
and here's the controller method that should upload the file to /uploads folder
var multer = require('multer');
const storage = multer.diskStorage({
destination: './uploads',
});
const upload = multer({
storage: storage,
});
exports.postBulk = async (req, res) => {
try {
console.log('test');
upload(req, res, (err) => {
if (err) {
console.log('error');
} else {
console.log(req.file);
res.send('test-req.file');
}
});
} catch (err) {
res.status(404).json({
status: 'fail',
message: err.message,
attention: 'Cannot verify the CSV file. Call support!',
});
}
};
I don't get either a message in the console, so the method is not "accessed" somehow (I should get a "test" when I try to upload the file at least).
Any ideas, please?
Try this
const express = require('express');
const router = express.Router();
const multer = require('multer');
const storage = multer.diskStorage({
destination: './uploads',
});
const upload = multer({
storage: storage,
});
const postBulk = async (req, res) => {
try {
console.log(req.file);
req.status(200).json({})
} catch (err) {
res.status(404).json({
status: 'fail',
message: err.message,
attention: 'Cannot verify the CSV file. Call support!',
});
}
};
router.post(`/api/bulk`, upload.single('file'), postBulk);
try this
router.route('/api/bulk').post(upload.single('file'),modelController.postBulk);
upload is your constant holding multer configuration
.single is for your single file upload and .array is for multiple file upload.
please check req.file inside your controller to get the file uploaded via form
I am working on a Nodejs project and I am trying to use multer to store images locally. I have made my form in HTML and was able to get everything working as it should. When images are saved, they are stored in an uploads folder i created. However, I am running into the issue of images from the form being stored in an uploads folder, even when there are errors such as empty fields that cause a redirection to the form page. Is there anyway to prevent the image from saving unless the form is properly completed? Heres the link to my repo: https://github.com/halsheik/RecipeWarehouse.git. Below are the edits made to add multer into project.
// Modules required to run the application
const express = require('express');
const multer = require('multer');
const crypto = require('crypto');
const path = require('path');
const { ensureAuthenticated } = require('../config/auth');
// Creates 'mini app'
const router = express.Router();
// Models
const Recipe = require('../models/Recipe'); // Recipe Model
// Set up storage engine
const storage = multer.diskStorage({
destination: function(req, file, callback){
callback(null, 'public/uploads');
},
filename: function(req, file, callback){
crypto.pseudoRandomBytes(16, function(err, raw) {
if (err) return callback(err);
callback(null, raw.toString('hex') + path.extname(file.originalname));
});
}
});
const upload = multer({
storage: storage
});
// My Recipes
router.get('/myRecipes', ensureAuthenticated, function(req, res){
Recipe.find({}, function(err, recipes){
if(err){
console.log(err);
} else {
res.render('./home/myRecipes', {
recipes: recipes,
ingredients: recipes.ingredients,
directions: recipes.directions
});
}
});
});
// Create Recipe Page
router.get('/createRecipe', ensureAuthenticated, function(req, res){
res.render('./home/createRecipe');
});
// Create Recipe
router.post('/createRecipe', upload.single('recipeImage'), ensureAuthenticated, function(req, res){
const { recipeName, ingredients, directions } = req.body;
let errors = [];
// Checks that all fields are not empty
if(!recipeName || !ingredients || !directions){
errors.push({ msg: 'Please fill in all fields.' });
}
// Checks that an image is uploaded
if(!req.file){
errors.push({ msg: 'Please add an image of your recipe' });
}
// Checks for any errors and prevents recipe creation if any
if(errors.length > 0){
console.log(errors);
res.render('./home/createRecipe', {
errors,
recipeName,
ingredients,
directions
});
} else {
// Create a new 'Recipe' using our model
const newRecipe = new Recipe({
recipeName: recipeName,
author: req.user._id,
ingredients: ingredients,
directions: directions,
});
// Saves recipe to mongoDB database
newRecipe.save().then(function(){
res.redirect('/recipes/myRecipes');
}).catch(function(err){
console.log(err);
});
}
});
module.exports = router;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Homemade</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div class="newRecipeContainer">
<form action="/recipes/createRecipe" method="POST" enctype="multipart/form-data">
<div class="recipeNameContainer">
<label class="recipeNameLabel">Title</label>
<input type="text" name="recipeName">
</div>
<div class="recipeImage">
<input type="file" accept="image/*" name="recipeImage" onchange="validateImageFile(this);"/>
</div>
<div class="ingredientsContainer">
<button class="addIngredientButton" type="button" #click="addIngredientForm">Add Another Ingredient</button>
<div class="allIngredients" v-for="(ingredient, ingredientIndex) in ingredients">
<label class="ingredient">{{ ingredientIndex + 1 }}.)</label>
<input type="text" name="ingredients" v-model="ingredient.ingredient">
<button class="deleteIngredientButton" type="button" v-if="ingredientIndex > 0" #click="deleteIngredientForm(ingredientIndex)">Delete Ingredient</button>
</div>
</div>
<div class="directionsContainer">
<button class="addDirectionButton" type="button" #click="addDirectionForm">Add Another Direction</button>
<div class="allDirections" v-for="(direction, directionIndex) in directions">
<label class="direction">{{ directionIndex + 1 }}.)</label>
<input type="text" name="directions" v-model="direction.direction">
<button class="deleteDirectionButton" type="button" v-if="directionIndex > 0" #click="deleteDirectionForm(directionIndex)">Delete Direction</button>
</div>
</div>
<button class="createRecipeButton" type="submit">Create Recipe</button>
</form>
</div>
<script src="/controls/newRecipeControl.js"></script>
</body>
</html>
Thanks for any help!
I had the same problem with this for a school project I did a month back. I solved it by using multers memory storage and then persisting it myself using the buffer that multer gives. a bit of a dumb workaround, but it did the trick for me, and since you seem to have the same problem as I did, it will work for you too.
check out their documentation on how to use it. also check out how to write the buffer to a file with fs module.
EDIT:
Ok, I've found the code:
export const validateRequest = (req, res, next, schema, fileExpected = false) => {
const options = { abortEarly: false, allowUnknown: true, stripUnknown: true };
const { error, value } = schema.validate(req.body, options);
const validationErrors = [];
if (fileExpected && req.file === undefined) validationErrors.push('"prod_image" is requiered.');
if (error) error.details.forEach(x => validationErrors.push(x.message));
if (validationErrors.length > 0) {
res.status(400).json(validationErrors);
} else {
req.body = value;
next();
}
};
since multer populates req.file and req.body at the same time, and since it needs to run before joi to handle the multipart/form-data, this is how I validate the reqest. After this, all that is left is to persist the file to disk. I did it like so:
import fs from 'fs';
import path from 'path';
import multer from 'multer';
import { randomBytes } from 'crypto';
import { srcPath } from './../settings';
const storage = multer.memoryStorage();
const fileFilter = (req, file, cb) => {
const ext = path.extname(file.originalname);
if (ext !== '.jpg' && ext !== '.png') return cb(new Error('Invalid image extension.'));
cb(null, true);
};
export const upload = multer({storage: storage, fileFilter: fileFilter });
export const persistImage = (file, cb) => {
const ext = path.extname(file.originalname);
const newName = randomBytes(16).toString('hex') + ext;
const imagesFolderPath = srcPath + '/productImages/';
const finalPath = path.join(imagesFolderPath, newName);
fs.writeFile(finalPath, file.buffer, (err) => cb(err, newName));
};
export const removeImage = (imageName, cb) => {
const imagesFolderPath = srcPath + '/productImages/';
const finalPath = path.join(imagesFolderPath, imageName);
fs.unlink(finalPath, (err) => cb(err));
};
The removeImage function is needed if saving data to the database fails. This is a really bad solution in my opinion, but it was a requirement for the class. My professor considers saving images in the database evil. In a real scenario you would want to save them to something like Azures blob storage or something akin to that. That would be ideal, but my project needed the files to be saved in the project folder, soooooo.....
Many things can go wrong when doing it like this. Hope this helps, cheers.
I have created a simple node solution which contains a form and on that submit the form it will display the image that is being inserted in the form.
app.js
const app = express()
app.use(express.static('public'))
app.engine('hbs',handlebars({
layoutsDir : __dirname + '/views/layouts',
defaultLayout : "mainlayout",
extname : "hbs",
partialsDir : __dirname + '/views/partials'
}))
app.use("/uploader", imgUploader)
app.set('view engine','hbs')
impUpload.js
const express = require('express')
const route = express.Router();
const multer = require('multer');
const path = require('path');
const Storage = multer.diskStorage({
destination: './public/uploads',
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
}
})
const upload = multer({
storage: Storage,
fileFilter: function (req, file, cb) {
checkFileType(file, cb);
}
}).single('myImage');
function checkFileType(file, cb) {
const filetypes = /jpeg|jpg|png|gif/;
const extname = filetypes.test(path.extname(file.originalname).toLowerCase())
const mimeType = filetypes.test(file.mimetype);
if (extname && mimeType) {
return cb(null, true)
}
else {
cb('Error: Images Only!!!');
}
}
route.get("/", (req, res) => {
console.log("inside imgupload folder")
res.render("fileUpload")
})
route.post("/uploaded", (req, res) => {
upload(req, res, (error) => {
if (error) {
res.render("fileUpload", { message: error })
}
else {
if (req.file == undefined) {
res.render("fileUpload", { message: 'Please upload a file' })
}
else {
res.render('fileUpload', {
message: 'File Uploaded Successfully',
file: `uploads/${req.file.filename}`
});
}
}
})
})
module.exports = route
fileUpload.js
<div class="container">
<h1>file upload</h1>
{{message}}
<form action="/uploader/uploaded" method="post" enctype="multipart/form-data">
<div class="file-field input-field">
<div class="btn">
<span>File</span>
<input name="myImage" type="file">
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text">
</div>
</div>
<button type="submit" class="btn n">Submit</button>
</form>
<br>
</div>
<div>
{{#if file}}
<img src="{{file}}" class="responsive-img">
{{/if}}
</div>
Currently, my solution is structured as below
I am getting the error in the console
GET http://localhost:3000/uploader/uploads/myImage-1589223958713.PNG 404 (Not Found)
I am not getting why it's trying to find that img in the uploader/uploads although I have defined public folder in the app.js
But when trying the same code in the app.js it's working absolutely fine.
also if I try express().use(express.static(path.join(__dirname , '../public'))) in the imgupload.js then i am getting the error
Not allowed to load local resource: file:///C:/arunoday/node/login_express_mongo/public/uploads/myImage-1589220613014.PNG
any help would be appreciated.
This is just how browser's handle relative paths.
You have a Handlebars template that contains the following:
<img src="{{file}}" class="responsive-img">
The value of file is set to uploads/${req.file.filename}, which becomes something like uploads/myImage-1589223958713.PNG.
When your template is executed with above value for file you get:
<img src="uploads/myImage-1589223958713.PNG" class="responsive-img">
When the browser sees a relative URL, like uploads/myImage-1589223958713.PNG, it has to figure out the absolute URL. Since this relative URL does not begin with a /, the browser thinks it is a child path of the current page URL.
If the current page URL is http://localhost:3000/uploaded/uploader, the browser thinks your uploads/myImage-1589223958713.PNG URL is a child of http://localhost:3000/uploader/ and so produces: http://localhost:3000/uploader/uploads/myImage-1589223958713.PNG.
To get the correct URL, you want to set the value for file so that it includes the full path:
file: `/uploads/${req.file.filename}`
Update:
Note that /public should not be used included in the value for file because the /public directory is registered with express as a directory in which to look for static assets.
I am using cloudinary to upload photos on my webpage.
I have added a new function to the webpage ( facebook login) and now the cloudinary throw an error when I try to upload a photo like before.
"/home/ubuntu/workspace/YelpCamp/node_modules/cloudinary/lib/utils.js:982
throw "Must supply api_key";
^
Must supply api_key"
The API_KEY, API_SECRET and the CLOUD_NAME are saved in a .env file, and these are correct.
var express = require("express");
var router = express.Router();
var Campground = require("../models/campground");
var middleware = require('../middleware') ; //because of the index.js default name
var geocoder = require('geocoder');
///////////////////////////////////////
// MULTER
var multer = require('multer');
var storage = multer.diskStorage({
filename: function(req, file, callback) {
callback(null, Date.now() + file.originalname);
}
});
var imageFilter = function (req, file, cb) {
// accept image files only
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/i)) {
return cb(new Error('Only image files are allowed!'), false);
}
cb(null, true);
};
var upload = multer({ storage: storage, fileFilter: imageFilter});
///////////////////////////////////////
//CLOUDINARY
var cloudinary = require('cloudinary');
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.CLOUD_KEY,
api_secret: process.env.CLOUD_SECRET
});
router.post('/', middleware.isLoggedIn, upload.single('image'), function(req, res) {
console.log(req.user);
//GEOCODER
geocoder.geocode(req.body.location, function (err, data) {
//CLOUDINARY UPLOAD
cloudinary.uploader.upload(req.file.path, function(result) {
var image = {
path: result.secure_url,
id: result.public_id
};
var name = req.body.name;
var price = req.body.price;
var description = req.body.description;
if(req.user.facebook.username) {
var username = req.user.facebook.username;
} else {
username = req.user.local.username;
}
var author = {
id: req.user._id,
username: username
};
if(!data.results[0]) {
var lat = 90;
var lng = 0;
var location = "North Pole";
} else {
lat = data.results[0].geometry.location.lat;
lng = data.results[0].geometry.location.lng;
location = data.results[0].formatted_address;
}
var newCampground = {name: name, price: price, image: image, description: description, author: author, location: location, lat: lat, lng: lng};
//create
Campground.create(newCampground, function(err, result) {
if(err) {
console.log(err);
} else {
console.log(result);
res.redirect('/campgrounds');
}
});
});
});
});
<% include ../partials/header %>
<div class="row">
<h1 style="text-align: center;">Create a New CampGround</h1>
<div style='width: 30%; margin: 30px auto;'>
<form action='/campgrounds' method='POST' enctype="multipart/form-data">
<div class='form-group'>
<label for="name">Name</label>
<input class="form-control" id="name" type='text' name='name' placeholder='Name'>
</div>
<div class='form-group'>
<label for="price">Price</label>
<input class="form-control" id="price" type='number' name='price' placeholder='price' min="0.01" step="0.01">
</div>
<div class='form-group'>
<label for="image">Image</label>
<input type="file" id="image" name="image" accept="image/*" required>
</div>
<div class='form-group'>
<label for="description">Description</label>
<input class="form-control" id="description" type='text' name='description' placeholder='Write description'>
</div>
<div class="form-group">
<label for="location">Location</label>
<input class="form-control" type="text" name="location" id="location" placeholder="Yosemite National Park, CA">
</div>
<div class='form-group'>
<button class="btn btn-lg btn-default btn-primary btn-block">Submit!</button>
</div>
</form>
Go Back
</div>
</div>
<% include ../partials/footer %>
The cloudinary config values must be in strings otherwise they won't work.
Configure your code either of these ways in your cloudinary segment:
1a: CloudinaryConfig.js:
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.CLOUD_KEY,
api_secret: process.env.CLOUD_SECRET
});
1b: .env
CLOUDINARY_CLOUD_NAME='sample'
CLOUDINARY_API_KEY='874837483274837'
CLOUDINARY_API_SECRET='a676b67565c6767a6767d6767f676fe1'
2: Putting your cloudinary-supplied values directly into the code:
cloudinary.config({
cloud_name: 'sample',
api_key: '874837483274837',
api_secret: 'a676b67565c6767a6767d6767f676fe1'
});
NOTE: Ensure that you don't expose your config values for security reasons. If you're committing code to a public repo like github, use .gitignore to leave out whatever file where you store your secret info.
Checkout the cloudinary docs for more reference: https://cloudinary.com/documentation/node_integration.
The reason can be your js file where you would have not included require('dotenv').config();
Try adding this and then run your code.
Can you try putting the API key directly in your code and see if that works. You could also add a print statement and see what response you get for API key
The dotenv must be required up top where you are using your process.env.API_KEY:
require("dotenv").config();
If you're using a NodeJS server-side function to upload,
ensure you've installed the dotenv package;
yarn add dotenv
Thereafter, put the cloudinary variables in a '.env' file.
From the file (controller) where you're to make the upload, you must point to
dotenv, to that file, relative to where your package.json file is sitting. Here's an instance for me, trying to make an upload from 'CompanyControllers.ts'
import dotenv from "dotenv";
dotenv.config({path: "./backend/src/settings/controllers/CompanyControllers.ts"});
So, if I do;
console.log("\n\t Cloud name: ", process.env.CLOUDINARY_UPLOAD_CLOUD_NAME)
.....my cloudinary cloud name is logged
const cloudinaryResponse = await cloudinary.v2.uploader.upload(filePath, {
cloud_name: process.env.CLOUDINARY_UPLOAD_CLOUD_NAME,
api_secret: process.env.CLOUDINARY_UPLOAD_API_SECRET,
upload_preset: process.env.CLOUDINARY_UPLOAD_PRESET_NAME,
api_key: process.env.CLOUDINARY_UPLOAD_API_KEY,
resource_type: "image",
secure: true
});
for common js files put :
const dotenv = require('dotenv');
dotenv.config();
for mjs files put:
import * as dotenv from "dotenv";
dotenv.config();
in the same js/mjs file that you use:
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.CLOUD_KEY,
api_secret: process.env.CLOUD_SECRET
});
use this
const dotenv = require('dotenv');
dotenv.config();