How to upload files to AWS S3 bucket from the react frontend - reactjs

Hello I am trying to upload images from my react frontend but its unable to read the file in axios and the images are not being uploaded.
This is my code.
<form onSubmit={Upload}>
<input
type='file'
onChange={(e) => {
generate(e.target.files);
}}
/>
<button type='submit'>Submit</button>
</form>
const generate = async (data) => {
const payload = {
entity: 'test',
userId: '200',
documentFileName: data[0].name,
action: 'putObject',
};
console.log('DAATA', data[0].name);
await api
.generatePreSignedUrl(payload, token)
.then((res) => {
setUploadUrl(res.data.data);
setImageData(data);
})
.catch((err) => {
console.log('Error', err);
});
};
const Upload = async (e) => {
e.preventDefault();
console.log('IMAGEdata', imageData);
console.log('URL', uploadUrl);
let file = imageData[0];
console.log('THIS IS FILE', file);
console.log('FILENAME', file.name);
console.log('FIELETYPE', file.type);
var options = {
headers: {
'Content-Type': file.type,
},
};
await axios
.put(uploadUrl, file, options)
.then((res) => {
console.log('Response from s3');
console.log(res);
})
.catch((error) => {
alert('ERROR ' + JSON.stringify(error));
});
};
Here imageUrl is the signed URL that I get from was to upload my files. I am successfully able to call the put request to upload the file but the image or file is not being read and uploaded.
This is the response when I am trying to look at the uploaded file.
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
<Key>documents/ama/test/200/displaypicture.png</Key>
<RequestId>R7M63AHQKDNC6NH5</RequestId>
<HostId>3u8Jb8Ev4S1s/ODicL3Py4hvrvwKKOs/5zGxUEDYORfN1+U37Zx8PfYvIcGWUIAfeCqHpWqXd8o=</HostId>
</Error>

Look at the AWS SDK for JavaScript Developer Guide Version 3. There are examples that show how to upload photos to an Amazon S3 bucket.
Uploading photos to Amazon S3 from a browser

A pretty simple way to use this node module to achieve this:
https://www.npmjs.com/package/react-aws-s3
https://medium.com/#steven_creates/uploading-files-to-s3-using-react-js-hooks-react-aws-s3-c4c0684f38b3
enter image description here
npm install --save react-aws-s3

User " aws-sdk " and use this code
import React ,{useState} from 'react';
import AWS from 'aws-sdk'
const S3_BUCKET ='YOUR_BUCKET_NAME_HERE';
const REGION ='YOUR_DESIRED_REGION_HERE';
AWS.config.update({
accessKeyId: 'YOUR_ACCESS_KEY_HERE',
secretAccessKey: 'YOUR_SECRET_ACCESS_KEY_HERE'
})
const myBucket = new AWS.S3({
params: { Bucket: S3_BUCKET},
region: REGION,
})
const UploadImageToS3WithNativeSdk = () => {
const [progress , setProgress] = useState(0);
const [selectedFile, setSelectedFile] = useState(null);
const handleFileInput = (e) => {
setSelectedFile(e.target.files[0]);
}
const uploadFile = (file) => {
const params = {
ACL: 'public-read',
Body: file,
Bucket: S3_BUCKET,
Key: file.name
};
myBucket.putObject(params)
.on('httpUploadProgress', (evt) => {
setProgress(Math.round((evt.loaded / evt.total) * 100))
})
.send((err) => {
if (err) console.log(err)
})
}
return <div>
<div>Native SDK File Upload Progress is {progress}%</div>
<input type="file" onChange={handleFileInput}/>
<button onClick={() => uploadFile(selectedFile)}> Upload to S3</button>
</div>
}
export default UploadImageToS3WithNativeSdk;

Related

Post local image to facebook group via Graph API

