Uploding a profile picture in a React native Expo Application - node.js

So, I have an expo application that the user can set his profile picture, but I'm having trouble to store the picture in my backend.
I'm using Node.js in the backend and i have 2 libraries which are fs-extra and path.
So When a user set his profile picture, what i get in the backend is this file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540motaa%252FNatureApp/ImagePicker/783259e0-e3b5-41ee-8fd7-e03fd54425a0.jpg
And i have tried to use a form to transfer the file but i couldn't get it to work so i stuck with this.
But now i don't know how to extract the picture so i can store it on my backend or even in my database.
This is the code i'm using to store the picture in a directory in the backend:
const { image } = req.body;
console.log(image);
const newPath = path.resolve(__dirname, "../PostImages") + "/" + "image.jpg";
await fs.move(image, newPath);
Frontend Code:
const image = result;
const data = new FormData();
data.append("profilePicture", {
name: "hello.jpg",
type: image.type,
uri:
Platform.OS === "android"
? image.uri
: image.uri.replace("file://", ""),
});
uploadUserImage({ data });
Request code:
const uploadUserImage = (dispatch) => {
return async ({ data }) => {
console.log(data);
const token = await AsyncStorage.getItem("token");
try {
await axios({
method: "post",
url: "http://localhost:5000/userImg",
headers: {
Authorization: `Bearer$${token}`,
"Content-Type": "multipart/form-data",
},
body: data,
});
} catch (err) {
console.log(err);
}
};
};

You can use multer which is great to save images on backend. If you are passing the form data you can use it, this is how you can add it:
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" />
</form>
var express = require('express')
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
var app = express()
app.post('/profile', upload.single('nameofImage'), function (req, res, next) {
// req.file is the `avatar` file
// req.body will hold the text fields, if there were any
})
For more details, you can check here.

For React Native/Expo projects, to upload/send a file to the backend you need to create a formData and use it in the Body of the request, like this
const formData = new FormData();
formData.append('FileData', {
name: 'FileData.jpg',
uri: 'file:///data/user/0/host.exp.exponent/cache/ExperienceData/%2540motaa%252FNatureApp/ImagePicker/783259e0-e3b5-41ee-8fd7-e03fd54425a0.jpg',
type: 'image/jpg', // mime type of file
});
Now perform a request to the backend with this formData in the body
Request Code would be
const uploadUserImage = (dispatch) => {
return async ({ data }) => {
console.log(data);
const token = await AsyncStorage.getItem("token");
try {
await axios({
method: "post",
url: "http://localhost:5000/profile", // make sure URL is correct
headers: {
Authorization: `Bearer$${token}`,
"Content-Type": "multipart/form-data",
},
body: data,
});
} catch (err) {
console.log(err);
}
};
};
And in the backend access the file as shown below
var express = require('express')
var multer = require('multer')
const upload = multer({ storage: multer.memoryStorage() });
var app = express()
app.post('/profile', upload.single('FileData'), function (req, res, next) {
// access it with --> req.file
})

Related

busyboy not piping file to specified folder in node js (windows)

I am trying to upload mp4 files using typescript, node js and mongodb but it is not working.
I am using busboy library for the file upload
I am using postman to test my api
when I send the file I receive the file on busboy
I can display the file info like the filename
but the file does not move to the new location
video.controller.ts
const MIME_TYPES = ["video/mp4"];
function getPath({
videoId,
extension,
}: {
videoId: Video["videoId"];
extension: Video["extension"];
}) {
return `${process.cwd()}/videos/${videoId}.${extension}`;
}
export async function uploadVideoHandler(req: Request, res: Response) {
const bb = busboy({ headers: req.headers });
const user = res.locals.user;
const video = await createVideo({ owner: user._id });
console.log(video);
bb.on("file", async (_, file, info) => {
const extension = info.mimeType.split("/")[1];
const filePath = getPath({
videoId: video.videoId,
extension,
});
video.extension = extension;
const { filename } = info;
console.log("filename ", filename);
await video.save();
console.log(filePath);
const stream = fs.createWriteStream(filePath);
file.pipe(stream);
});
bb.on("close", () => {
res.writeHead(StatusCodes.CREATED, {
Connection: "close",
"Content-Type": "application/json",
});
res.write(JSON.stringify(video));
res.end();
});
return req.pipe(bb);
}
Api route
const router = express.Router();
router.post("/", requireUser, uploadVideoHandler);
export default router;

