Getting an image from localhost gives 404 with NodeJS and Express - node.js

I implemented an upload of images in NodeJS express using multer, and when I try to get the images in the browser I got this error:
URL: http://localhost:5500/images/posts/5e2843e4efa65f188fc5552f.png
Cannot GET /images/posts/5e2843e4efa65f188fc5552f.png
I saw in the console the 404 so I cannot get why I can not see my image.
I did the upload in this way:
postRouter.post(
"/:id/uploadImg",
multerConfig.single("image"),
async (req, res) => {
try {
const fileName =
req.params.id + path.extname(req.file.originalname);
const newImageLocation = path.join(
__dirname,
"../../images/posts",
fileName
);
await fs.writeFile(newImageLocation, req.file.buffer);
req.body.image =
req.protocol +
"://" +
req.get("host") +
"/images/posts/" +
fileName;
const newPostImg = await Posts.findOneAndUpdate(
{ _id: req.params.id },
{
$set: { image: req.body.image }
}
);
newPostImg.save();
res.send("Image URL updated");
} catch (ex) {
res.status(500).send({ Message: "Internal server error", err: ex });
}
}
);
And in my server.js
server.use(express.static("./images"));
I'm able to upload but then not able to see it and cannot figure out what is the issue.

When you wrote server.use(express.static("./images")); you told your server to search for dir's and files starting from the ìmages folder on your app.
Therefore you should go to http://localhost:5500/posts/5e2843e4efa65f188fc5552f.png to search for your uploaded file. Assuming there's no other matching route before server.use(express.static("./images"));.
EDIT:
If you want to be able to use the path you are already using, you should change your images directory like this:
--images
--images
--posts
-photo.png
And change the path where your multer code saves the file like this:
const newImageLocation = path.join(
__dirname,
"../../images/images/posts",
fileName
);

Related

Why is my cloudinary image upload slower in nodejs and reactjs?

It is my first time of trying to use cloudinary and multer to upload and store images for my blog application.
Initially, I was storing the images locally using multer and saved the image url in mongodb. Now, I store the images in cloudinary and save the url in database.
I noticed that the cloudinary option is a bit slower compared to saving locally. Is there a workaround this?
Here is my nodecode:
app.post("/api/v1/upload", upload.single("file"), async (req, res) =>{
try {
const fileStr = req.file.path
if(!fileStr){
return res.status(500).json( 'No image found');
}
const uploadResponse = await cloudinary.uploader.upload(fileStr, {
upload_preset: 'nodeblog',
});
fs.unlinkSync(fileStr)
const result = {
url: uploadResponse.secure_url,
publicId: uploadResponse.public_id
}
return res.status(200).json(result)
} catch (err) {
console.error(err);
return res.status(500).json({ err: 'Something went wrong' });
}
});
After image has uploaded successfully, I simply deleted it locally.

How to fix cloudinary error "Must supply api_key"

