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.
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 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,
});
I am trying to upload an image with a post request form. In my express.js server I am both using multer and urlencodedParser, because I am passing all the html inputs as a json object through the post request body plus a selection of a file, which I am handeling it with multer.
Here is the HTML code for the form:
<form action="http://localhost:8000/api/addOrgs" method="post">
<div class="form-row">
<!-- OTHER CODE -->
<div class="form-group col-md-6">
<label>Image or Logo</label>
<input type="file" class="form-control" name="image" id="fileToUpload">
</div>
<button class="btn btn-primary"> Add the organization to the system</button>
</div>
</form>
I have set up the multer:
// add image to folder
var multer = require('multer')
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '../public/images/')
},
filename: function (req, file, cb) {
cb(null, file.originalname)
}
})
var upload = multer({ storage: storage })
Here is the express post request handler:
// to add a new organization to the list
app.post('/api/addOrgs', upload.single('imageToUpload'), urlencodedParser, (req, res)=> {
try {
var newobj = req.body;
// want to change name of id in obj
// add image name and delete image field
newobj['img'] = newobj.image;
delete newobj.image;
// read orgs file
let orgs = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../orgs.json')));
//... OTHER CODE
// send respond
res.send('<h1>Successfully added!</h1><br><p>'+JSON.stringify(newobj, null, 4)+'</p></br><b>Now you can go BACK</b>')
// OTHER CODE
} catch (e) {
// ... OTHER CODE
}
});
The problem is that I successfully have the name of the file uploaded in the json object sent through the req.body but it does NOT save the file in the folder. Is the problem coming from the usage of urlencodedParser for the app.post() parameter?
Or do I have to do something in order to save the file inside my app.post() function?
The file's form should have enctype="multipart/form-data" attribute. Try this, and it should work
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.