I'm trying to create a form where it will be a field to upload multiple images, like real estate site.
In Postman, I set the body to form-data and try to post two files to http://localhost:8080/api/files/upload while server is running to check if it is working before implementing the view in React, but getting this error:
Also addition information, I'm using
app.use("/api/files", imageRoutes); // => /api/files/
RangeError [ERR_HTTP_INVALID_STATUS_CODE]: Invalid status code: LIMIT_UNEXPECTED_FILE
at new NodeError (node:internal/errors:393:5)
at ServerResponse.writeHead (node:_http_server:343:11)
at ServerResponse._implicitHeader (node:_http_server:334:8)
at write_ (node:_http_outgoing:867:9)
at ServerResponse.end (node:_http_outgoing:977:5)
at ServerResponse.send (C:\Users\User\Downloads\yedy-react-backend-master\node_modules\express\lib\response.js:232:10)
at ServerResponse.json (C:\Users\User\Downloads\yedy-react-backend-master\node_modules\express\lib\response.js:278:15)
at C:\Users\User\Downloads\yedy-react-backend-master\server.js:35:7
at Layer.handle_error (C:\Users\User\Downloads\yedy-react-backend-master\node_modules\express\lib\router\layer.js:71:5)
at trim_prefix (C:\Users\User\Downloads\yedy-react-backend-master\node_modules\express\lib\router\index.js:326:13)
images-controllers.js
const fs = require("fs")
const uploadImage = (req, res, next) => {
const files = req.files;
if (! files) {
res.status(400).send('Please choose files');
return; }
// convert images into base64 encoding
let imgArray = files.map((file) => {
let img = fs.readFileSync(file.path)
return encode_image = img.toString('base64')
})
let result = imgArray.map((src, index) => { // create object to store data in the collection
let finalImg = {
filename: files[index].originalname,
contentType: files[index].mimetype,
imageBase64: src
}
let newUpload = new UploadModel(finalImg);
return newUpload.save().then(() => {
return {msg: `${
files[index].originalname
} Uploaded Successfully...!`}
}).catch(error => {
if (error) {
if (error.name === 'MongoError' && error.code === 11000) {
return Promise.reject({error: `Duplicate ${
files[index].originalname
}. File Already exists! `});
}
return Promise.reject({
error: error.message || `Cannot Upload ${
files[index].originalname
} Something Missing!`
})
}
})
});
Promise.all(result).then(msg => { // res.json(msg);
res.redirect('/')
}).catch(err => {
res.json(err);
})
}
exports.uploadImage = uploadImage;
multer.js
const multer = require('multer');
// set storage
var storage = multer.diskStorage({
destination : function ( req , file , cb ){
cb(null, 'uploads')
},
filename : function (req, file , cb){
// image.jpg
var ext = file.originalname.substr(file.originalname.lastIndexOf('.'));
cb(null, file.fieldname + '-' + Date.now() + ext)
}
})
module.exports = store = multer({ storage : storage })
image-routes.js
const express = require("express");
const router = express.Router()
const store = require("../middleware/multer")
const imagesController = require("../controllers/images-controllers");
router.post('/upload', store.array('images', 12), imagesController.uploadImage)
module.exports = router;
It looks like you are not sending images properly from the Postman. You should specify the name of images array to be images because you specified that in the Multer middleware.
You should send it like this:
Related
I am trying to write an app using NodeJS and Express which will upload both a user avatar which is a single image (jpeg, png or webp) and 'legalDocs' which is essentially also a single file that can either be an image (jpeg, png or webp) or a pdf file. The avatar needs to be uploaded to ./resources/(userID)/userAvatar and the legal docs to ./resources/(userID)/legalDocs. Along with some other text info that will be posted along with the request as a multipart/form-data.
The userID is only generated after processing the text data posted.
Here's my current non-working code
APP.JS
//ENV
require("dotenv").config();
require("express-async-errors");
//EXPRESS
const express = require("express");
const server = express();
global.__basedir = __dirname;
//routes
const authRouter = require("./routes/authRoutes.js");
server.use(express.json());
server.use(express.urlencoded({extended: true}));
server.use(express.static("./public"));
server.use("/api/v0/auth", authRouter);
const port = process.env.PORT || 3000;
const start = async () => {
try {
server.listen(port, () => {
console.log(`--> Listening on port ${port}...`);
});
} catch (error) {
console.log(error);
console.log("Fatal error!");
}
}
start();
authRoutes.js:
const express = require("express");
const router = express.Router();
const {signupWithEmail} = require("../controllers/auth.js");
router.route("/signup").post(signupWithEmail);
module.exports = router;
../controllers/auth.js:
const multer = require("multer");
const processReq = require("../middleware/upload.js");
const signupWithEmail = async (req, res, next) => {
//Processing to get the ID happens here.
//For simplicity we can pretend like the ID is directly
//posted in a field with the name id.
const id = req.body.id;
console.log("id:");
console.log(id);
await processReq(id)(req, res, next);
res.status(200).send("done!");
}
upload.js
const multer = require("multer");
const errors = require("../errors");
const maxSize = 2 * 1024 * 1024;
const acceptableFileMIMETypes = ["image/jpeg", "image/png", "image/webp", "application/pdf"];
const userAvatarFilter = function (req, file, cb) {
if ((file.fieldname === "userAvatar" || file.fieldname === "legalDocs")
&& acceptableFileMIMETypes.includes(file.mimetype)
&& !(file.fieldname === "userAvatar" && file.mimetype === "application/pdf")) {
if (file.fieldname === "userAvatar") {
cb(null, true);
} else {
cb(null, false);
}
} else {
cb(new errors.BadRequestError("Both files are required, and must be JPEG, PNG, WEBP. Legal documents can also be PDF."));
}
}
const userlegalDocsFilter = function (req, file, cb) {
if ((file.fieldname === "userAvatar" || file.fieldname === "legalDocs")
&& acceptableFileMIMETypes.includes(file.mimetype)
&& !(file.fieldname === "userAvatar" && file.mimetype === "application/pdf")) {
if (file.fieldname === "userAvatar") {
cb(null, false);
} else {
cb(null, true);
}
} else {
cb(new errors.BadRequestError("Both files are required, and must be JPEG, PNG, WEBP. Legal documents can also be PDF."));
}
}
const uploadFunc = (id) => {
return (req, res, next) => {
const userAvatarStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, __basedir + `/resources/${id}/userAvatar`);
},
filename: (req, file, cb) => {
console.log("Uploaded avatar!");
console.log(file.originalname);
console.log(file);
cb(null, file.originalname);
}
});
const legalDocsStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, __basedir + `/resources/${id}/legalDocs`);
},
filename: (req, file, cb) => {
console.log("Uploaded legalDocs!");
console.log(file.originalname);
console.log(file);
cb(null, file.originalname);
}
});
const uploadAvatar = multer({
storage: userAvatarStorage,
limits: {
fileSize: maxSize,
files: 2
},
fileFilter: userAvatarFilter
}).any();
const uploadLegalDocs = multer({
storage: legalDocsStorage,
limits: {
fileSize: maxSize,
files: 2
},
fileFilter: userlegalDocsFilter
}).any();
uploadAvatar(req, res, next);
uploadLegalDocs(req, res, next);
next();
}
}
module.exports = uploadFunc;
The Problem
The first problem with my code is quite obvious, I cannot parse text data from the request unless it had been processed by multer (with the current code, console.logging the id shows that it's undefined), but I need the ID to be passed to multer in order to save the files to the correct directory. It seems like I need some way to process text only and generate the ID, then process the files.
The second problem is that, even if the ID is static - meaning that in ./controllers/auth.js the signupWithEmail function became:
const multer = require("multer");
const processReq = require("../middleware/upload.js");
const signupWithEmail = async (req, res, next) => {
const id = 1337;
console.log("id:");
console.log(id);
await processReq(id)(req, res, next);
res.status(200).send("done!");
}
I this is the output I get on the console when I try to post:
id:
1337
Uploaded legalDocs!
cours-analyse-numerique-ch1-21_22.pdf
{
fieldname: 'legalDocs',
originalname: 'cours-analyse-numerique-ch1-21_22.pdf',
encoding: '7bit',
mimetype: 'application/pdf'
}
node:_http_outgoing:771
throw new ERR_HTTP_HEADERS_SENT('remove');
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot remove headers after they are sent to the client
at new NodeError (node:internal/errors:400:5)
at ServerResponse.removeHeader (node:_http_outgoing:771:11)
at write (C:\Users\der_u\source\repos\ATEEK\node_modules\finalhandler\index.js:282:9)
at AsyncResource.runInAsyncScope (node:async_hooks:204:9)
at listener (C:\Users\der_u\source\repos\ATEEK\node_modules\on-finished\index.js:170:15)
at onFinish (C:\Users\der_u\source\repos\ATEEK\node_modules\on-finished\index.js:101:5)
at callback (C:\Users\der_u\source\repos\ATEEK\node_modules\ee-first\index.js:55:10)
at IncomingMessage.onevent (C:\Users\der_u\source\repos\ATEEK\node_modules\ee-first\index.js:93:5)
at IncomingMessage.emit (node:events:513:28)
at endReadableNT (node:internal/streams/readable:1359:12) {
code: 'ERR_HTTP_HEADERS_SENT'
}
and only 'legalDocs' get uploaded.
Is there any way to solve these problems without using a temporary directory to save all the files then move them once the ID had been generated? And what does the [ERR_HTTP_HEADERS_SENT] error mean?
Not able to upload image in MongoDB database using NodeJS
Middleware fieldName and key name is also same still not working. no solution on internet
server.js
import cookieParser from "cookie-parser";
import multer from "multer";
export const app = express();
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))
let upload = multer()
app.use(upload.array())
app.use("/",routLog,router)
app.listen(process.env.PORT,()=>{
db()
console.log(`Server is Runing on http://localhost:${process.env.PORT}`)
})
blogroute.js
import { fileUploder } from "../middelware/fileUploder.js";
export const blogRouter = Router()
blogRouter.post('/:user/create-post', fileUploder.single("file"), blogsController.createPos
This middleware file fileUplad.js which is use to store in upload folder and then from that will store in MongoDB in buffer ferment
import { randomBytes } from "crypto";
import multer from "multer";
import path from "path";
const storage = multer.diskStorage({
destination: './upload',
filename: async (req, file, callback) => {
return await randomBytes(16, (err, buf) => {
if (err) {
return reject(err);
}
const filename = buf.toString("hex") + path.extname(file.originalname);
const fileInfo = {
filename: filename,
bucketName: "upload"
};
resolve(fileInfo);
})
}
})
export const fileUploder = multer({
storage: storage,
limits: { fileSize: 2000 },
fileFilter: (req, file, callback) => {
allowedFileTypes = /jpeg|jpg|png|gif/;
if (file.mimetype == 'jpg' || file.mimetype == 'jpeg' || file.mimetype == 'png', file.mimetype == 'gif') {
return cb(null, true);
} else {
cb("Invalid file type. Only JPEG, PNG and GIF file are allowed.")
}
}
})
MulterError: Unexpected field
at wrappedFileFilter (/mnt/ssd/BlogProject/backendapi/node_modules/multer/index.js:40:19)
at Multipart.<anonymous> (/mnt/ssd/BlogProject/backendapi/node_modules/multer/lib/make-middleware.js:107:7)
at Multipart.emit (node:events:527:28)
at HeaderParser.cb (/mnt/ssd/BlogProject/backendapi/node_modules/busboy/lib/types/multipart.js:358:14)
at HeaderParser.push (/mnt/ssd/BlogProject/backendapi/node_modules/busboy/lib/types/multipart.js:162:20)
at SBMH.ssCb [as _cb] (/mnt/ssd/BlogProject/backendapi/node_modules/busboy/lib/types/multipart.js:394:37)
at feed (/mnt/ssd/BlogProject/backendapi/node_modules/streamsearch/lib/sbmh.js:248:10)
at SBMH.push (/mnt/ssd/BlogProject/backendapi/node_modules/streamsearch/lib/sbmh.js:104:16)
at Multipart._write (/mnt/ssd/BlogProject/backendapi/node_modules/busboy/lib/types/multipart.js:567:19)
at writeOrBuffer (node:internal/streams/writable:389:12)```
This is simple example :
let multer = require('multer');
let multerImage = multer().single('file');
function uploadAvatar(req, res) {
let phone = data.phoneNumber;
multerImage(req, res, () => {
let file = req.file;
if (isUndefined(file))
return Json.builder(Response.HTTP_BAD_REQUEST);
let {
fileUrl
} = File.validationAndWriteFile(file.buffer, Util.getFileFormat(file.originalname));
Update.img(phone, fileUrl, result => {
if (!result)
return Json.builder(Response.HTTP_BAD_REQUEST);
Json.builder(Response.HTTP_OK);
});
});
}
I'm using NodeJS w/ Express to create a web app that records your audio using the VMSG library and posts the BLOB audio to my file system using HTTP Requests and multer. It also adds that instance of a recording into a MongoDB database.
I'm having an issue with the fetch command. It's not working unless I put an alert right after the fetch. The way I have it set up is that I have my main express app (index.js), and a router to the /recordingsDirectory (recordings.js) which is the endpoint for processing the posts. My main index HTML page uses Handlebars and uses a separate JS script (recorder.js) to 1) use the VMSG library and 2) fetch a POST to the /recordingsDirectory once someone submits the audio file w/ the name and the AudioBlob present. This is where I'm stuck. I can fetch in recorder.js with an alert line after the fetch, but I can't have the fetch on the end of the else if block by itself. I'd like to do it without this since the alert is ugly. A solution I've tried is that I tried to make the onsubmit function async and await fetch since I thought maybe it's waiting for a promise but that didn't work.
Here are the files. I commented CRITICAL and SUPER CRITICAL to the lines of code that you should check out and I think where the issues lie:
index.js
const express = require('express')
const handlebars = require('express-handlebars')
const path = require('path')
const XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest
const xhr = new XMLHttpRequest()
const db = require('./db')
const app = express()
const PORT = process.env.PORT || 8000
app.set('view engine', 'hbs')
app.engine('hbs', handlebars({
layoutsDir: path.join(__dirname, 'views', 'layouts'),
extname: 'hbs',
defaultLayout: 'index',
partialsDir: path.join(__dirname, 'views', 'partials'),
}))
app.use(express.json())
app.use(express.urlencoded({extended: false}))
app.use((err, req, res, next) => {
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
return res.status(400).send({ status: 404, message: err.message })
}
next()
})
app.get('/', (req, res) => {
res.render('main', {
title: 'Main Page'
})
})
app.get('/recordings', (req, res) => {
var database = db.get().db('AudioJungle')
database.collection('recordings').find().sort({ "date": -1 }).toArray(function(err, docs) {
res.render('recordings', {
title: 'Recordings',
recordings: docs
})
})
})
// CRITICAL
app.use('/recordingsDirectory', require('./recordings/recordings'))
app.use(express.static('public'))
app.use('/scripts', express.static(path.join(__dirname, 'node_modules', 'vmsg')))
db.connect(function(err) {
if (err) {
console.log('Unable to connect to Mongo.')
process.exit(1)
} else {
app.listen(PORT, () => console.log(`Listening on Port: ${PORT}`))
}
})
process.on('SIGINT', function() {
db.close(function () {
console.log('Disconnected on app termination');
process.exit(0);
});
});
app.use((req, res, next) => {
res.status(404).send({
status: 404,
error: 'Not found'
})
})
recordings.js (Aka the /recordingsDirectory endpoint for a fetch POST)
const express = require('express')
const router = express.Router()
const multer = require('multer')
const fs = require('fs-extra')
const db = require('../db')
const { ObjectId } = require('bson')
const moment = require('moment')
const upload = multer({
storage: multer.diskStorage({
destination: (req, file, callback) => {
let path = './public/uploads'
fs.mkdirsSync(path)
callback(null, path)
},
filename: (req, file, callback) => {
createRecording(req).then((id) => {
var file_name = id + '.mp3'
callback(null, file_name)
})
}
})
})
var type = upload.single('audio-file')
// CRITICAL
router.post('/', type, (req, res) => {
console.log('made it')
res.status(200)
res.send('OK')
})
router.delete('/delete', (req, res) => {
deleteRecording(req.body._id).then((dbResponse) => {
if (dbResponse == null || dbResponse == undefined) {
res.status(400).json({ msg: 'ID already deleted' })
} else {
res.status(200)
}
})
})
router.get('/', (req, res) => {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
recordings.findOne({"_id": ObjectId(req.query.id)}, function(err, result) {
if (err) throw err
if (result == null || result == undefined) {
return res.status(400).json({
status: 404,
error: 'Recording no longer in the database'
})
}
res.status(200)
res.json({
name: result.name,
date: result.date
})
})
})
async function createRecording(req) {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
var audioObject = {
name: req.body.name,
date: moment().format('MMMM Do YYYY, h:mm:ss a')
}
var dbResponse = await recordings.insertOne(audioObject)
return dbResponse.insertedId
}
async function deleteRecording(id) {
var database = db.get().db('AudioJungle')
var recordings = database.collection('recordings')
var audioToDelete = {
_id: ObjectId(id)
}
var deleteResult = await recordings.deleteOne(audioToDelete)
return deleteResult
}
module.exports = router
And below is the Script the audio and name and tries to Fetch (where I need the alert for it to actually process into the /recordingsdirectory)
recorder.js
import { record } from "/scripts/vmsg.js";
let recordButton = document.getElementById("record");
var blobObj = null
recordButton.onclick = function() {
record({wasmURL: "/scripts/vmsg.wasm"}).then(blob => {
blobObj = blob
var tag = document.createElement("p")
tag.id="finishedRecording"
var text = document.createTextNode("Audio File Recorded")
tag.appendChild(text)
var element = document.getElementById("box")
element.appendChild(tag)
document.getElementById('box').appendChild(a)
})
}
let form = document.getElementById('mp3Form');
form.addEventListener("submit", submitAudio)
function submitAudio() {
var fileName = form.elements[0].value
if (fileName == "") {
alert('Please enter a name for your file')
} else if (blobObj != null) {
// CRITICAL
// SUPER CRITICAL WHERE FETCH DOESN'T WORK UNLESS I PUT AN ALERT AT THE END
const formData = new FormData()
formData.append('name', fileName)
formData.append('audio-file', blobObj)
const options = {
method: 'POST',
body: formData
}
fetch('/recordingsDirectory', options);
// If I comment an alert here, /recordingsDirectory will process the post since it console.logs 'made it'
} else {
alert('Record some Audio to upload')
}
}
Here's my file system.
Also, I'd like to mention that the fetch works properly on my Windows PC without having to add the alert, but it doesn't work without the alert on my macbook. If any one figures out a fix or an error in how I'm doing things to allow this please let me know. I've been stuck on this problem for a day now. Thanks a bunch!
I have a code for upload images in nodejs using multer and sharp. But i want to ask how to set dynamic array name at line 19? I want to set that array name from the nodejs routes, because my field name in mongodb collections is different in every collection.
This is my middleware upload code:
const multer = require("multer");
const sharp = require("sharp");
const multerStorage = multer.memoryStorage();
const multerFilter = (req, file, cb) => {
if (file.mimetype.startsWith("image")) {
cb(null, true);
} else {
cb("Please upload only images.", false);
}
};
const upload = multer({
storage: multerStorage,
fileFilter: multerFilter
});
const uploadFiles = upload.array("images", 10); //Change this 'images' name from route
const uploadImages = (req, res, next) => {
uploadFiles(req, res, err => {
if (err instanceof multer.MulterError) {
if (err.code === "LIMIT_UNEXPECTED_FILE") {
return res.send("Too many files to upload.");
}
} else if (err) {
return res.send(err);
}
next();
});
};
const resizeImages = async (req, res, next) => {
if (!req.files) return next();
req.body.images = [];
await Promise.all(
req.files.map(async file => {
const filename = file.originalname.replace(/\..+$/, "");
const newFilename = `cariboss-${filename}-${Date.now()}.jpeg`;
await sharp(file.buffer)
.resize(640, 320)
.toFormat("jpeg")
.jpeg({ quality: 90 })
.toFile(`uploads/${newFilename}`);
req.body.images.push(newFilename);
})
);
next();
};
const getResult = async (req, res) => {
if (req.body.images.length <= 0) {
return res.send(`You must select at least 1 image.`);
}
const images = req.body.images
.map(image => "" + image + "")
.join("");
return res.send(`Images were uploaded:${images}`);
};
module.exports = {
uploadImages: uploadImages,
resizeImages: resizeImages,
getResult: getResult
};
My routes code:
module.exports = app => {
const articles = require("../controllers/articles.controller.js");
const upload = require("../middlewares/upload");
var router = require("express").Router();
router.post("/", upload.uploadImages, upload.resizeImages, upload.getResult, articles.create);
app.use("/api/article", verifyToken, router);
};
I have already search the solution on internet, but there is nothing. Thanks for your help sir.
The function I wrote on the node JS server does not delete the image. He can't find the picture. But when you type the name of the picture, it deletes it from the folder.
I get this error:
failed to delete local image:Error: ENOENT: no such file or directory, unlink 'C:\Users\mary\Desktop\Node.js-UploadFiles-RestAPIs\uploads\undefined'
NodeJS : File Controller
const uploadFolder = __basedir + '/uploads/';
const fs = require('fs');
exports.deleteFile=(req, res)=> {
let filename = req.params.filename;
fs.unlink(uploadFolder + filename, (err) => {
if (err) {
console.log("failed to delete local image:"+err);
} else {
console.log('successfully deleted local image');
}
});
}
NodeJS : File Router
let express = require('express');
let router = express.Router();
let fileWorker = require('../controllers/file.controller.js');
router.delete('/api/file/delete', fileWorker.deleteFile);
module.exports = router;
Service.ts :
delete(file : File):Observable<HttpEvent<{}>>{
// const formdata: FormData = new FormData();
// formdata.append('file' , file);
const req = new HttpRequest('DELETE', 'http://localhost:8000/api/file/delete',file, {
responseType: 'text'
});
return this.http.request(req);
}
component.ts :
x:any[];
deleteImage(y){
// #ts-ignore
this.x=getFile();
this.imageServis.delete(y).subscribe(event => {
console.log("Here");
});
}
See the undefined in your error :
'C:\Users\mary\Desktop\Node.js-UploadFiles-RestAPIs\uploads\undefined'
Filename is not define in your function :
exports.deleteFile=(req, res)=> {
let filename = req.params.filename;
fs.unlink(uploadFolder + filename, (err) => {
You use req.params but it seems that you send the filename in the body :
const req = new HttpRequest('DELETE', 'http://localhost:8000/api/file/delete',file, {
responseType: 'text'
});
If you want to use req.params do the following :
const req = new HttpRequest('DELETE', 'http://localhost:8000/api/file/delete'+file,null, {
responseType: 'text'
});
router.delete('/api/file/delete/:filename', fileWorker.deleteFile);