'multer file upload req.file.path undefined put request
I've created a simple RESTapi with express and for file upload I'm using multer. Creating a new item / post works. My issue is with the put route and the function to handle saving and updating the post/gear.
When the product image field is left blank, it returns
undefined [Object: null prototype] {
title: 'joey',
price: '123',
weight: '123',
description: 'lorem'
}
(node:5293) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'path' of undefined
at /Users/Zack/Desktop/LeFIAT/lefiatV2/routes/gear.js:81:34
When all the fields in the product fields are filled out returns a duplicate key
MongoServerError: E11000 duplicate key error collection: lefiatv2.products index: slug_1 dup key: { slug: "joey" }
at /Users/Zack/Desktop/LeFIAT/lefiatV2/node_modules/mongodb/lib/operations/update.js:80:33
The object is being populated I just can't seem to find the correct path or set up for the function
{
fieldname: 'image',
originalname: 'TorridV2_Toggle__.jpg',
encoding: '7bit',
mimetype: 'image/jpeg',
destination: './public/products/',
filename: '2022-02-02T15:44:30.813ZTorridV2_Toggle__.jpg',
path: 'public/products/2022-02-02T15:44:30.813ZTorridV2_Toggle__.jpg',
size: 23382
} [Object: null prototype] {
title: 'joey',
price: '123',
weight: '123',
description: 'lorem'
}
edit.ejs
Edit Product<form action="/gear/<%= product.slug %>?_method=PUT" method="POST" enctype="multipart/form-data">
<%- include('../partials/product_fields') %>
</form>
product_fields.ejs
<div class="form-group">
<label for="title">Title</label>
<input type="text" name="title" id="title" class="form-control" value="<%= product.title %>" required>
</div>
<div class="form-group">
<label for="price">Price</label>
<input type="number" min="1" step="0.01" name="price" id="price" class="form-control"
required><%= product.price %></input>
</div>
<div class="form-group">
<label for="weight">Weight</label>
<input type="number" min="1" step="0.01" name="weight" id="weight" class="form-control"
required><%= product.weight %></input>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea name="description" id="description" class="form-control" required><%= product.description %></textarea>
</div>
<div class="form-group">
<label for="image">Upload Images</label>
<input type="file" name="image" id="image" class="form-control-file" multiple>
</div>
<a href="/gear" class="btn btn-secondary"> Cancel </a>
<button type="submit" class="btn btn-primary"> Save </button>
product.js modal
const { date } = require('joi')
const mongoose = require('mongoose')
const slugify = require('slugify')
const productSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
price: {
type: Number,
required: true
},
weight: {
type: Number,
required: true,
},
slug: {
type: String,
required: true,
unique: true
},
createdAt: {
type: Date,
default:Date.now
},
image: {
type: String,
data: Buffer
}
})
productSchema.pre('validate', function(next) {
if (this.title) {
this.slug = slugify(this.title, { lower: true, strict: true })
}
next()
})
module.exports = mongoose.model('Product', productSchema)
gear.js route
const express = require('express')
const multer = require('multer')
const Product = require('./../models/product')
const router = express.Router()
const storage = multer.diskStorage({
// destination for file
destination: function (req, file, callback) {
callback(null, './public/products/')
},
// add back the extension
filename: function (req, file, callback) {
callback(null, new Date().toISOString() + file.originalname)
},
})
const fileFilter = (req, file, cb) => {
// reject a file
if (file.mimetype === 'image/jpeg' || file.mimetype === 'image/png') {
cb(null, true);
} else {
cb(null, false);
}
};
// upload parameters for multer
const upload = multer({
storage: storage,
limits: {
fileSize: 1024*1024*3,
},
fileFilter: fileFilter
})
router.get('/', async (req, res) => {
const products = await Product.find().sort({ createdAt: 'desc' })
res.render('gear/index', { products: products })
})
router.get('/new', (req, res) => {
res.render('gear/new', { product: new Product() })
})
router.get('/edit/:id', async (req, res) => {
const product = await Product.findById(req.params.id)
res.render('gear/edit', { product: product })
})
router.get('/:slug', async (req, res) => {
const product = await Product.findOne({ slug: req.params.slug })
if(product == null) res.redirect('/gear')
else res.render('gear/show', { product: product })
})
router.post('/', upload.single('image'), async (req, res, next) => {
req.product = new Product()
next()
}, saveProductAndRedirect('new'))
router.put('/:id', upload.single('image'), async (req, res, next) => {
console.log(req.file, req.body)
req.product = await Product.findOneAndUpdate({ slug: req.params.slug })
next()
}, saveProductAndRedirect(`edit`))
router.delete('/:id', async (req, res) => {
await Product.findByIdAndDelete(req.params.id)
res.redirect(`/gear`)
})
function saveProductAndRedirect(path) {
return async (req, res) => {
let product = req.product
product.title = req.body.title
product.description = req.body.description
product.price = req.body.price
product.weight = req.body.weight
product.image = req.file.path
try {
if (! req.file || ! req.file.path) {
return res.sendStatus(400);
}
product = await product.save()
res.redirect(`/gear/${product.slug}`)
} catch(err) {
console.log(err)
res.render(`gear/${path}`, { product: product })
}
}
}
module.exports = router
server.js
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}
const express = require('express')
const path = require('path');
const ejsMate = require('ejs-mate');
const session = require('express-session');
const methodOverride = require('method-override');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const mongoSanitize = require('express-mongo-sanitize');
const expressLayouts = require('express-ejs-layouts')
const ExpressError = require('./utils/ExpressError');
const indexRouter = require('./routes/index')
const tripsRouter = require('./routes/trips')
const gearRouter = require('./routes/gear')
const aboutRouter = require('./routes/about')
const blogRouter = require('./routes/blog')
const app = express()
app.engine('ejs', ejsMate)
app.set('view engine', 'ejs')
app.set('views', __dirname + '/views')
app.use('/public', express.static(__dirname + '/public'));
app.use(express.urlencoded({ extended: false }));
app.use(methodOverride('_method'));
app.set('layout', 'layouts/layout')
app.use(expressLayouts)
app.use(express.static(path.join(__dirname, 'public')))
app.use(mongoSanitize({
replaceWith: '_'
}))
const MongoDBStore = require('connect-mongo')(session)
const mongoose = require('mongoose')
mongoose.connect(process.env.DATABASE_URL, {
useNewUrlParser: true })
const db = mongoose.connection
db.on('error', error => console.error(error))
db.once('open', () => console.log('Connected to Mongoose'))
const secret = process.env.SECRET || 'thisshouldbeabettersecret!';
const dbUrl = process.env.DATABASE_URL || 'mongodb://localhost:3000/lefiatv2'
const store = new MongoDBStore({
url: dbUrl,
secret,
touchAfter: 24 * 60 * 60
});
store.on('error', function (e) {
console.log('session store error', e)
})
const sessionConfig = {
store,
name: 'session',
secret,
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: true,
expires: Date.now() + 1000 * 60 * 60 * 24 * 7,
maxAge: 1000 * 60 * 60 * 24 * 7
}
}
app.use(session(sessionConfig));
app.use(passport.initialize());
app.use(passport.session());
app.use('/', indexRouter)
app.use('/trips', tripsRouter)
app.use('/gear', gearRouter)
app.use('/about', aboutRouter)
app.use('/blog', blogRouter)
app.use((err, req, res, next) => {
const { statusCode = 500 } = err;
if (!err.message) err.message = 'Oh No, Something Went Wrong!'
res.status(statusCode).render('error', { err })
})
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Serving on port ${port}`)
})
Solution 1:[1]
use optional chaining on your req.file.path so it would be like this
req.file?.path (kinda)
the error is telling you - hey where is the path?, there is no path in undefined - so your result if not passing the file will be
undefined.path
here is some example (i use nestjs):
async updateCommit(
@Res() res,
@Param('user_id') user_id: string,
@Param('repo_id') repo_id: string,
@Param('commited_id') commited_id: string,
@Body() updatedCommit: UpdateCommitDto,
@UploadedFile() file: UpdateCommitDto["file"]): Promise<User> {
Object.assign(updatedCommit, {
file: file?.path,
createdAt: false,
updatedAt: Date.now
})
const response = await this.commitService.updateCommit(user_id, repo_id, commited_id, updatedCommit)
return res.status(HttpStatus.OK).json({ response })}
The result :
sorry for the image ( low reputation lol )
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |