Upload image from React Native to Apollo Server not working? - node.js

I have created my backend with Nodejs and Apollo Server. I am trying to upload an image from my React Native client. Here is my backend code
Mutation: {
async singleUpload(_, { fileInput: { file, fileName } }) {
try {
const { createReadStream, filename, mimetype } = await file;
const stream = createReadStream();
const path = `./upload/${filename}`;
const tempFile = { id: 123, filename, mimetype, path };
await new Promise((resolve, reject) => {
const writeStream = createWriteStream(path);
// When the upload is fully written, resolve the promise.
writeStream.on("finish", resolve);
// If there's an error writing the file, remove the partially written file
// and reject the promise.
writeStream.on("error", (error) => {
unlink(path, () => {
reject(error);
});
});
// In node <= 13, errors are not automatically propagated between piped
// streams. If there is an error receiving the upload, destroy the write
// stream with the corresponding error.
stream.on("error", (error) => writeStream.destroy(error));
// Pipe the upload into the write stream.
stream.pipe(writeStream);
});
...then upload it to Firebase Storage
When I use Altair plugin for Firefox browser to upload image , it works fine.
Now for my React Native client, I am using apollo-upload-client lib. After a user picks image using the Image Picker plugin for React Native , I am creating a file
const file = new ReactNativeFile({
uri: image.uri,
type: image.type,
name: 'Hi there',
});
ReactNativeFile is from apollo-upload-client lib. Now when I upload it I get an error saying from my backend saying TypeError: createReadStream is not a function which I understand as there is no createReadStream parameter in my ReactNativeFile
so I decided to change my backend code to something like this
const { name, type } = await file;
console.log("Mime" + type);
const stream = fs.createReadStream(name);
fs module is from nodejs
Now when I try uploading image from Altair or from React Native , I get an error saying
[Error: ENOENT: no such file or directory,
Due to my lack of knowledge of backend I am not sure what is causing the issue.
I think if I try uploading it from ReactJS instead of React Native, my code might work for the backend.
But the image picker for browser and native mobile are very much different and I have not tried doing with ReactJS for now
I am following this article https://moonhighway.com/how-the-upload-scalar-works for my backend code

Related

How to upload images from Next.js to Sanity

I have been working on a project using next.js and sanity but I can't seem to find a way to upload images to Sanity. I have been following the Sanity tutorial on how to upload assets and it works only if I set the filepath manually or if the assets are in the same project folder.
Below is the sanity method I have been using the upload the files.
const filePath = '/Users/mike/images/bicycle.jpg'
client.assets
.upload('image', createReadStream(filePath), {
filename: basename(filePath)
})
.then(imageAsset => {
return client
.patch('some-document-id')
.set({
theImageField: {
_type: 'image',
asset: {
_type: "reference",
_ref: imageAsset._id
}
}
})
.commit()
})
.then(() => {
console.log("Done!");
})
The main issue for me is the onchange handler returns a fake path which I understand is due to browser security and for images to successfully upload there should be an actual filepath like C:/Users/user/downloads/image.jpg instead of C:/fakepath/image.jpg
I have also attempted to change the onChange handler to get the filename only but I still can't upload the images because of the filepath issue.
const [image, setImage] = React.useState(null)
function handleImage(e){
const selectedFile = e.target.files[0]
if(selectedFile){
return setImage(selectedFile.name)
}
}
I have tried to use formidable but I didn't succeed as well. Please assist with a method on how to upload images.

How to append file in formData object without physically saving file in Node.js?

I am trying to work on an application to upload a file from react to NodeJs. Once upload running few validations from multer and then again sending the file as part of multipart/form-data to another API. I am failing to use the uploaded file to append in the formData as below.
router.post("/upload", uploadAndScan.any("filename"), (req, res) => {
scanFiles(req.files[0])
.then((data) => {
global.logger.info("scanned successfully.....");
res.status(200);
})
.catch((err) => {
global.logger.info("scan api ended with errors", err);
res.status(400).send({ error: err.message })
});
});
My scanFiles function looks like
import FormData from "form-data";
export function scanFiles(file) {
return new Promise((resolve, reject) => {
const hrstart = process.hrtime();
const formData = new FormData();
formData.append("file", file), "sample_pdf.pdf"); // Getting error in this line
I am getting an error for the last line as it is expecting a usvstring or Blob but I am unable to provide any. I cannot save the file to disk as it may have some virus. I am looking to see if there is a way to convert either file to Blob or some other way to append the file in the formData.
You'll find the file Blob/Buffer in file.buffer if you're using multer with MemoryStorage (which is the default). All available file properties are documented here: https://github.com/expressjs/multer#file-information.
This Request Parsing in Node.js Guide (it's free) will help you with file uploads in Node.js.

Upload images into a file server and store the url to the image using nodejs

I am implementing a web app using MEAN Stack and Angular 6. There I want to submit a form with file upload. '.png' files should be uploaded.
I want to save the file in a different file server and send the url to the image.Currently I upload files into a folder in my project and save the image in db (I used ng2fileupload and multer for that.). Then it saves like this.
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAV4AAAFUCAYAAABssFR8AAAK..."
But I want to save the image url and the image should be retrived by the url. Does anyone can explain a proper method for that?
I faced the same problem a month ago and find out a solution to this problem. Though I haven't used multer in the app.
From my frontend, I will be sending an object to Node API endpoint /event which will look like:-
let img = {
content: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
filename: 'yourfile.png'
}
At the backend, I'm using Cloudinary to store my images (Its free plan allows 10GB storage) and returns secure https URLs. So install it using npm i cloudinary and require in your api.js file.
And add the below configuration
cloudinary.config({
cloud_name: 'yourapp',
api_key: 'YOUR_KEY',
api_secret: 'YOUR_SECRET_KEY'
});
Last Step:- (Not so optimized code)
Let say I have an event Schema which has images array, where I'll be storing the URLs returned by cloudinary.
app.post('/event', (req, res) => {
try {
if (req.body.images.length > 0) {
// Creating new Event instance
const event = new Event({
images: [],
});
// Looping over every image coming in the request object from frontend
req.body.images.forEach((img) => {
const base64Data = img.content.split(',')[1];
// Writing the images in upload folder for time being
fs.writeFileSync(`./uploads/${img.filename}`, base64Data, 'base64', (err) => {
if (err) {
throw err;
}
});
/* Now that image is saved in upload folder, Cloudnary picks
the image from upload folder and store it at their cloud space.*/
cloudinary.uploader.upload(`./uploads/${img.filename}`, async (result) => {
// Cloudnary returns id & URL of the image which is pushed into the event.images array.
event.images.push({
id: result.public_id,
url: result.secure_url
});
// Once image is pushed into the array, I'm removing it from my server's upload folder using unlinkSync function
fs.unlinkSync(`./uploads/${img.filename}`);
// When all the images are uploaded then I'm sending back the response
if (req.body.images.length === event.images.length) {
await event.save();
res.send({
event,
msg: 'Event created successfully'
});
}
});
});
}
} catch (e) {
res.status(400).send(e);
}
});
P.S. Go ahead and suggest some optimization solution for this code here

Stream node requests to the cloud with file metadata

Im using koa in order to build a web app, and I want to allow users to upload files to it. The files need to be streamed to the cloud, but I would like to avoid saving the file locally.
The problem is that I need some file metadata before I pipe the upload stream to the writeable stream. I want to have the mime-type and optionally attach other data like the original file name etc.
I tried sending the binary data with the request's "content-type" header set to the file's type, but I would like the request to have the content type application/octet-stream so I can know in the back-end how to handle the request.
I read somewhere that the better option would be to use multipart/form-data but I'm not sure how to structure the request, and how to parse the metadata in order to notify the cloud before I pipe to its write stream.
Here is the code im currently using. Basically, it just pipes the request as is, and I use the request header to know the type of the file:
module.exports = async ctx => {
// Generate a random id that will be part of the filename.
const id = pushid();
// Get the content type from the header.
const contentType = ctx.header['content-type'];
// Get the extension for the file from the content type
const ext = contentType.split('/').pop();
// This is the configuration for the upload stream to the cloud.
const uploadConfig = {
// I must specify a content type, or know the file extension.
contentType
// there is some other stuff here but its not relevant.
};
// Create a upload stream for the cloud storage.
const uploadStream = bucket
.file(`assets/${id}/original.${ext}`)
.createWriteStream(uploadConfig);
// Here is what took me hours to get to work... dev life is hard
ctx.req.pipe(uploadStream);
// return a promise so Koa doesn't shut down the request before its finished uploading.
return new Promise((resolve, reject) =>
uploadStream.on('finish', resolve).on('error', reject)
);
};
Please assume I don't know much about the uploading protocols and managing streams.
Ok so after a lot of searching I found out that there is a parser that works with streams called busboy. It is pretty easy to use, but before jumping into the code I highly suggest everyone dealing with multipart/form-data requests to read this article.
Here is how I solved it:
const Busboy = require('busboy');
module.exports = async ctx => {
// Init busboy with the headers of the "raw" request.
const busboy = new Busboy({ headers: ctx.req.headers });
busboy.on('file', (fieldname, stream, filename, encoding, contentType) => {
const id = pushid();
const ext = path.extname(filename);
const uploadStream = bucket
.file(`assets/${id}/original${ext}`)
.createWriteStream({
contentType,
resumable: false,
metadata: {
cacheControl: 'public, max-age=3600'
}
});
stream.pipe(uploadStream);
});
// Pipe the request to busboy.
ctx.req.pipe(busboy);
// return a promise that resolves to whatever you want
ctx.body = await new Promise(resolve => {
busboy.on('finish', () => {
resolve('done');
});
});
};

Upload base64 encoded jpeg to Firebase Storage (Admin SDK)

I am trying to send a picture from my mobile hybrid app (Ionic 3) to my Heroku backend (Node.js) and have the backend upload the picture to Firebase Storage and return the newly uploaded fil download url to the mobile app.
Keep in mind that I am using the Firebase Admin SDK for Node.js.
So I send the base64 encoded image to Heroku (I check the encoded string with an online base64 decoder and it is alright) which is handle by the following function:
const uploadPicture = function(base64, postId, uid) {
return new Promise((resolve, reject) => {
if (!base64 || !postId) {
reject("news.provider#uploadPicture - Could not upload picture because at least one param is missing.");
}
let bufferStream = new stream.PassThrough();
bufferStream.end(new Buffer.from(base64, 'base64'));
// Retrieve default storage bucket
let bucket = firebase.storage().bucket();
// Create a reference to the new image file
let file = bucket.file(`/news/${uid}_${postId}.jpg`);
bufferStream.pipe(file.createWriteStream({
metadata: {
contentType: 'image/jpeg'
}
}))
.on('error', error => {
reject(`news.provider#uploadPicture - Error while uploading picture ${JSON.stringify(error)}`);
})
.on('finish', (file) => {
// The file upload is complete.
console.log("news.provider#uploadPicture - Image successfully uploaded: ", JSON.stringify(file));
});
})
};
I have 2 major issues:
Upload succeeds but I when I go to Firebase Storage console, there is an error when I try to display the preview of the picture and I cannot open it from my computer when I download it. I guess it is an encoding thing....?
How can I retrieve the newly uploaded file download url ? I was expecting an object to be returned in the .on('finish), like in the upload() function, but none is returned (file is undefined). How could I retrieve this url to send it back in the server response?
I want to avoid using the upload() function because I don't want to host files on the backend as it is not a dedicated server.
My problem was that I add data:image/jpeg;base64,at the beginning of the base64 object string ; I just had to remove it.
For the download url, I did the following:
const config = {
action: 'read',
expires: '03-01-2500'
};
let downloadUrl = file.getSignedUrl(config, (error, url) => {
if (error) {
reject(error);
}
console.log('download url ', url);
resolve(url);
});

Resources