How can I save an image sent from my NextJS client to my NodeJS server?

I was trying a few weeks ago to upload an image to my NodeJS server and, from there, save it to an S3 bucket, but I'm stuck on the first part and my server doesn't receive the image. I'm using multer for saving it in the public/ folder.
This is what I have in my client:
const onSubmit = async (data: any) => {
const image = data.file[0];
const params: RequestInit = {
method: 'POST',
headers: {
'Content-Type': `multipart/form-data`,
'Accept': 'image/*'
},
body: JSON.stringify(data)
};
await fetch('/api/post/create-post', params);
// Upload image to S3 Bucket
// Save post to db
}
I was setting a boundary in the Content-Type with data=${JSON.stringify(data)}, and when I log the headers of my request before trying to save the image, there is no "boundary" and instead I receive this error Error: Multipart: Boundary not found (This error is shown in my server console)
And this is what I have in my server:
const upload = multer({
dest: 'public/'
})
const router = express.Router();
router.post('/', (req, res, next) => {
console.log(req.headers);
next();
}, upload.single('photo'), (req, res) => {
console.log(req.file);
res.json({ success: true });
})
module.exports = router;
You can remove the content type from the Axios headers and let Axios set it by itself.
const onSubmit = async (data: any) => {
const image = data.file[0];
const params: RequestInit = {
method: 'POST',
body: JSON.stringify(data)
};
await fetch('/api/post/create-post', params);
// Upload image to S3 Bucket
// Save post to db
}
You can check this link for the boundaries error.

Uploading images to google cloud storage with multer and nodejs getting error