I'm working on a React project with Facebook user integration.
I need to post a client-side generated image to a private group on behalf of the logged-in user.
Using the {group-id}/photo endpoint I can successfully post a picture which already exists on the web providing the url and caption parameters:
const postImageUrlToFacebook = () => {
const url = "https://upload.wikimedia.org/wikipedia/commons/e/e0/New.gif";
let caption = "test upload url image from React";
httpFacebookGraphClient(facebookToken)
.post("/" + ldlTestFacebookGroupId + "/photos", { url, caption })
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
};
The definition of the httpFacebookGraphClient is the following:
import axios, { AxiosRequestConfig } from "axios";
const httpFacebookGraphClient = (token: string | null) => {
const defaultOptions = {
baseURL: "https://graph.facebook.com/v14.0",
method: "get",
// withCredentials: true,
headers: {
"Content-Type": "application/json",
},
};
// Create instance
let instance = axios.create(defaultOptions);
// Set the access token parameter for any request
instance.interceptors.request.use((config: AxiosRequestConfig): AxiosRequestConfig => {
if (!config) {
config = {};
}
if (!config.params) {
config.params = {};
}
config.params.access_token = token;
config.params.limit = "999";
return config;
});
return instance;
};
export default httpFacebookGraphClient;
I would now need to start from a default svg, modifiy some g tags inside of it via javascript, convert to jpg and post it to a facebook group.
Let's suppose the starting SVG is the following:
<svg id="Livello_1" data-name="Livello 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 107"><defs><style>.cls-1,.cls-3,.cls-4,.cls-5{fill:#ffc000;}.cls-2{fill:#fff;}.cls-3,.cls-4,.cls-5{stroke:#fff;stroke-miterlimit:10;}.cls-3{stroke-width:0.87px;}.cls-4{stroke-width:0.79px;}.cls-5{stroke-width:0.65px;}</style></defs><title>bannerino scegli il tuo sconto</title><path class="cls-1" d="M136.88,2.63a10,10,0,0,1,10,10V94.37a10,10,0,0,1-10,10H13.13a10,10,0,0,1-10-10V12.63a10,10,0,0,1,10-10H136.88m0-2H13.13a12,12,0,0,0-12,12V94.37a12,12,0,0,0,12,12H136.88a12,12,0,0,0,12-12V12.63a12,12,0,0,0-12-12h0Z"/></svg>
I started from the end, trying to post a local image to the Facebook group before trying to build the image, but I'm already stuck.
Reading the Facebook api docs at this link I found this sentence on the url parameter;
The URL of a photo that is already uploaded to the Internet. You must specify this or a file attachment
mentioning a file attachment.
Again on the Facebook docs I found this that explain how to upload a file to Facebook to use it in subsequent api calls, but I can't make it to work.
Anyone could give me a hint on how to proceed?
I found the way around this and I can successfully post an image selected by input field client side.
React component state:
const [fileToUpload, setFileToUpload] = useState<any>();
inputFileChangeHandler:
const inputFileChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.currentTarget != null && event.currentTarget.files != null) {
setFileToUpload(event.currentTarget.files[0]);
}
};
uploadImageToFacebook:
const uploadImageToFacebook = () => {
let caption = "test upload local image from React";
const fileReader = new FileReader();
fileReader.onloadend = async () => {
if (fileReader.result != null) {
const photoData = new Blob([fileReader.result], { type: "image/png" });
const formData = new FormData();
formData.append("source", photoData);
formData.append("caption", caption);
httpFacebookGraphClient(facebookToken)
.post("/" + ldlTestFacebookGroupId + "/photos", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
console.log(res.data);
})
.catch((err) => {
console.log(err);
});
}
};
fileReader.readAsArrayBuffer(fileToUpload);
};
On jsx:
<input type="file" id="file-input" onChange={inputFileChangeHandler} />
<button onClick={uploadImageToFacebook}>Invia</button>

Upload multiple images to separate folder in Next Js [duplicate]

I have a task for uploading images using Next js. API for image uploading has been given and it exists in the same project and when it becomes successful images are stored in a separate directory.
API for image uploading is as follows,
import formidable from "formidable";
import path from "path";
import { renameSync } from "fs";
export const config = {
api: {
bodyParser: false,
},
};
export default async function upload(req, res) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const form = new formidable.IncomingForm({
keepExtensions: true,
maxFileSize: 200 * 1024 * 1024,
});
return new Promise((resolve, reject) => {
form.parse(req, async (err, fields, files) => {
if (err) {
res.status(500).json({ error: err.message });
return resolve();
}
if (!files.image) {
res.status(422).json({ error: "Bad request: missing image field" });
return resolve();
}
const ext = path.extname(files.image.path);
const uuid = Math.random().toString(26).slice(2);
await renameSync(files.image.path, `./public/images/${uuid}${ext}`);
res.status(201).json({
uuid,
url: `/images/${uuid}${ext}`,
});
return resolve();
});
});
}
Code for the front-end is mentioned below,
import React from "react";
export default function UploadImage() {
const [imgsSrc, setImgsSrc] = React.useState([]);
const uploadToClient = (e) => {
for (const file of e.target.files) {
setImgsSrc((imgs) => [...imgs, file]);
}
};
const uploadToServer = async () => {
let formData = new FormData();
for (const file of imgsSrc) {
formData.append('image', file, file.name)
}
const response = await fetch("/api/upload", {
method: "POST",
body: formData
});
};
return(
<div>
<h3>Upload photos</h3>
<input onChange={uploadToClient} type="file" name="file" multiple />
<div>
{imgsSrc.map((link) => (
<img src={link} style={{ width: 200, height: 200 }} />
))}
</div>
<button type="submit" onClick={uploadToServer}>Send to server</button>
</div>
)
}
When uploading images I am getting 'error - unhandledRejection: TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined'.
Any suggestion would be highly appreciated!