I'm building an image upload app using multer and cloudinary. I've configured my environment variables properly using dotenv. However, when I try to upload files using Cloudinary I get the error "Must supply api_key".
The Cloudinary API credentials are correctly supplied and provisioned as in the code below:
cloudinary.js
const cloudinary = require('cloudinary');
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET
})
exports.uploads = (file, folder) => {
return new Promise(resolve => {
cloudinary.uploader.upload(file, (result) => {
resolve({
url: result.url,
id: result.public_id
})
}, {
resource_type: "auto",
folder: folder
})
})
}
.env
CLOUDINARY_CLOUD_NAME='my_cloudinary_cloud_name'
CLOUDINARY_API_KEY='my_cloudinary_api_key'
CLOUDINARY_API_SECRET='my_cloudinary_api_secret'
The .env file is also required correctly in my app.js file:
app.js
require('dotenv').config({
path: './app/config/.env'
});
If I console.log any of the cloudinary config variables inside the cloudinary.js file, I get the expected output, but when I try to use cloudinary in my upload route, I get the error that I must supply api_key. Please can someone help me point out what I'm doing wrong? I don't want to have to write out my cloudinary config variables in the cloudinary.js file because the code is being published to Github.
Here is my post route using cloudinary:
const express = require('express'),
Product = require('../app/models/product'),
upload = require('../app/utils/multer'),
cloudinary = require('../app/utils/cloudinary'),
fs = require('fs'),
router = express.Router();
router.post('/products', upload.array('image'), async (req, res) => {
const uploader = async (path) => await cloudinary.uploads(path, 'Images');
const urls = []
const files = req.files
for (const file of files) {
const {
path
} = file;
const newPath = await uploader(path)
urls.push(newPath)
fs.unlinkSync(path)
}
const name = req.body.name
const product = new Product({
name: name,
imageone: urls[0].url,
imagetwo: urls[1].url,
imagethree: urls[2].url,
imagefour: urls[3].url
})
product.save(function (err, prod) {
if (err) {
throw err
} else {
req.flash('success', "You have added a new product.")
res.redirect("/dashboard")
}
})
})
module.exports = router;
Kóyo #awesome-bassey!
Following cloudinary docs for Nodejs and it's official repo i would advice you to import v2 of the cloudinary API.
In case your issue still remains - please share stack-trace with us
Using the Node.js v2, it can be declared as shown in the following code sample as described in the installation setup document (i.e. require('cloudinary').v2):
var cloudinary = require('cloudinary').v2;
cloudinary.config({
cloud_name: '<YOUR_CLOUD_NAME>',
api_key: '<YOUR_API_KEY>',
api_secret: '<YOUR_API_SECRET>',
secure: true
});
cloudinary.uploader.upload("my_image.jpg",
function(error, result) {
console.log(result, error);
});

Send Blob File from html form to express server so it can be uploaded to cloud

So I'm trying to make the html form:
<form action="blahblah" encblah="multipart/form-data" whatever>
Thats not the problem, I need to make that form send the blob to express
app.post('/upload/avatars', async (req, res) => {
const body = req.body;
console.log(req.file);
console.log(body);
res.send(body);
});
So I can access the blob, create a read stream, pipe it to the cloud, and bam, upload the file without downloading anything on the express server it self.
Is that possible?
If yes, please tell me how.
If no, please tell me other alternatives.
On the client we do a basic multi-part form upload. This example is setup for a single image but you could call uploadFile in sequence for each image.
//client.ts
const uploadFile = (file: File | Blob) => {
const formData = new FormData();
formData.append("image", file);
return fetch("/upload", {
method: "post",
body: formData,
});
};
const handleUpload = (event: any) => {
return event.target.files.length ? uploadFile(event.target.files[0]) : null;
};
On the server we can use multer to read the file without persisting it to disk.
//server.js
const express = require("express");
const app = express();
const multer = require("multer");
const upload = multer();
app.post(
"/upload",
upload.fields([{ name: "image", maxCount: 1 }]),
(req, res, next) => {
console.log("/upload", req.files);
if (req.files.image.length) {
const image = req.files.image[0]; // { buffer, originalname, size, ...}
// Pipe the image.buffer where you want.
res.send({ success: true, count: req.files.image.originalname });
} else {
res.send({ success: false, message: "No files sent." });
}
}
);
For larger uploads I recommend socket.io, but this method works for reasonably sized images.
it is possible, but when you have a lot of traffic it would overwhelm your express server (in case you are uploading videos or big files ) but if it's for uploading small images (profile image, etc...) you're fine. either way you can use Multer npm
I'd recommend using client-side uploading on ex: s3-bucket, etc..., which returned a link, and therefore using that link.

Downloading the file from server but still getting error 'Cannot fetch' and not going inside fetch.then function

