good evening.
I'm trying to create a POST request with a file and some data on a REST API I'm building using NodeJS.
If not clear, my goal to this feature of the API is to save a register of a picture, so I'd like to send the picture file, the picture name and it's number on the same request.
I'm currently using Jest / supertest for testing and to test this specific functionality, I've tried the following:
const response = await request(app)
.post("/uploads/pics")
.field("name", "PicureName")
.field("number", "PictureNumber")
.attach("file", picture);
I've read this from https://visionmedia.github.io/superagent/#multipart-requests
My problem is that I can't get the values of name and number on my request on my controller, so I can't use them to save the object.
I've tried many ways, such as:
req.body.name
req.name
req.field.name
req.query.name
but none of these worked for me.
I also tried printing the whole request, however I couldn't find anything related to name, number or field there.
Does anyone can tell what I'm doing wrong ?
You should use https://github.com/expressjs/multer middleware for handling file upload. Then, req.body will hold the text fields, if there were any.
E.g.
index.js:
const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });
app.post('/uploads/pics', upload.single('file'), (req, res) => {
console.log(req.body);
console.log(req.file);
res.sendStatus(200);
});
module.exports = app;
index.test.js:
const request = require('supertest');
const app = require('./');
const path = require('path');
const { expect } = require('chai');
describe('62862866', () => {
it('should pass', async () => {
const picture = path.resolve(__dirname, './test.jpg');
const response = await request(app)
.post('/uploads/pics')
.field('name', 'PicureName')
.field('number', 'PictureNumber')
.attach('file', picture);
expect(response.status).to.be.eq(200);
});
});
integration test result:
62862866
[Object: null prototype] { name: 'PicureName', number: 'PictureNumber' }
{ fieldname: 'file',
originalname: 'test.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: 'uploads/',
filename: '181b96eb9044aac5d50c8c1e3159a120',
path: 'uploads/181b96eb9044aac5d50c8c1e3159a120',
size: 0 }
✓ should pass (84ms)
1 passing (103ms)
Related
I have a express backend I am currently building. I created a router and controller system that process each and every request that comes through the server. I am currently trying to save image files that are sent with the profile as profile picture. I am currently integrating multer into my backend. I have the multer destination set but I am not sure how to grab the request that passing through in order to actually save the file before the request gets to the controller.
my app.js:
...
// app.use(logger('dev'))
app.use(cors());
app.use(cookieParser())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended : true }));
app.use(session({
secret: 'helloworld',
cookie: {maxAge: 60000},
saveUninitialized: false,
}))
app.use(morgan('dev'))
app.use(fileUpload());
app.use('/api/auth', AuthenticationRouter)
app.use('/api/user', UserRouter)
app.use('/api/profile', ProfileRouter)
app.use('/api/follow', FollowRouter)
app.use('/api/post', PostRouter)
app.use('/api/like', LikeRouter)
app.use('/api/edit', EditRouter)
app.use('/api/download', DownloadRouter)
app.use('/api/comment', CommentRouter)
the following is the routers page. this is the point where i am trying to grab the request and save the file that was sent with the request. I am not sure how to inject and grab the request object at this point
routers:
const multer = require('multer')
const upload = multer({dest:'../client/static/images/ProfilePictures'})
const router = require("express").Router()
const profileController = require("../../controllers/UserControllers/ProfileController.js");
router
.route('/')
.get(profileController.get)
.post(upload,single(''), profileController.post)
.delete(profileController.delete)
.patch(profileController.patch)
module.exports = router;
controller:
const Profile = require("../../models/UserModels/Profiles.js")
const User = require('../../models/UserModels/Users.js')
const profileController = {
post:(req, res) => {
console.log(req.files.profile_pic.name)
let body = req.body
let file = req.files.profile_pic
Profile.create({
profile_pic: file.name,
f_name: body.f_name,
l_name: body.l_name,
bio: body.bio,
location: body.location,
sm_facebook: body.sm_facebook,
sm_instagram: body.sm_instagram,
sm_twitter: body.sm_twitter,
sm_website: body.sm_website,
followers: body.followers,
following: body.following,
photos: body.photos,
downloads: body.downloads,
edits: body.edits,
userId: body.userId
})
.then((data) => {
res.send(data).status(200)
})
.catch((err) => {
console.error(err)
res.send(err).status(400)
})
},
You have a typo. Instead of upload,single(''), it should be upload.single('').
Multer will automatically parse the image and populate the req.file with the image upload data. However, in your controller, you are trying to access req.files. So, if you will upload multiple files, I would suggest that you switch from upload.singe('') to upload.any(), since upload.any() will populate req.files and not req.file.
You can change your router like this:
router
.route('/')
.get(profileController.get)
.post(upload.any(), profileController.post)
.delete(profileController.delete)
.patch(profileController.patch)
And you can change your contoller like this:
const profileController = {
post:(req, res) => {
console.log(req.files)
let file = req.files[0];
...
},
}
I'm creating a scholarship website where users can submit an application. I've created a form and wired it up so that when it's submitted, the form data populates an email which is sent to the person who handles the applicant data, and uploaded PDFs are attached to it. This error keeps popping up on submit though, even though I'm pretty sure I should be selecting the files correctly. During other trials I selected them incorrectly and the email was sent, but while the text data was all correct, the files would be corrupted or something. The error makes it sound like the files are non-existent, but I don't know why that would be.
I had it working at one point by sending the data through regular form submission, but I need to be able to do a .then() statement when it's finished sending the email, and I don't know how to do that with regular form submission. If you can tell me how that works, I'll also accept it as an answer.
Here's my relevant code:
Fetch Request:
handleSubmit = async (e) => {
e.preventDefault()
try {
const formData = new FormData()
for (let value in this.state.formValues ) {
formData.append(value, this.state.formValues[value])
}
formData.append('essay', document.getElementById('essay').value)
formData.append('recLetter1', document.getElementById('recLetter1').value)
formData.append('recLetter2', document.getElementById('recLetter2').value)
await fetch('/', {
method: 'POST',
body: formData
})} catch (err) {
console.log(err)
}
}
Server Side POST handling:
const express = require('express')
const sendMail = require('./mail')
const multer = require('multer')
const upload = multer({dest: './uploads/'})
const app = express()
const PORT = process.env.PORT || 4000
const cpUpload = upload.fields([{name: 'essay', maxCount: 1}, {name: 'recLetter1', maxCount: 1}, {name: 'recLetter2', maxCount: 1}])
const path = require('path')
const cors = require('cors')
app.use(express.urlencoded({
extended: false
}))
app.use(express.json())
app.use(cors())
app.use(express.static(path.join(__dirname, 'client/build')))
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname, 'client/build/index.html'))
})
app.post('/', cpUpload, (req, res) => {
sendMail(req.files['essay'][0], req.files['recLetter1'][0], req.files['recLetter2'][0], req.body, (err) => {
if(err) {
res.status(500).json({message: 'Internal Error'})
}
})
})
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`)
})
sendMail Function:
const sendMail = (file1, file2, file3, info) => {
const mailOptions = {
from: process.env.GMAIL_USER_NAME,
to: process.env.GMAIL_USER_NAME,
subject: `New Applicant, ${info.studentFirstName} ${info.studentMI} ${info.studentLastName}`,
text:
`New Applicant - \n
*Name*: ${info.studentFirstName} ${info.studentMI} ${info.studentLastName}
*Phone Number*: ${info.studentPhone}
*Date of Birth*: ${info.studentDOB}
*Address*: ${info.studentAddress} ${[info.studentAddress2] ?
info.studentAddress2 : ''}
${info.studentCity}, ${info.studentState}, ${info.studentZip}
// (There's more but I'm sure you get the idea)
attachments: [
{
filename: `${info.studentFirstName}${info.studentLastName}essay.pdf`,
content: file1,
contentType: 'application/pdf'
},
{
filename: `${info.studentFirstName}${info.studentLastName}recLetter1.pdf`,
content: file2,
contentType: 'application/pdf'
},
{
filename: `${info.studentFirstName}${info.studentLastName}recLetter2.pdf`,
content: file3,
contentType: 'application/pdf'
},
]
}
The Error I keep getting:
TypeError: Cannot read property '0' of undefined
at C:\Users\brand\OneDrive\Documents\GitHub\ShatteredCeilingWebsite\scweb\index.js:24:32
at Layer.handle [as handle_request] (C:\Users\brand\OneDrive\Documents\GitHub\ShatteredCeilingWebsite\scweb\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Users\brand\OneDrive\Documents\GitHub\ShatteredCeilingWebsite\scweb\node_modules\express\lib\router\route.js:137:13)
at Array.<anonymous> (C:\Users\brand\OneDrive\Documents\GitHub\ShatteredCeilingWebsite\scweb\node_modules\multer\lib\make-middleware.js:53:37)
at listener (C:\Users\brand\OneDrive\Documents\GitHub\ShatteredCeilingWebsite\scweb\node_modules\on-finished\index.js:169:15)
at onFinish (C:\Users\brand\OneDrive\Documents\GitHub\ShatteredCeilingWebsite\scweb\node_modules\on-finished\index.js:100:5)
at callback (C:\Users\brand\OneDrive\Documents\GitHub\ShatteredCeilingWebsite\scweb\node_modules\ee-first\index.js:55:10)
at IncomingMessage.onevent (C:\Users\brand\OneDrive\Documents\GitHub\ShatteredCeilingWebsite\scweb\node_modules\ee-first\index.js:93:5)
at IncomingMessage.emit (events.js:388:22)
at endReadableNT (internal/streams/readable.js:1336:12)
I think that's all I need to show, but let me know if I missed anything crucial. I've tried setting the Content-Type to both multipart/form-data and undefined, I tried setting the Accept header to '/', I've tried all kinds of combinations of selecting the req.files in the post handler, (That's how the email sometimes goes through with corrupted files), I don't know what to do anymore. Can someone please help me?
Figured it out. I was using document.getElementById('file').value when I should've been using document.getElementById('file').files[0]
I have uploaded the file in my backend filesystem using multer
My server is node and client is react.
I'm having trouble downloading and displaying the saved file on the client react
Whenever I do res.download(file) it just throws an error as connection refused on client side.
My code is as follows:
UserToUploadMapping.js
const mongoose = require("mongoose");
const UserToUploadMapping = new mongoose.Schema({
userId: {
type:String,
required:true
},
file: {
type: Object,
required: true,
},
date: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("UserToUploadMapping", UserToUploadMapping);
uploadVideo.js
const router = require("express").Router();
const multer = require('multer');
const UserToUploadMapping = require('../models/UserToUploadMapping')
let nameFile = ''
const storage = multer.diskStorage({
destination:'./Videos',
filename:(req,file,cb) => {
console.log(file)
nameFile = file.originalname + " "+ Date.now()
cb(null, nameFile)
}
})
const upload = multer({storage:storage})
router.post('/upload', upload.single('video'), async (req,res,next) => {
console.log("object")
const saveMapping = new UserToUploadMapping({
userId:'123',
file:req.file,
})
await saveMapping.save()
res.send("Video uploaded")
})
router.get('/download', async(req,res,next) => {
const x = await UserToUploadMapping.find()
// res.send(x)
res.download(x[0].path)
})
module.exports = router;
CLIENT
const fetchVideo = async () => {
const resp = await axios.get(
"http://localhost:5000/api/user/video/download"
);
console.log(resp)
};
return (
<>
<NavContainer />
<div className={classes.Post}>
<Input
type="file"
onChange={(e) => uploadVideos(e.target.files)}
accept="video/mp4"
/>
{/* <Button onClick={(e) => submitHandler(e)}>Upload</Button> */}
<video></video>
</div>
</>
);
Error
There is a few problems within the uploadVideo.js file :
to get the path from the data, you need to use x[0].file.path
(based on how you save the file in the database)
const saveMapping = new UserToUploadMapping({
userId:'123',
file:req.file,
})
to avoid problems about where the file uploadVideo.js is and where we run the application, you should use an absolute path when saving files in the system.
(small problem) your filename function will give filenames like this video.mp4 1622180824748. I think this is better "video-1622181268053.mp4" (we have the correct file extension)
You can refer to this code
const router = require("express").Router();
const multer = require('multer');
const UserToUploadMapping = require('../models/UserToUploadMapping')
const path = require('path');
const uploadFolder = path.join(__dirname, "Videos"); // use a variable to hold the value of upload folder
const storage = multer.diskStorage({
destination: uploadFolder, // use it when upload
filename: (req, file, cb) => {
// nameFile = file.originalname + " "+ Date.now() // --> give "video.mp4 1622180824748"
let [filename, extension] = file.originalname.split('.');
let nameFile = filename + "-" + Date.now() + "." + extension; // --> give "video-1622181268053.mp4"
cb(null, nameFile)
}
})
const upload = multer({ storage: storage })
router.post('/upload', upload.single('video'), async (req, res, next) => {
const saveMapping = new UserToUploadMapping({
userId: '123',
file: req.file,
})
await saveMapping.save()
res.send("Video uploaded")
})
router.get('/download', async (req, res, next) => {
const video = await UserToUploadMapping.find({});
res.download(video[0].file.path); // video[0].file.path is the absolute path to the file
})
module.exports = router;
Your code indicates you are handling large files (videos). I would strongly recommend looking at separation of concerns, handling this as part of your other business logic is not recommended based on my experience. This can e.g. complicate firewall rules and DDOS protection when that is needed in the future.
As a minimum, move upload and download into its own server, e.g. 'files.yourappnamehere.com' so that you can handle the specifics separately from your business logic api.
If you run in the public cloud, I would strongly recommend looking at reusing blob upload/download functionality, letting your clients upload directly to blob storage and also handling downloads directly from blob storage, e.g. in Azure, AWS or GCP.
This will save you a lot of the implementation details of handling (very) large files, and also give "free" extensibility options such as events on file upload completion.
You are running 2 apps Frontend and Backend with difference ports (3000, 5000) so browsers block cross domain requests. On Express you must enable CORS to allow request from FrontEnd Url (http://localhost:3000).
For the download route, try using window.location functions instead of using Axios.
It looks like you might have a typo in your get handler... you're referencing an element called 'path', but that's not declared in your schema
router.get('/download', async(req,res,next) => {
const x = await UserToUploadMapping.find()
// res.send(x)
res.download(x[0].path)//<-Path Doesn't seem to be in the schema
})
Since you don't have a try/catch in that function, the resulting error could be bringing down your server, making it unavailable
You might also want to take a look at this for more detail on How to download files using axios
Hello sorry for my english, i have a little problem. i try to upload many images but in back side i have just one image, (i use React express formidable cloudinary) here is my code front :
const [arrayFiles, setArrayFiles] = useState([]);
const handleFiles = (e) => {
let arrayUpload = [...arrayFiles];
arrayUpload.push(e.target.files[0]);
setArrayFiles(arrayUpload);
};
const handleSubmit = async (e) => {
arrayFiles.forEach((file) => {
formData.append("image", file);
});
const response = await axios.post(
"http://localhost:3100/offer/publish",
formData
);
here is my code back but req.files => just one image
my page route :
router.post("/offer/publish", async (req, res) => {
console.log(req.files);
const result = await cloudinary.uploader.upload(req.files.image.path, {
folder: `api/leboncoin/offers/${newOffer._id}`, // _id vient de la création du newOffer au dessus
public_id: "preview",
cloud_name: process.env.CLOUDINARY_NAME,
});
my page index.js:
page index.js :
const express = require("express");
const formidable = require("express-formidable");
const mongoose = require("mongoose");
const cloudinary = require("cloudinary").v2;
const cors = require("cors");
const app = express();
app.use(cors());
app.use(formidable({ multiples: true }));
You only get one file in req.file as you've set your multer.single
Using multer
There are 3 ways you can handle multiple file upload, each with a slightly different taste.
Assume you have a base multer
const storage = multer.diskStorage({
destination: "public/data/",
filename: function(req, file, cb){
// You may change this to however you want, only affect your file name
crypto.randomBytes(20, (err, buf) => {
cb(null, buf.toString("hex") + path.extname(file.originalname))
})
}
});
const upload = multer({ storage: storage });
Use .any()
Accepts all files that comes over the wire. An array of files will be stored in req.files.
WARNING: Make sure that you always handle the files that a user uploads. Never add multer as a global middleware since a malicious user could upload files to a route that you didn't anticipate. Only use this function on routes where you are handling the uploaded files.
router.post("/offer/publish",upload.any(), async (req, res) => {
console.log(req.files); // Should give you an array of files
// Do anything else
});
Use .array(fieldname[, maxCount])
Accept an array of files, all with the name fieldname. Optionally error out if more than maxCount files are uploaded. The array of files will be stored in req.files.
router.post("/offer/publish",upload.array('someFieldName', 10), async (req, res) => {
console.log(req.files); // Should give you an array of files
// Do anything else
});
Use .fields(fields)
Accept a mix of files, specified by fields. An object with arrays of files will be stored in req.files.
fields should be an array of objects with name and optionally a maxCount. Example:
router.post(
"/offer/publish",
upload.fields([
{
name: "image",
maxCount: 1,
},
{
name: "audio",
maxCount: 1,
},
]),
async (req, res) => {
console.log(req.files.image[0]);
console.log(req.files.audio[0]);
// Do anything else
}
);
For your case, I would recommend going with Option 2.
In an Angular2 CLI project, i finnaly implemented this upload button from Vaadin. The button UI works, but i don't know how to actually make it upload a file anywhere.
I keep finding solutions about express server that listens for file uploads, multer or node server, and i really have no idea how to write such a server, where to put it, how to start it, how to access it, etc.. I figured that something as trivial as file upload should be easier to achieve, but it seems is not.
What is a simple solution to implement along side Angular2 in order make the button actually upload files somewhere so i can download them later?
Found the solution in ng2-uploader repo and adapted to work with Vaadin Upload.
component.html
<div *ngIf="newName.valid">
<vaadin-upload
target="http://localhost:10050/upload"
</vaadin-upload>
</div>
server.js
'use strict';
const Hapi = require('hapi');
const Inert = require('inert');
const Md5 = require('md5');
const Multiparty = require('multiparty');
const fs = require('fs');
const path = require('path');
const server = new Hapi.Server();
server.connection({ port: 10050, routes: { cors: true } });
server.register(Inert, (err) => {});
const upload = {
payload: {
maxBytes: 209715200,
output: 'stream',
parse: false
},
handler: (request, reply) => {
const form = new Multiparty.Form();
form.parse(request.payload, (err, fields, files) => {
if (err) {
return reply({status: false, msg: err});
}
let responseData = [];
files.file.forEach((file) => {
let fileData = fs.readFileSync(file.path);
const originalName = file.originalFilename;
const generatedName = Md5(new Date().toString() +
originalName) + path.extname(originalName);
const filePath = path.resolve(__dirname, 'uploads',
originalName);
fs.writeFileSync(filePath, fileData);
const data = {
originalName: originalName,
generatedName: generatedName
};
responseData.push(data);
});
reply({status: true, data: responseData});
});
}
};
const uploads = {
handler: {
directory: {
path: path.resolve(__dirname, 'uploads')
}
}
};
server.route([
{ method: 'POST', path: '/upload', config: upload },
{ method: 'GET', path: '/uploads/{path*}', config: uploads }
]);
server.start(() => {
console.log('Upload server running at', server.info.uri);
});
And a bonus for those who need server.js running at startup this is an awesome solution tested and working.