== updated question on 9/9 ===
Tried to use Multer directly without the Middleware like before and using Postman to upload the images.
From Nodejs, req return
files: [Object: null prototype] {
imagebackup: [ [Object] ],
imagebanner: [ [Object] ]
},
However, when I console req.file
it showing "undefined"
new file-routers.js as below:
const express = require('express');
const multer = require('multer');
const router = express.Router();
const upload = multer({
storage: multer.MemoryStorage,
}).fields([{name: "imagebackup"}, {name: "imagebanner"}]);
router.post('/file', (req, res)=>{
upload(req, res, (err) => {
console.log(req) // return Files [object null]
console.log(req.file) // return "undefined"
if(err) throw err;
})
});
**Weird thing is, by using upload.single(), everything works just fine. **
==
==
===== Here is the old code & can't solve it =====
It return an error
MulterError: Unexpected field
at wrappedFileFilter (C:\Users\carchaw\Documents\pfx_template_generator_api\node_modules\multer\index.js:40:19)
at Busboy.<anonymous> (C:\Users\carchaw\Documents\pfx_template_generator_api\node_modules\multer\lib\make-middleware.js:114:7)
at Busboy.emit (node:events:379:20)
On the form submit, I need upload 2 images from different input field, create new prefix on GCS, and also store the image's name and other's details to be sent in request.body.
From the front-end part, I using Fetch as below:
const getFormContianer = document.getElementById("get_form")
async function handleForm(e) {
e.preventDefault();
let dataForm = new FormData(e.target)
await fetch(file_api, {
method: 'POST',
body: dataForm
}).then((res)=>{
return res.json();
}).then((data)=>{
console.log('api err: '+data);
}).catch((err) =>{
console.log('api err: '+ err)
})
}
getFormContianer.addEventListener('submit', handleForm)
index.html
<form id="get_form">
<label for="video_url">video_url</label>
<input name="video_url" type="text" id="video_url" value=""><br>
<label for="image_backup">image_backup</label>
<input name="image_backup" type="file" id="image_backup" value=""><br>
<label for="image_banner">image_banner</label>
<input name="image_banner" type="file" id="image_banner" value=""><br>
</form>
<input type="submit" id="handle_submit">
Nodejs
multer middleware
const util = require("util");
const multer = require("multer");
let processFile = multer({
storage: multer.memoryStorage()
}).fields([{ name: "image_backup" }, { name: "image_banner" }])
let processFileMiddleware = util.promisify(processFile);
module.exports = processFileMiddleware;
handling Upload
const handleUploadImages = async (req, res) =>{
try {
await processFile(req, res);
if (!req.file) {
return res.status(400).send({ message: "Please upload a file!" });
}
// Create a new blob in the bucket and upload the file data.
const blob = bucket.file(newFolderPath + req.file.originalname);
const blobStream = blob.createWriteStream({
resumable: false,
});
blobStream.on("error", (err) => {
res.status(500).send({ message: err.message });
});
blobStream.on("finish", async (data) => {
// Create URL for directly file access via HTTP.
const publicUrl = format(
`https://storage.googleapis.com/${bucket.name}/${newFolderPath}/${blob.name}`
);
try {
// Make the file public
await bucket.file(newFolderPath + req.file.originalname).makePublic();
} catch {
return res.status(500).send({
message:
`Uploaded the file successfully: ${newFolderPath + req.file.originalname}, but public access is denied!`,
url: publicUrl,
});
}
res.status(200).send({
message: "Uploaded the file successfully: " + newFolderPath + req.file.originalname,
url: publicUrl,
});
});
blobStream.end(req.file.buffer);
} catch (err) {
res.status(500).send({
message: `Could not upload the file: ${req.file.originalname}. ${err}`,
});
}
}
I did use express json and urlencoded on index.js
const express = require('express');
const cors = require('cors');
const config = require('./config')
const app = express()
const templates = require('./routes/templates-routes');
const files = require('./routes/files-routes');
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'))
app.use('/api', templates.routes);
app.use('/create', files.routes);
app.listen(config.port, () => {
console.log(`Example app listening at http://localhost:${config.port}`)
})
Hope that can get some suggestion on this, thank you!
Where is body: dataForm declared? That error arises when you try to upload a field that is not mentioned in fields:
fields([{ name: "image_backup" }, { name: "image_banner" }])
Make sure your multipart form has these 2 fields only for uploading files.
I suggest you to check this post in which they discuss the same issue.
In order to solve it they basically formatted the file that need to be uploaded.
formData.append("type_of_the_file", uploadfile);
Finally solved the issue after few days keep trying.
for front-end POST rest API, passing files by appending file itself and the name.
function handleForm(e) {
e.preventDefault();
let dataForm = new FormData(e.target)
dataForm.append("image_backup", document.getElementById("image_backup").files[0]);
dataForm.append("image_banner", document.getElementById("image_banner").files[0]);
dataForm.append("image_banner_name", document.getElementById("image_banner").value.replace(/^.*[\\\/]/, ''));
dataForm.append("image_backup_name", document.getElementById("image_backup").value.replace(/^.*[\\\/]/, ''));
await fetch(file_api, {
method: 'POST',
body: dataForm
}).then((res)=>{
return res.json();
}).then((data)=>{
console.log('api data: '+ data);
}).catch((err) =>{
console.log('api err: '+ err)
})
}
on Nodejs
const multer = require('multer');
const upload = multer({
storage: multer.MemoryStorage,
}).fields([{name: "image_backup"}, {name: "image_banner"}]);
const startCreatefiles = async(req, res, next) =>{
upload(req, res, (err) => {
console.log(req.body);
console.log(req.files);
})
}
Then successfully get the text form data and file itself.

Fetch /POST request to Server: Empty array when receiving multipart/form-data;

