I want to make an app that takes a name and an image from user and saves it to the server using multer.
The problem is that i want to validate the name before saving the image, using a middleware before the multer upload middleware.
this is the server code:
const express = require('express');
const app = express();
const ejsMate = require('ejs-mate');
const path = require('path');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
app.engine('ejs', ejsMate);
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
const validateName = ( req, res, next ) => {
const { name } = req.body;
if ( name.length <= 0 ) return res.send(JSON.stringify({ success: -1 }));
next();
};
app.route('/upload')
.get(( req, res ) => { res.render('index'); })
.post(validateName, upload.single('image_file'), function (req, res) {
console.log(req.file, req.body);
res.send(JSON.stringify({ success: 1 }));
});
app.listen(3000, () => {
console.log("Server started");
});
I am sending the name and image using fetch api:
window.onload = () => {
const submitBtn = document.querySelector("#submit");
const nameInput = document.querySelector('[name="name"');
const imageInput = document.querySelector('[name="image"');
function handleRenderImage() {
console.log( "SUCCESS" );
};
submitBtn.addEventListener('click', async () => {
const formData = new FormData();
const name = nameInput.value;
const image = imageInput.files[0];
formData.append('name', name);
formData.append('image_file', image);
await fetch('/upload', {
method: "POST",
body: formData
})
.then( response => response.json() )
.then( data => {
const { success } = data;
if ( success ) handleRenderImage();
})
.catch(e => { console.error(e); });
});
};
I think the app.use(express.urlencoded({ extended: true })); and app.use(express.json()); is the reason because the request send from client side should be json encoded.
How can i read the req.body inside validateName middleware ?
First off, if you're sending a name and a binary image from a browser, then your data won't be urlencoded or JSON. It will likely have a content-type of multipart/form-data.
You won't get any access to any data from the body of the request until some code reads the body from the stream and parses it for you. If this content-type is multipart/form-data, then you have to use middleware that can read and parse that type (which is something multer can do).
So, what you're kind of asking to do is to read part of the body, parse it, let you look at, then if you like it, read the rest of the body. I'm not aware of any multipart/form-data middleware that will do that easily. It's not even clear to me that the name is guaranteed to arrive BEFORE the image in the data so you might have to read through the whole image just to get to the name anyway.
What I suggest you do instead, is parse the whole response, put the image in a temporary location and then, if the rest of the data looks appropriate, you can then put the image in its final location. If not, you can delete it.
Another possibility would be take the name out of the body and make it a query parameter (this would require custom coding on the client-side). You can then see the query parameter before you've read and parsed the body.
Another possibility would be to split the upload into two parts, the first request would give you the name which you could check independently. The second would then provide the image. You would have to keep some server-side state (perhaps express-session) that has the name that is to be associated with the uploaded image. I would generally not recommend this approach as it has lots of edge cases to deal with and avoiding server-side state between requests when possible is a general design goal.
Related
I have this endpoint on backend (node, express):
router.post('/createUser', (req, res, next) => {
try{
const { user } = req.body;
if(!user) next(createError(401))
data.push(user);
res.status(200).json(user)
} catch(e) {
next(e)
}
})
and this on front (service)
class EmployeeService {
constructor() {
this.api = axios.create({
baseURL: process.env.REACT_APP_BACK_DOMAIN || 'http://localhost:4000',
withCredentials: true
})
}
getPaginated = (page, size) => this.api.get(`/?page=${page}&size=${size}`).then(({data}) => data)
createUser = user => this.api.post('/createUser', user).then(({data}) => data)
}
When i receive createUser Request, i take body from req, but it is undefined, i console log "req" and this returned me an incoming message object Why? i need to receive body for push on array :(
the object that i create on front:
It seems to me that you are sending the user object wrong, Try this:
user = {id:1,name:"nombre"}
createUser = this.api.post('/createUser', user).then(({data}) => data)
And check if the body arrives to backEnd
This is let answer but...
As you mention you are receiving an object but not able to excess data though req.body
server API and Axios working fine then the problem is parser
try adding parser as shown below and make sure it is before app.use("/", router)
const express = require('express');
const app = express();
var router = express.Router();
// create application/x-www-form-urlencoded parser
app.use(express.urlencoded({ extended: false }));
// parse application/json
app.use(express.json());
app.use("/", router);
for more information check this
Express.js req.body undefined
My problem is a bit tricky one. I know everyone will say bodyparser for solution but i used bodyparser as well but I get still same error.
this is my back end server.js
const fs = require('fs')
const path = require('path')
const express = require('express') // We import express module in our project.
const app = express() // We assign express's functions to app variable.
const bodyParser = require('body-parser') // BodyParser catches data from the http request(POST,PATCH,PUT) and parsing this data to JSON object.
const HttpError =require('./models/HttpError')
const mongoose = require('mongoose')
const userRouter = require('./routes/user-routes')
const recipeRouter = require('./routes/recipe-routes')
const mealPlanRouter = require('./routes/mealplan-routes')
app.use(bodyParser.json())
app.use(
bodyParser.urlencoded({
extended: true
})
);
// We catch data from request and turn this data to json object with BodyParser.
app.use('/uploads/images', express.static(path.join('uploads','images'))) // We create middleware for uploading images and we called this middleware here.
app.use((req, res, next) => { // We need to write this middleware. Because We decide to how to get a request from the client.This is like protocol between server and client for the communication.
res.setHeader('Access-Control-Allow-Origin','*')
res.setHeader('Access-Control-Allow-Headers',
'Origin, X-Request-With, Content-Type, Accept, Authorization'
)
res.setHeader('Access-Control-Allow-Methods','GET, POST, PUT, DELETE')
next()
})
app.use('/api/users',userRouter)
app.use('/api/recipes',recipeRouter)
app.use('/api/mealplans',mealPlanRouter)
app.use((req, res, next) => { // When the client try to access wrong routes. We need to say the client is going wrong way.
const error = new HttpError('Could not find this route', 404)
throw error
})
app.use((err,req,res,next) => { // We check if user pick file for importing an image.
if(req.file){
fs.unlink(req.file.path, err => {
console.log(err)
})
}
if(res.headerSent){
return next(err)
}
res.status(err.code || 500)
res.json({message:err.message || 'Unknown error occured'})
})
// We connect our project with database. Mongoose communicate with database and we communicate with mongoose. This way is more secure.
mongoose
.connect(`mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASSWORD}#cluster0-vvtq0.mongodb.net/${process.env.DB_NAME}?retryWrites=true&w=majority`,{ useNewUrlParser: true, useUnifiedTopology: true})
.then(() => {
app.listen( process.env.PORT || 5000, () => {
console.log('The Server is running')
})
})
.catch(err => {
console.log(err)
})
this is where i send formdata. I check my formdata from network tab. It seems fine. when i check my formdata with req.body in my server.js returns empty object.
const handleSubmit = async e => {
e.preventDefault()
const datestr = (new Date(startDate)).toUTCString();
try{
const formData = new FormData()
formData.append('title',formState.inputs.title.value)
formData.append('date',datestr)
formData.append('timeFrame',formState.inputs.timeFrame.value)
formData.append('targetCalories',formState.inputs.targetCalories.value)
formData.append('diet',formState.inputs.diet.value)
formData.append('exclude',excludeData.join())
formData.append('creator',auth.userId)
const responseData = await axios.post(
process.env.REACT_APP_BACKEND_URL+'/mealplans/new',
formData,{
headers: {Authorization : `Bearer ${auth.token}`} })
console.log(responseData)
}
catch(err){
console.log(err.message)
}
}
Thanks from now for your helps.
FormData objects get converted to Multi-part request bodies.
You need a body parser capable of handling that data format. You only have body parsers for JSON and URL encoded data.
The body-parser module homepage recommends 4 modules which support Multi-part bodies.
I posted data on angular front end as formData like this.
postButton(name: string): Observable<any> {
const formData = new FormData();
formData.append('name', name);
return this.http.post(environment.apiUrl + '/url, formData);
}
And I receive data on Node.js front end like this.
const bodyParser = require("body-parser");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/api/url', (req, res, next) => {
console.log(req.body)
res.status(200).json({
'message': 'OK',
});
});
But I get {},empty value.
what is wrong in my code?
Regards.
According to my knowledge, if you are sending some file then using FormData is useful. In other scenario's like this in which you are just sending plain text. You can just send a normal json object and it will work. You can try this.
postButton(name: string): Observable<any> {
return this.http.post(environment.apiUrl + '/url, { name });
}
In case you really want to use FormData then you need to install a npm package as:
npm install multer
And change your app.js to:
var express = require('express');
var app = express();
var multer = require('multer');
var upload = multer();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(upload.array());
app.use(express.static('public'));
app.post('/api/url', function (req, res) {
console.log(req.body);
});
module.exports = app;
Now, what multer does is, it supports multi-part form submission. And FromData uses that. To get the data from request body you need to install and configure multer.
Hope this works for you.
Delete the Content-Type header:
postButton(name: string): Observable<any> {
const formData = new FormData();
formData.append('name', name);
const httpOptions = {
headers: new HttpHeaders().delete('Content-Type')
};
return this.http.post(environment.apiUrl + '/url, formData, httpOptions);
}
When the XHR API send method sends a FormData Object, it automatically sets the content type header with the appropriate boundary. When the Angular http service overrides the default content type, the server will get a content type header without the proper boundary.
I want to send a images folder along with the other data. (name and openingHours);
this is my vuejs code that I use to submit data to the backend
const formData = new FormData();
formData.append("name", this.name);
formData.append("openingHours", this.openingHours);
formData.append("photos", this.selectedFile);
await this.$http.post("spa", formData);
Here my controller code
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
router.post('/', upload.array('photos', 10), async (req, res) => {
console.log(req.file);
console.log(req.body);
});
Here the req.file is undefined and photos also come under the body and also this openingHours is not an array. I pass and array in the front-end.
This is my body parser settings in the app.js
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json());
Can anybody tell me what's wrong with my code?
I want to pass openingHours as an JS array.
UPDATED
this is what I get if I console log openingHours in that method.
You need to stringify your array calling JSON.stringify before saving to FormData:
formData.append("openingHours", JSON.stringify(this.openingHours));
And on the backend you need to parse it calling JSON.parse:
const openingHours = JSON.parse(req.body.openingHours)
I'm trying to upload images. It's reaching the backend, but the request body and req.image are coming out empty.
I have the submission:
const handleSubmit = async () => {
try {
const data = createFormData();
console.log(data); // prints the correct request object
const response = await axios.post(
`http://${GATEWAY}:5000/api/uploads/single`,
JSON.stringify(data)
);
alert("Upload success!");
console.log("response.data", response.data);
} catch (err) {
console.log("err caught --> ", err);
}
};
const createFormData = () => {
const data = new FormData();
data.append("title", title); // coming from
data.append("body", body); // react hooks state (useState)
data.append("image", {
height: image.height,
width: image.width,
type: image.type,
uri:
Platform.OS === "android" ? image.uri : image.uri.replace("file:/", "")
});
return data;
};
My endpoint:
const express = require("express");
const multer = require("multer");
const bodyParser = require("body-parser");
express().use(bodyParser.json());
const router = express.Router();
// middleware
const auth = require("../../middleware/auth");
const storage = multer.diskStorage({
destination(req, file, callback) {
callback(null, "./images");
},
filename(req, file, callback) {
callback(null, `${file.fieldname}_${Date.now()}_${file.originalname}`);
}
});
const upload = multer({ storage });
// #route POST api/uploads/single
// #desc Upload single image
// #access Private
router.post(
"/single",
// upload.array("photo", 3),
auth,
upload.single("image"),
(req, res) => {
console.log("req", req.body); // {}
console.log("req", req.image); // undefined
return res.status(200).json({
message: "Response from backend"
});
}
);
module.exports = router;
And my server.js
const express = require("express");
const connectDb = require("./config/db");
const app = express();
// connect to db
connectDb();
// Define routes (some omitted for brevity)
app.use("/api/uploads", require("./routes/api/uploads"));
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Listening on port ${PORT}`));
For some reason, in the first snippet, if I do not stringify the data: FormData object that is built from createFormData(), my backend is never even reached.
I've tried so many things, and I'm not starting to think that maybe my backend isn't setup properly. The line where I'm doing express().use(bodyParser.json()); exists because I can't do app.use(bodyParser.json()); (or at least I think), because the app object is in the main server.js file. I'm including other API routes in other files.
For example, my server.js has these, amongst others:
// Define routes
app.use("/api/auth", require("./routes/api/auth"));
app.use("/api/users", require("./routes/api/users"));
app.use("/api/profile", require("./routes/api/profile"));
app.use("/api/uploads", require("./routes/api/uploads"));
And I was following this tutorial to use multer with react-native. A little lost at this point, not sure what I'm doing wrong.
Edit:
I'm making the request like this now,
const config = {
headers: {
"Content-Type": "multipart/form-data"
}
};
const response = await axios.post(
`http://${GATEWAY}:5000/api/uploads/single`,
data,
config
);
But It's failing with a
If I stringify it, it hits the backend but not in the way I need it to:
Edit:
I got it working by specifying the image type, as per the suggestion here
I think this is because you are not setting your content-type to multipart/form-data. Try adding this to your request options:
const response = await axios.post(
`http://${GATEWAY}:5000/api/uploads/single`,
data,
headers: {
`Content-Type`: `multipart/form-data`
}
);
Because of multipart/form-data, do not stringify the data you are sending. Stringifying the data will cause it to only be read as text by the server but is expecting a file to be attached.
Got it working by specifying the image type as per the suggestion here
You're trying to access the file from req.image and req.body, but as mentioned in the https://www.npmjs.com/package/multer, you can access it from :
req.file
req.body will hold the text fields only, on the other hand, if you only uploaded a single file you can find it in req.file, but if you uploaded multiple files you will find them in req.files.
I used the following line to get images:-
concole.log(req.files);