0👍
The body-parser module does not support multipart/form-data as it says in their documentation (this can be hard to figure out if you’re new to nodejs).
You will need to find some middleware that will handle this for you. The body-parser documentation recommends a few:
This does not handle multipart bodies, due to their complex and
typically large nature. For multipart bodies, you may be interested in
the following modules:
Each library will work slightly differently, so you should use their documentation to construct your images
object on the server side.
0👍
I was struggling also with empty request body in my api server project.
I wanted to post multipart-form data which consists of string/text data and an image to a backend restful api server. I thought that by just passing the data using formData object through Axios would be sufficient. However I ended up almost frustated because in the backend restful api project I kept getting empty request body. Thankfully through search on some articles, stackoverflow and youtube. I managed to assemble a fully working node.js program. No more empty request body data !!! .
Here I would like to share my working codes.
To pass multipart/form-data from client to server, I have found out that we to use several middlewares and node/java objects :
- In the server side api project : I use {multer, body-parser} middlewares
- In the client app project : I use formData and axios
Here are my working codes : ( coded in node.js , using visual studio code)
// in my (server backend api) node project : /src/routes/products/index.js
const express = require("express")
const router = express.Router()
const multer = require('multer')
const ProductsController = require('../controllers/products')
const storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './uploads/')
},
filename: function(req, file, cb) {
// use this on Windows dir
cb(null, new Date().toISOString().replace(/:/g, '-') + file.originalname)
// otherwise (in Linux or IOS use this
// cb(null, new Date().toISOString() + file.originalname)
}
})
const fileFilter = (req, file, cb) => {
// reject a file if not allowed
var allowedMimes = ['image/jpeg','image/png' , 'image/gif']
if (allowedMimes.indexOf(file.mimetype) > -1) {
cb(null, true)
} else {
cb(null, false)
}
}
const upload = multer({
storage: storage,
limits: {fileSize: 1024 * 1024 * 10},
fileFilter: fileFilter
})
router.post("/", upload.single('productImage'),ProductsController.products_create_product)
module.exports = router
// end of /src/routes/products/index.js
Now in (server backend api) /src/app.js :
const express = require("express")
const morgan = require("morgan")
const bodyParser = require("body-parser")
const mongoose = require("mongoose")
const productRoutes = require("./routes/products")
const app = express()
mongoose.connect('mongodb://localhost/shopcart', {
useMongoClient: true
})
var db = mongoose.connection
db.on('error', console.error.bind(console, 'connection error:'))
db.once('open', function() {
// we're connected!
console.log("Connected to mongoDB..."+ db.db.databaseName+ ', url: ' +'http://localhost:5000')
})
mongoose.Promise = global.Promise
app.use(morgan("dev"))
app.use('/uploads', express.static('uploads'))
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*")
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
)
if (req.method === "OPTIONS") {
res.header("Access-Control-Allow-Methods", "PUT, POST, PATCH, DELETE, GET")
return res.status(200).json({})
}
next()
})
// Routes which should handle requests
app.use("/products", productRoutes)
app.use((req, res, next) => {
const error = new Error("api Not found...")
error.status = 404
next(error)
})
app.use((error, req, res, next) => {
res.status(error.status || 500)
res.json({
error: {
message: error.message
}
})
})
module.exports = app
//end of : /src/app.js
Now in (server backend api) /src/index.js :
const http = require('http')
const app = require('./app.js')
const ip_addr = 'localhost'
const port = process.env.PORT || 3000
const server = http.createServer(app)
server.listen(port , ip_addr)
console.log('Server started on %s port: %s',ip_addr , port )
// end of : /src/index.js
Code for ProductController in (backend api) : /src/controller/Products.js :
(please note that the req.file is automatically added by ‘multer’ middleware)
exports.products_create_product = (req, res, next) => {
// note : productImage: req.file.path
// (file) is added by 'multer' middleware
const product = new Product({
_id: new mongoose.Types.ObjectId(),
name: req.body.name,
price: req.body.price,
specification: req.body.specification,
productImage: req.file.path
})
product
.save()
.then(result => {
res.status(201).json({
message: "Product created successfully",
createdProduct: {
name: result.name,
price: result.price,
specification: result.specification,
_id: result._id,
productImage: result.productImage,
request: {
type: "POST",
url: "http://localhost:5000/products/"
}
}
})
})
.catch(err => {
res.status(500).json({
error: err
})
})
}
And finally in my client app project, I use Axios and formData to send post request to the backend api server.
Here is the code extract for Front end client app to post:
function createProduct (payload, apiUrl, myAuthToken) {
const headers = {
'Content-Type': 'multipart/form-data',
// 'Authorization': 'Bearer ' + myAuthToken // if using JWT
}
var formData = new FormData()
formData.append( 'productImage', payload.productImage)
formData.append( 'name', payload.name)
formData.append( 'price', payload.price)
formData.append( 'specification', payload.specification)
Axios.post(apiUrl + '/products',
formData,
{'headers' : headers}
)
.then((response) => {
var imgUrl = response.data.createdProduct.productImage
payload.productImage = apiUrl+'/'+ imgUrl.replace(/\\/g, "/")
payload.id= response.data.createdProduct._id
payload.specification = response.data.createdProduct.specification
payload.price =response.data.createdProduct.price
commit('createProduct', payload)
})
.catch((error) => {
alert(error)
})
}
The following function can be used to extract image data from an input html event :
function onFilePicked (event) {
const files = event.target.files
if (!files) {
return
}
let filename = files[0].name
if (filename.lastIndexOf('.') <= 0) {
return alert('Please add a valid file!')
}
const fileReader = new FileReader()
fileReader.addEventListener('load', () => {
this.productImage = fileReader.result
})
fileReader.readAsDataURL(files[0])
return files[0]
}
I hope these codes will help anybody running into empty request body problem.