Im trying to upload an image (ReactJs) to my server (NodeJs+ Express+ multerJs) to digital Ocean.
Im using a POST Request with multipart/form-data;
I get a succesfull message from multerJs but I can see in the log on the server that the files: [] has an empty array. And that Digital Ocean didnt add any file of course.
When i do the same Post request with the form without handling the submission of the form ,everything is working and the Files array is not empty.the code above is working :
<form method="post" enctype="multipart/form-data" action="http://localhost:3004/upload_image_test_4" > </form>
Client-Side React.js :
export default class Select_size extends React.Component {
on_form_submit = (e) => {
e.preventDefault();
const fileInput = this.state.file;
const formData = new FormData();
formData.append('file', fileInput);
const options = {
method: 'POST',
body: formData,
headers: {
'Access-Control-Allow-Origin': '*',
Accept: 'application/json',
'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'
}
};
var url =
this.state.action_link +
'?url=' +
this.state.id_user +
'/' +
this.state.id_draft +
'&name=' +
this.state.name_file;
fetch(url, options);
};
onChange = (e) => {
this.setState({ file: e.target.files[0] });
};
constructor(props) {
super(props);
this.state = {
files: [],
};
this.onChange = this.onChange.bind(this);
}
render() {
return (
<form onSubmit={this.on_form_submit} enctype="multipart/form-data">
<input type="file" name="myImage" onChange={this.onChange} />
<button type="submit">Upload</button>
</form>
)}
}
Server-side (Node.js/ ExpressJs/Multer/Digital Ocean) :
const express = require('express');
const router = express.Router();
const bodyParser = require('body-parser');
const aws = require('aws-sdk');
const spacesEndpoint = new aws.Endpoint('fra1.digitaloceanspaces.com');
const s3 = new aws.S3({
endpoint: spacesEndpoint,
accessKeyId: 'myID',
secretAccessKey: 'MySecretKey'
});
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'bucketName',
acl: 'public-read',
key: function(request, file, cb) {
cb(null, urlBucket + name_image);
}
})
}).array('upload', 1);
router.post('/upload_image_test_4', function(request, response, next) {
upload(request, response, function(error) {
console.log(request, 'the request');
if (error) {
console.log(error);
return response.json(error, 'error');
}
console.log('File uploaded successfully.');
return response.json('success its uploaded to digital ocean');
});
});
module.exports = router;
If anyone can help me with that ! (I already 'googled' insanely,,,)
Thanks !!
//EDIT I found a solution to my own question... on the github page dedicated to a bug related to Next.js :
The file was not send to the request for some reasons.
I used axios in this way and its now working :
React-js :
sendFile = (e) => {
const data = new FormData();
const file = e.target.files[0];
data.append('avatar', file);
axios
.post('http://localhost:3004/upload_test_stack_2', data)
.then(console.log)
.catch(console.error);
}
The server side (nodeJs) :
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'My Bucket',
acl: 'public-read',
key: function(request, file, cb) {
cb(null, file.originalname);
}
})
});
router.post('/upload_test_stack_2', upload.single('avatar'), (req, res)
=> {
console.log(req.file, 'The file appears here');
return res.sendStatus(200);
});
Files from input are not an array but FileArray, try [...e.target.files][0]
Related to Event Pooling in React and async behavior of setState.
Modify onChange handler:
Method 1:
Put the file to variable and set it in setState.
onChange = (e) => {
const uploadFile = e.target.files[0] ;
this.setState({ file: uploadFile });
};
Method 2 with event.persist():
onChange = (e) => {
e.persist();
this.setState({ file: e.target.files[0] });
};

how to upload file saved in memory by multer to another API