Better option for image upload (cloud) in NextJS

I'm currently developing my first real project for a client with NextJS and MongoDB and I'm having problems uploading images. I'm working with Cloudinary but it can't receive multiple files and I'm also having issues with state management because when the form is submitted my database doesn't receive the files whereas Cloudinary does.
The API works fine so I post here the code of the form (REACT).
export default function NewProduct() {
const initialState = {
title: "",
price: 0,
description: "",
content: "",
images: [],
category: "tortas",
};
const [product, setProduct] = useState(initialState);
const { title, price, description, content, category } = product;
const [files, setFile] = useState("");
//const handleChangeInput = (e) => {
// setProduct({ ...product, [e.target.name]: e.target.value });
//};
const handleUploadInput = async (e) => {
const uploadFiles = [...e.target.files];
setFile([...files, uploadFiles]);
};
const handleSubmit = async (e) => {
e.preventDefault();
const formData = new FormData();
for (let file of files) {
formData.append("file", file);
}
formData.append("upload_preset", "balbla");
const res = await fetch(
"https://api.cloudinary.com/v1_1/blabla/image/upload",
{
method: "POST",
body: formData,
}
);
const data = await res.json();
setProduct((p) => ({ ...p, images: data.secure_url}));
await createProduct();
setProduct(initialState);
};
const createProduct = async () => {
try {
const res = await fetch("http://localhost:3000/api/products", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(product),
});
const data = await res.json();
console.log(data);
} catch (err) {
console.log(err);
}
};
return (
<Layout>
<div className={styles.formDiv}>
<form className={styles.form} onSubmit={handleSubmit}>
<input
type="file"
name="file"
onChange={handleUploadInput}
multiple
accept="image/*"
/>
<button type="submit">Crear</button>
</form>
</div>
</Layout>
);
}
In case using Cloudinary isn't the best option with NextJS, what other cloud or stuff I could use?
I hope I made myself clear.
Thank you in advance.
The Cloudinary Upload API unfortunately doesn't support uploading multiple resources within a single request, so you would need to likely loop through all of your media items and upload them each individually.
As far as the production creation is concerned, I can't tell for sure, but you may be trying to create the product before your product state is updated.
Inside createProduct have you checked to see if all of the data you expect is available at the time it's being ran?
You could try listening to updates to the product state and create a product based off of that with a useEffect hook, for instnace:
useEffect(() => {
// Check if product is available or perform
// a check that you know isn't ready to create yet
if ( !product ) return;
(async function run() {
await createProduct(product);
setProduct(initialState);
})()
}, [product])

How do I make an image stored in mongodb with multer, show on the page when requesting it from server?

