I am using MERN to create simple eCommerce I am facing error when i am in production mode. The file uploads on the specified folder but don't get load on front-end.
Server.js
import path from 'path'
import express from 'express'
import dotenv from 'dotenv'
const __dirname = path.resolve()
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '/frontend/build')))
app.get('*', (req, res) =>
res.sendFile(path.resolve(__dirname, 'frontend', 'build', 'index.html'))
)
} else {
app.get('/', (req, res) => {
res.send('Api running....')
})
}
app.use('/uploads', express.static(path.join(__dirname, '/uploads')))
Upload routes and code
import path from 'path'
import express from 'express'
import multer from 'multer'
const router = express.Router()
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, '/uploads')
},
filename(req, file, cb) {
cb(
null,
`${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`
)
},
})
function checkFileType(file, cb) {
const filetypes = /jpg|jpeg|png/
const extName = filetypes.test(path.extname(file.originalname).toLowerCase())
const mimeType = filetypes.test(file.mimetype)
if (extName && mimeType) {
return cb(null, true)
} else {
cb('Images only with jpg, jpeg, png ')
}
}
const upload = multer({
storage,
fileFilter: function (req, file, cb) {
checkFileType(file, cb)
},
})
router.post('/', upload.single('image'), (req, res) => {
res.send(`/${req.file.path}`)
})
export default router
When i inspect the file it says on the src the image cant load check the image=>
Now when i am in development mode the same code works as expected and the image loads.
What might the possible error please help me. Thank you in Advance.
Your production server is not meant to store files because that will lead to increase in consumption of server resources. So it will automatically delete any files/images that you have uploaded to the production server.
And why would you want to store files on production server, just use a database or file storage system like s3 bucket.
I got the same error and what I did is just switching the place the of the upload folder on the top. You want to make sure that all the image be available before the build of frontend request any image from it.
import path from 'path'
import express from 'express'
import dotenv from 'dotenv'
const __dirname = path.resolve()
// Put it here
app.use('/uploads', express.static(path.join(__dirname, '/uploads')))
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '/frontend/build')))
app.get('*', (req, res) =>
res.sendFile(path.resolve(__dirname, 'frontend', 'build', 'index.html'))
)
} else {
app.get('/', (req, res) => {
res.send('Api running....')
})
}
Related
I am testing multer package for the first time, but when I do the post request on Postman it returns an empty object. Reading similar questions from someone who had the same problem I found several answers saying it was a Postman problem. I have tried restarting the server and reopening the program several times but without success.
This is my server settings:
import express from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import dotenv from 'dotenv';
import authRouter from './routes/auth.js';
import categoryRouter from './routes/category.js';
import productRouter from './routes/product.js';
//DOTENV CONFIG
dotenv.config();
const MONGODB_CONNECTION = process.env.MONGO_URI;
//INITIALIZE APP
const app = express()
//MIDDLEWARES
/*app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});*/
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors({
origin: 'http://localhost:3000',
methods: ['GET','POST','DELETE','UPDATE','PUT','PATCH']
}));
//ROUTES
app.use('/api/auth', authRouter);
app.use('/api/categories', categoryRouter);
app.use('/api/products', productRouter)
//PORT
const PORT = process.env.PORT || 5020
//CONNECT MONGODB
const connectDB = () => {
mongoose.connect(MONGODB_CONNECTION, {
useNewUrlParser: true,
useFindAndModify: true,
useUnifiedTopology: true,
useCreateIndex: true
} )
.then(() => console.log('MONGODB is connected'))
.catch(err => console.log('MONGODB connection error:', err ))
}
connectDB();
//INITIALIZE SERVER
app.listen(PORT, () => console.log (`Connection is established and running on port ${PORT}`)
)
My multer middleware:
import multer from 'multer';
import path from 'path';
const storage = multer.diskStorage({
destination: function (req, file, cb){
cb(null, path.join(path.dirname(__dirname), 'uploads'))
},
filename : function (req, file, cb){
cb(null, Date.now() + '__' + file.originalname)
}
})
const fileFilter = function (req, file, cb) {
if(file.mimetype === "images/png" || file.mimetype === "images/jpg" || file.mimetype === "images/jpeg") {
cb(null, true)
}else{
cb(null, false)
}
}
export const upload = multer({storage: storage, fileFilter: fileFilter})
My test controller:
//CREATE PRODUCT
export const createProduct = async (req, res) => {
res.status(200).json({file: req.file})
}
My route:
import express from 'express';
import { authMiddleware, authAdminMiddleware } from '../middlewares/auth.js';
import { getProducts, createProduct } from '../controllers/product.js';
import { upload } from '../middlewares/upload.js';
router.route('/create-product').post(authMiddleware, authAdminMiddleware, upload.single('image'), createProduct);
On Postman :
Has anyone managed to solve this problem?
There's a couple of things that's not entirely correct.
res.status(200).json({file: req.file})
I'm not sure you can send a file using response.json, instead something like:
res.sendFile()
You also don't have to specifically use response.status(200).
Other than that, I'm not sure you can get the file from the request using file, I think it should be files, plural. Unless some middleware you're using is changing that. To access files use request.files.
Also you're declaring the route like this:
app.use('/api/products', productRouter)
...
export const createProduct = async (req, res)
However doing that I'm not sure how express would know that it's a post request (even if it applies it to all routes), but it's also just not the way it should be used. Instead do:
app.post('/api/products/create_product', createProduct)
...
export const createProduct = async (req, res) => {}
Or alternatively:
export const productRouter = express.Router()
app.use('/api/products', productRouter)
...
import {productRouter} from './server'
router.post('/create_product', async (req, res) => {})
Somewhere I think you have to tell it that you're actually looking for a post request. You can either directly map routes to the app or you can create a router and "use" the router, but I don't think you should be "using" route handlers directly if they're not pure middleware, like if they're "METHOD" dependent (like POST in this case).
If you've fixed those things then I think it might work, otherwise try try again.
The problem is when I use multer and send a request in Postman the req.body comes as an empty object and the req.file comes as undefined. I've unchecked the content-type header in postman.
And here's the code:
//Route
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, '../uploads/');
},
filename: function (req, file, cb) {
cb(null, new Date().toISOString() + file.originalname);
}
});
const upload = multer({
storage,
limits: {fileSize: 1024 * 1024 * 10}
});
router.post('/test', upload.single('profilePicture'), authController.test);
//Controller
const test = (req, res) => {
console.log(req.body)
console.log(req.files)
res.json({body:req.body, files:req.files})
}
//app.js
app.use(express.json({extended: true, limit: '30mb'}));
app.use(express.urlencoded({extended: true, limit: '30mb'}))
app.use(cookieParser());
app.use('/api/auth', authRoutes);
app.use('/api/product', productRoutes);
app.use('/api/profile', profileRoutes);
Edit: turnes out, the problem is in Postman. I made a request with axios from a React app and everything works. So the question is, why doesn't it work in Postman? Is it some Bug in software or is there some settings that we're supposed to change?
The problem is that Nodejs is by default uses Ansynchornus Javascript. You need to use the async-await approach and try-catch-finally methods over conventional JS programming.
So your controller would look like -
//Route
router.post('/test', async (req, res, next)=>
{
try{
await upload.single('profilePicture')
next()
} catch(err){
console.log(err)
res.send('failed!')
},
authController.test);
//Controller
const test = async (req, res) => {
try{
console.log(req.body)
console.log(req.files)
res.json({body:req.body, files:req.files})
} catch(err){
console.log(err);
}
}
A late addition to the answer.
If you're trying to just access the uploaded image, then you should make use of the buffer.
var storage = multer.memoryStorage()
var upload = multer({ storage: storage })
I have a node.js express code below to upload a image and store into a default folder.
I realised that the file gets renamed and the extension is lost. can some one help me fix this error?
1.How to retain extension and file name
if a zip file is upload, i want to unzip it and upload it
const __basefolder = "C:/Users/user/Desktop";
const express = require('express');
const multer = require('multer');
const upload = multer({dest: __basefolder + '/uploads/images'});
const app = express();
const PORT = 3000;
app.use(express.static('public'));
app.post('/upload', upload.single('file'), (req, res) => {
if(req.file) {
res.json(req.file);
}
else throw 'error';
});
app.listen(PORT, () => {
console.log('Listening at ' + PORT );
});
You can define a filename-option in your disk-storage setup, which lets you choose your filename arbitrarily. Since you want the original file-name, you can use file.originalname (note that using this an existing file with the same name will be overwritten though):
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, __basefolder + '/uploads/images');
},
filename: (req, file, cb) => {
cb(null, file.originalname);
}
})
const upload = multer({storage});
Regarding the second question: this has been answered before, see this or that for example.
I've developed a small web server to upload pictures.
Now I would like to use the original name of the picture and move the picture into a folder. the name of the folder is in the req.body.
Ok, the upload works, but where is the point to rename oand move the picture?
const express = require('express');
const multer = require('multer');
const upload = multer({ dest: __dirname + '/uploads/images' });
const app = express();
const PORT = 3000;
app.use(express.static('public'));
app.post('/upload', upload.single('image'), (req, res) => {
console.log(req.file.originalname)
console.log(req.body.foldername)
if (req.file) {
res.json(req.file);
}
else throw 'error';
});
app.listen(PORT, () => {
console.log('Listening at ' + PORT);
});
This is your question answer to rename a file before it upload
var storage = multer.diskStorage({
// Where to save
destination: function (req, file, cb) {
cb(null, '/tmp/my-uploads')
},
// File name
filename: function (req, file, cb) {
cb(null, file.originalname) // file.originalname will give the original name of the image which you have saved in your computer system
}
})
var upload = multer({ storage: storage })
I deployed my express app on heroku and am attempting to upload images using multer. This all works perfectly on localhost but on heroku when I upload an image it will not save to my images folder, and I get a 500 server error with:
Error: ENOENT: no such file or directory, open '../images/kauai-mountain.jpg'
I am using create react app on front end.
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(error, '../images/');
},
filename: (req, file, cb) => {
cb(null, file.originalname);
}
});
router.post('/', multer({ storage }).single('image'), (req, res) => {
const { description, category } = req.body;
const url = req.protocol + '://' + req.get('host');
const newPost = new Post({
description,
category,
imagePath: url + '/images/' + req.file.filename
});
newPost.save().then(post => res.json(post)).catch(e => res.json(e));
});
server.js
if (process.env.NODE_ENV === 'production') {
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use('/', express.static('client/build'));
app.get('*' , (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
My front end code is uploading the image and sending it to the route, the error is in the back end.
EDIT
I am reading that the problem may be that heroku cant save images consistently. I don't understand because I am saving them in an images folder on my server which I run on heroku. Could this be the problem?