I have 2 separate NodeJS APIs that uses multer to save a file in memory.
My express middleware looks like this
import multer from 'multer';
const storage = multer.memoryStorage();
export default multer({ storage }).single('image');
I am able to receive the file which is saved in memory successfully so my req.file.image looks like this
{
fieldname: 'image',
originalname: 'image-2017-08-28-13-47-31-218.png',
encoding: '7bit', mimetype: 'image/png',
buffer: <Buffer 89 50 4e 47 0d 0a 1a 0 ... >,
size: 493181
}
After receiving the file, on the first API, I need to send it to the second API that also uses multer & express
function secondApiReceiveImage(req, res) {
console.log(req.file)
console.log(req.files)
console.log(req.body)
res.send('ok');
}
I tried sending using the following implementations
Via https://github.com/request/request#multipartform-data-multipart-form-uploads
import request from 'request';
function firstApiReceiveImage(req, res) {
const options = {
url:'http://SECOND_API/api/image',
formData: { image: req.file.buffer }
};
request.post(options, (err, httpResponse, body) => {
console.log('err', err);
console.log('body', body);
});
}
In this case, logs of req.file, req.files and req.body are all undefined on secondApiReceiveImage API handler function
My next try was with https://github.com/form-data/form-data
import multer from 'multer';
const storage = multer.memoryStorage();
export default multer({ storage }).single('image');
function firstApiReceiveImage(req, res) {
const CRLF = '\r\n';
const form = new FormData();
const opts = {
header: `${CRLF} + '--' + ${form.getBoundary()} + ${CRLF} + 'X-Custom-Header: 123' + ${CRLF} + ${CRLF}`,
knownLength: 1
};
form.append('image', req.file.image.buffer, opts);
form.submit('http://SECOND_API/api/image', (err, res) => {
console.log('err', err);
console.log('res', res);
});
}
I got the same result, undefined for req.file, req.files & req.body on the second API
These are my middlewares for both APIs aside from multer BTW
app.use(compression());
app.use(helmet());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
I could have had an easier life if I can persist the file on the first API, but we are not allowed to save to disk in this case :(
Any advice for me?
I've stumbled upon the same issue and here is what I've managed to come up with.
First of all I used the form-data package to mimic the FormData data structure on the server side and here is the util file that does that.
const FormData = require('form-data')
const formData = new FormData()
const config = {
headers: {
'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`
}
}
export default { data: formData, config }
and here is my node.js api file
import formSet from '../utils/formData'
const multer = require('multer')
const upload = multer()
const { Router } = require('express')
const router = Router()
......
router.post('/api/images', upload.single('image'), async (req, res) => {
formSet.data.append('image', req.file.buffer, { filename: req.file.originalname })
formSet.data.append('other_data', 'value')
const response = await axios.post('/images', formSet.data, formSet.config)
.......
})
The team was able to get through this by converting the buffer to Stream first and send form data differently.
import stream from 'stream';
import rq from 'request-promise';
const { Duplex } = stream;
function bufferToStream(buffer) {
const duplexStream = new Duplex();
duplexStream.push(buffer);
duplexStream.push(null);
return duplexStream;
}
async function store({
originalname, mimetype, size, buffer,
}) {
logger.debug(`Saving image name:${originalname} type:${mimetype} size:${size}`);
const formData = {
image: {
value: bufferToStream(buffer),
options: {
filename: originalname,
contentType: mimetype,
knownLength: size,
},
},
};
const options = Object.assign({}, IMAGE_SAVE_ENDPOINT, { formData });
const response = await rq(options);
return response.id;
}
Using Request, formdata should be set to the buffer:
formData: { image: req.file.buffer }
I've found the way to upload files with multer through buffer and not storing locally...
Here is my code....
var multer = require('multer');
var storage= multer.memoryStorage();
var upload = multer({storage: storage});
var cloudinary = require('cloudinary');
cloudinary.config({
cloud_name: 'your-cloud-name',
api_key: process.env.CLOUDINARY_API_KEY, //your api-key
api_secret: process.env.CLOUDINARY_API_SECRET //your api-secret
});
Configure your cloudinary and multer as above.....
Get the buffer from req as follows..
router.post("/",upload.single('image'),function(req,res){
var buf = req.file.buffer.toString('base64');
// Upload code Here
)};
Upload code:
cloudinary.uploader.upload("data:image/png;base64," + buf, function(result) {
//your code here
console.log(result);
},{
folder: 'folder-name' //if you want to store in a folder
});
The output of result would be as follows:
{
asset_id: 'ed58484fb2bdce823f1b27d*******8',
public_id: '**************',
version: 1594455479,
version_id: '68450****88842ce0aa319603e68d3',
signature: '3785dbfc3**********883720b',
width: 750,
height: 500,
format: 'png',
resource_type: 'image',
created_at: '2020-07-11T08:17:59Z',
tags: [],
pages: 1,
bytes: 70846,
type: 'upload',
etag: 'd04dd691de532e941faccda846ef3d76',
placeholder: false,
url: 'http://res.cloudinary.com/*****************',
secure_url: 'https://res.cloudinary.com/*******************'
}

Resources