So I have successfuly used multer to load images on mongodb, but the problem is that I dont know how to make them show on the page.
const [selectedFile, setSelectedFile] = React.useState(getUser().avatar);
// getUser() function is returning the logged user from localStorage
const fileSelectedHandler = evt => setSelectedFile(evt.target.files[0]);
const uploadProfileImage = async (evt) => {
evt.preventDefault();
const formData = new FormData();
formData.append('image', selectedFile);
const config = {
headers: {
'content-type': 'multipart/form-data',
},
};
await axios.put(`http://localhost:8080/avatar/${id}`, formData, config)
.then(response => {
localStorage.setItem(localStorageName, JSON.stringify({ ...getUser(), avatar: response.data.user.avatar }));
}).catch(error => console.log(error));
}
multer settings in the route controller
const multer = require('multer');
const DIRECTORY = './uploads/images';
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, DIRECTORY);
},
filename: function (req, file, cb) {
cb(null, `IMAGE-${Date.now()}.${path.extname(file.originalname)}`);
},
});
const upload = multer({
// storage: storage,
storage: multer.memoryStorage(),
fileFilter: (req, file, cb) => {
if (file.mimetype == 'image/png' || file.mimetype == 'image/jpg' || file.mimetype == 'image/jpeg') {
cb(null, true);
} else {
cb(null, false);
return cb(new Error('Only .png, .jpg and .jpeg format allowed!'));
}
},
limits: { fileSize: 1024 * 1024 * 2 },
});
image upload route file
router.put('/avatar/:uid', upload.single('image'), avatar);
All of this is successfully saving the image's buffer to mongodb:
{
...
avatar: "very long string"
...
}
But the problem is that I don't know how to make that image show on the webpage.
<img src={getUser().avatar} />
<a href="#">
<form onSubmit={uploadProfileImage}>
<input type="file" name="image" onChange={fileSelectedHandler} />
<button type="submit">Upload</button>
</form>
<span>Add Photo</span>
</a>
image upload backend
const avatar = (req, res) => {
const image = req.file.buffer.toString('base64');
User.findByIdAndUpdate(req.params.uid, { $set: { avatar: image }}, { new: true, upsert: true })
.exec((error, user) => {
if (error) {
return res.status(400).json({
message: error.message,
});
}
console.log(req.file)
res.status(201).json({
message: 'Avatar added successfully',
user
});
});
};
It's highly not recommended for you to store images in MongoDB. But if you really want to do it, you'll have to convert the image into base64; and the you will have to have something like <img src="data:image/png;base64, base64codeHere" alt="Red dot" /> at the front end to render it.
However, a better approach is that you store the image locally somewhere and only store the path into MongoDB.
So when you want to render it just path the path to the src attribute in the <img> tag.
Example:
//Multer DiskStorage Config
const diskStorage = multer.diskStorage({
destination: 'assets/pic_upload',
filename: (req, file, call_back) => {
//Prepend date to the filename or anything that makes the
//File unique so it won't get overwritten
call_back(null, Date.now() + '_' + file.originalname);
}
});
//Create Multer Instance
const upload = multer({ storage: diskStorage });
//Picture upload
router.post('/upload--pic', upload.single('file'), (req, res) => {
//Example DB code from my project. Feel free to
//apply your own logic here
//Update the pic in the DB
User_DB.findOneAndUpdate({ _id: 'idHere' }, {
//Store the pic path in the DB
profImage: (req.file.path).replace('assets/', '')
})
});
At the front end you will need something like this:
<form action="/pic_upload" enctype="multipart/form-data"> </form>

501 Not Implemented error in small web app

I am trying to upload images to an S3 bucket with react and expressjs. When I attempt to upload the image I get a 501 Not Implemented Error. I am using axios to contact the end point I created in the server code.
My react code:
class FileUpload extends Component {
state = {
file: null
};
submitFile = (event) => {
event.preventDefault();
const formData = new FormData();
formData.append('file', this.state.file[0]);
axios.post(`test-upload`, formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
// handle your response;
}).catch(error => {
// handle your error
});
}
handleFileUpload = (event) => {
this.setState({file: event.target.files});
}
render () {
return (
<form onSubmit={this.submitFile}>
<input label='upload file' type='file' onChange=
{this.handleFileUpload} />
<button type='submit'>Send</button>
</form>
);
}
}
export default FileUpload;
My server code:
const uploadFile = (buffer, name, type) => {
const params = {
ACL: 'public-read',
Body: buffer,
Bucket: process.env.S3_BUCKET,
ContentType: type.mime,
Key: `${name}.${type.ext}`
};
return s3.upload(params).promise();
};
app.use('/', (req,res) =>{
res.send(JSON.stringify({ greeting: `Hello!` }));
});
// Define POST route
app.post('/test-upload', (request, response) => {
const form = new multiparty.Form();
form.parse(request, async (error, fields, files) => {
if (error) throw new Error(error);
try {
const path = files.file[0].path;
const buffer = fs.readFileSync(path);
const type = fileType(buffer);
const timestamp = Date.now().toString();
const fileName = `bucketFolder/${timestamp}-lg`;
const data = await uploadFile(buffer, fileName, type);
return response.status(200).send(data);
} catch (error) {
return response.status(400).send(error);
}
});
});
This was done by following a guide on the internet but there seems to be something missing that I just can't figure out.
Figured it out in the end,
axios.post(/test-upload,
should have been
axios.post(http://localhost:3000/test-upload,

Resources