This is the error I am getting while download a file from node server.
and the file is downloading but the name is not correct what I am providing because the fetch is not accessing it's response section.
This is the download file function that fetches the .pdf file from node server.
downloadFileHandler = async (name, path) => {
this.setState({ cvDownloadLoading: true });
try {
const response = await fetch(
`${process.env.REACT_APP_SERVER_URL}/${path}`
);
if (!response.ok) throw response;
const buffer = await response.arrayBuffer();
const url = window.URL.createObjectURL(new Blob([buffer]));
const element = document.createElement('a');
element.style.display = 'none';
element.href = url;
element.setAttribute('download', name);
document.body.appendChild(element);
element.click();
this.setState({ cvDownloadLoading: false });
window.URL.revokeObjectURL(element.href);
document.body.removeChild(element);
} catch (err) {
this.setState({ cvDownloadLoading: false });
if (err.name === 'AbortError') {
} else {
try {
// const body = await err.json();
console.log(err);
} catch (e) {
console.log('catch', e);
}
}
}
};
The backend node has 'cors' installed and app is using this.
const cors = require('cors');
app.use(cors());
And serving files statically like this.
app.use('/data', express.static(path.join(__dirname, 'data')));
EDITED:
Now I tried to download image file and it downloaded without any issue.
It is a problem with PDF downloads or any file not image (maybe).
Can anyone explain?
I think you must to config your cors settings and add your client domain in the origin field. Try like this:
const corsOptions = {
origin: 'http://localhost:8080'
};
// and then
app.use(cors(corsOptions));
You can see this exapmle in Configuring CORS section here
https://expressjs.com/en/resources/middleware/cors.html

Vuejs load image from nodejs res.sendFile

In my vue's created, I use axios to connect to my server to retrieve an image as below:
created() {
this.schoolID = localStorage.getItem('schoolID')
console.log(this.schoolID)
axios.get(process.env.MY_URL + '/public/logo', {
params: {
schoolID: this.schoolID
}
})
.then(response => {
this.logo = response.data
console.log(this.logo)
})
.catch(e => {
console.log(e.response)
this.errors.push(e)
})
},
and my nodejs will receive the request and send the response like below
router.get('/logo', function(req, res){
School.findOne({ _id: mongoose.mongo.ObjectId(req.query.schoolID) }).exec().then(function (data) {
if (!data){
console.log("error... no logo found for the given id: " + req.query.schoolID)
return res.status(200).send({success: false, msg: 'No logo found.'});
}else{
res.sendFile(__dirname + '/uploads/' + data.logo);
}
});
});
my image should be loaded into my code
<img :src="this.logo">
I'm positive I got (at least the nodejs) the image file correctly because if I just append some string at the end of data.logo, I will get 404 not found error in my Vue and ENOENT: no such file or directory, stat 'D:\web\musleh-api\uploads\1542208727664_logo_sdam.png' error in my nodejs
However, no image is being loaded,and my console.log(this.logo) will display funny symbols which I assume is the image file if we try to open it using any text editor.
What did I miss?
I believe what you need to do is this in your HTML code, assuming that your image is base64 encoded:
<img alt="Awesome logo" :src="'data:image/png;base64,'+this.logo" />
Here is the source
Addition, as above seems not to work:
After some research I think we are not completely on the wrong path here, but give this a try:
On the NodeJS side try this:
}else{
var img = fs.readFile('__dirname + '/uploads/' + data.logo', function (err, data) {
var contentType = 'image/png';
var base64 = Buffer.from(data).toString('base64');
base64='data:image/png;base64,'+base64;
res.send(base64);
});
});
});
then in VueJS as before:
<img alt="Awesome logo" :src="this.logo" />
OR THE EASY WAY:
On the NodeJS side try this:
}else{
// prefix your domain if your API serves to other domains
res.send('/uploads/' + data.logo');
});
});
});
Also here Vuejs code remains as you did it.
Assuming that you want to pass the school ID to request the image, you don't need axios to make the request, you can use directly the <img src> tag with a dynamic parameter to request the data. You also need to change you express configuration to return the image with Content-Type:'image/png' or whatever is the mime type of your data.
Then on vue template you will need to pass the school id to the dynamic route, the data request and handling will be done by the img element
<img :src="`/logo/${schoolID}">
.
router.get('/logo/:schoolID', function(req, res){
School.findOne({ _id: mongoose.mongo.ObjectId(req.params.schoolID) }).exec().then(function (data) {
if (!data){
console.log("error... no logo found for the given id: " + req.params.schoolID)
return res.status(200)
.send({success: false, msg: 'No logo found.'});
}else{
res.type('png')
.sendFile(__dirname + '/uploads/' + data.logo);
}
});
});

Resources