Accepting file and text inputs at the same time with Node.js - javascript

I have this form with two types of inputs, file and text. And I trying to save the image file (using Multer) in a local dir. and the text (using mongoose) in the database.
<form
action="publish"
method="POST"
enctype="multipart/form-data"
>
<input type="text" name="textinput" placeholder="Title" />
<input type="file" name="imginput" />
</form>
When i submit the form and try to handle both of the inputs (file & text), it prevents me from accessing the text because of 'enctype="multipart/form-data". I removed the enctype thing, and this time i couldn't access the image file.
async function addText(userInput) {
const text = new Text({ text: userInput });
const result = await text.save();
console.log(result);
};
app.post("/publish", (req, res) => {
// init upload
const upload = multer({
storage: storage,
limits: { fileSize: 1000000 },
fileFilter: (req, file, cb) => {
checkFileType(file, cb);
},
}).single("imginput");
upload(req, res, (err) => {
if (err) {
res.send(err);
} else {
if (req.file == undefined) {
res.send("no file selected");
} else {
res.send("file uploaded");
}
}
});
addText(req.body.textinput);
};
Is there is a way that i can handle both of the inputs?

Related

Unexpected behaviour of Multer and Express when field name is wrong

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.

How do I continuously update the progress of a file upload on the frontend in NodeJS/Express

I have a form for a multi-file upload on an EJS frontend and I am looking for a way of updating a span with an id of "progressSpan" to show the current progress in % of the file that is being uploaded.
The Frontend form looks like the following:
<form method="POST" action="/upload" enctype="multipart/form-data" id="uploadForm">
<label>Select Files</label>
<input type="file" name="fileUpload" id="realFileinput" multiple>
<button type="submit" id="submit-upload-btn" class="uploadFormBtns">Start Uploading</button>
<span id="progressSpan>0%</span>
</form>
The function responsible for handling the upload on the backend has been written using multer, altough if something like express-fileupload would be suited better for the progress tracking, then changing it would'nt be a big deal. In the codesnippet below, fileData is an array of JSON objects containing a name and a fileSize, which is handeled by an in this case irrelevant function in the frontend. Depending on the outcome (success/error) it prints the index.ejs file with the according message
router.post('/upload', (req, res) => {
upload(req, res, (err) => {
if (err) {
//render error
res.render('index', { msg: err, fileData });
} else {
req.files.forEach(file => {
fileData.push({
name: file.filename,
fileSize: convertDataUnit(dir, file.filename)
});
});
res.render("index", { msg: "Upload successful!", fileData });
}
});
});

Nodejs POST a FILE

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

How to Prevent an Image from Saving When Submitting Form Using Multer

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.

Uploading multiple files with Multer

I'm trying to upload multiple images using Multer. It all works as expected except that only one file is being uploaded (the last file selected).
HTML
<form class='new-project' action='/projects' method='POST' enctype="multipart/form-data">
<label for='file'>Select your image:</label>
<input type='file' multiple='multiple' accept='image/*' name='uploadedImages' id='file' />
<span class='hint'>Supported files: jpg, jpeg, png.</span>
<button type='submit'>upload</button>
</form>
JS
//Define where project photos will be stored
var storage = multer.diskStorage({
destination: function (request, file, callback) {
callback(null, './public/uploads');
},
filename: function (request, file, callback) {
console.log(file);
callback(null, file.originalname)
}
});
// Function to upload project images
var upload = multer({storage: storage}).any('uploadedImages');
// add new photos to the DB
app.post('/projects', function(req, res){
upload(req, res, function(err){
if(err){
console.log(err);
return;
}
console.log(req.files);
res.end('Your files uploaded.');
console.log('Yep yep!');
});
});
I get the feeling I'm missing something obvious...
EDIT
Code I tried following Syed's help:
HTML
<label for='file'>Select your image:</label>
<input type='file' accept='image/*' name='uploadedImages' multiple/>
<span class='hint'>Supported files: jpg, jpeg, png.</span>
<input type="submit" value="uploading_img">
JS
multer = require('multer'),
var upload = multer();
app.post('/projects', upload.array('uploadedImages', 10), function(req, res, err) {
if (err) {
console.log('error');
console.log(err);
}
var file = req.files;
res.end();
console.log(req.files);
});
Uploading multiple files with Multer
NodeJs Code
Set require files and Storage
const express = require('express');
const multer = require('multer');
const path = require('path');
const app = express();
const port = 3000
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, path.join(__dirname, './images/'))
},
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + file.originalname.match(/\..*$/)[0])
}
});
Set upload file limit or validataion
const multi_upload = multer({
storage,
limits: { fileSize: 1 * 1024 * 1024 }, // 1MB
fileFilter: (req, file, cb) => {
if (file.mimetype == "image/png" || file.mimetype == "image/jpg" || file.mimetype == "image/jpeg") {
cb(null, true);
} else {
cb(null, false);
const err = new Error('Only .png, .jpg and .jpeg format allowed!')
err.name = 'ExtensionError'
return cb(err);
}
},
}).array('uploadedImages', 2)
Create the main route for uploading
app.post('/projects', (req, res) => {
multi_upload(req, res, function (err) {
if (err instanceof multer.MulterError) {
// A Multer error occurred when uploading.
res.status(500).send({ error: { message: `Multer uploading error: ${err.message}` } }).end();
return;
} else if (err) {
// An unknown error occurred when uploading.
if (err.name == 'ExtensionError') {
res.status(413).send({ error: { message: err.message } }).end();
} else {
res.status(500).send({ error: { message: `unknown uploading error: ${err.message}` } }).end();
}
return;
}
// Everything went fine.
// show file `req.files`
// show body `req.body`
res.status(200).end('Your files uploaded.');
})
});
Listen port
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
});
HTML code
<form id="form_el" class='new-project' action='/projects' method='POST' enctype="multipart/form-data">
<label for='file'>Select your image:</label>
<input type='file' multiple='multiple' accept='image/*' name='uploadedImages' id='file' />
<span class='hint'>Supported files: jpg, jpeg, png.</span>
<button type='submit'>upload</button>
</form>
JAVASCRIPT CODE
form_el.addEventListener('submit', async function (e) {
const files = e.target.uploadedImages.files;
if (files.length != 0) {
for (const single_file of files) {
data.append('uploadedImages', single_file)
}
}
});
const submit_data_fetch = await fetch('/projects', {
method: 'POST',
body: data
});
Here you go for this example:
var multer = require('multer');
var upload = multer();
router.post('/projects', upload.array('uploadedImages', 10), function(req, res) {
var file = req.files;
res.end();
});
<form action="/projects" method="post" enctype="multipart/form-data">
<input type="file" name="uploadedImages" value="uploading_img" multiple>
<input type="submit" value="uploading_img">
</form>
Visit for more info about Multer.
My guess is that for each file that you want to upload, you reclick:
<input type='file' multiple='multiple' accept='image/*' name='uploadedImages' id='file' />
If you do this, then only the last file selected will be uploaded, as you overwrite the previous selected files.
To upload multiple files, you have to select them all at once in the file picker.

Categories

Resources