I Want to upload a file to Cloudinary with their nodejs SDK. I'm using Sveltekit and need to understand the way how it needs to be done. The flow:
Frontend: page.svelte Uploading a file with a <form>, submitting and calling server action ?/upload
<form
method="post"
enctype="multipart/form-data"
use:enhance
action="?/upload"
<input
class="hidden"
name="image"
id="file-to-upload"
type="file"
accept=".png,.jpg"
/>
<button class="border bg-orange-400 rounded py-2 text-sm"
>Upload!</button
>
</form>
Backend: +page.server.ts
import { v2 as cloudinary, type UploadApiResponse } from 'cloudinary'
import type { Actions } from './$types';
export const actions = {
upload: async ({request}) => {
const data = request.formData();
const file = (await data).get('image') as File
if (file){
const buffer = await file.arrayBuffer();
const stream = await file.stream();
const upload_stream = cloudinary.uploader.upload_stream((error, image) => {
?????
});
} else {
console.error('no file to upload')
}
}
} satisfies Actions;
Reads FormData and trying to upload submitted file to Cloudinary. But how to do this? Cloudinary has some methods like upload_stream but i cant figure out the way.
I've tried common cloudinary.uploader.upload function, but it wants a string as an input.
And i have only buffer and stream from FormData. I don't want to create base64 from file contents or upload a file to Cloudinary directly from browser.
Seems like I need to convert recieved FormData's file to another format, which can be successfully uploaded to cloudinary's upload_stream() function, for example. I really need help to make understanding how to correctly pass it from SvelteKit's backend to Cloudinary in the right way.
Any help will be appreciated.
So i found a solution. The trick is to convert file's ArrayBuffer to node's Buffer, so then Cloudinary's upload_stream works like a charm. Because of callback-nature of this function, i wrapped it in Promise.
async upload(file: File) {
const arrayBuffer = await file.arrayBuffer();
const buffer = Buffer.from(arrayBuffer) // <-- convert to Buffer
return new Promise((resolve, reject) => {
cloudinary.uploader.upload_stream({ resource_type: "image" }, onDone).end(buffer)
function onDone(error, result) {
if (error) {
return reject({ success: false, error });
}
return resolve({ success: true, result })
}
});
}
Related
I am building a Node JS application using Express JS. I need to implement the file upload logic for my application. I am using Formidable, https://www.npmjs.com/package/formidable for uploading file(s). I could upload the files using that library without any issue when the request body format is simple. Now, I am trying to upload the file which is a property/ field of an object in the request body. The following is the dummy request body structure.
{
users: [
{
name: `Test User 1`,
photoFile: {{ here I want to upload file for this user }}
},
{
name: `Test User 2`,
photoFile: {{ here I want to upload file for this user }}
},
// the list goes on
]
}
I am trying to send the test payload/ data in the Postman as in the screenshot below.
This is my code to parse the form
private getForm = async (
req: Request,
options: FormOptions = {
multiples: false
}
) => {
const tempUploadDir = this.getTempUploadDirectory(req);
if (!(await this.storage.exits(tempUploadDir))) {
await this.storage.mkdir(tempUploadDir);
}
return new IncomingForm({
multiples: options.multiples,
uploadDir: tempUploadDir,
maxFileSize: config.fileSystem.maxUploadLimit
});
};
public parseFormData = async <T>(
request: Request,
options: FormOptions = {
multiples: false
}
): Promise<T> => {
const form = await this.getForm(request, options);
return new Promise<T>((resolve) => {
form.parse(request, (err, fields, files) => {
if (err) {
if (err.code === FILE_TOO_LARGE_ERROR_CODE) {
// file too large
throw new UploadMaxFileSizeException(
`Upload file size limit exceeded.`
);
}
throw err;
}
let filesData: {
[key: string]: IUploadedFile[];
} = {};
for (let fileField in files) {
if (`length` in files[fileField]) {
filesData[fileField] = files[fileField] as IUploadedFile[];
} else {
filesData[fileField] = [files[fileField] as IUploadedFile];
}
}
return resolve({
...fields,
...files
} as T);
});
});
};
When I print out the result of parseFormData, I am getting the following result.
As you can see, the field field, 'users[0][photoFile]' is not parsed putting into the corresponding field of the request body object. Instead, the entire field name is string, 'users[0][photoFile]'. What is wrong with my code and how can I fix it?
I my project, file will upload to server when user upload files and get the link back
And when submit form, user avatar just is a link, not a file
this is my first post. Thank you all for the years, of assistance btw, I hope to pour in, as much as I've gotten from you guys/gals. Let's get started.
I have a Next.js / React-Redux application and here is my problem:
I'm attempting to update the Redux store, with JSON, that is returned from `fs.readFile' (the 'fs/promises' module:
//Product.js
function Product() {
const suggested_products = useSelector((state) => state.user.suggested_products) // json read from /api/products
const updateProducts = () => {
(JSON.parse(JSON.stringify(suggested_products)))?.map((product) => { // Compliler does not like this line
<div>
<input type='checkbox'>
<p> {product.category}</p>
<p>{product.service_name}</p>
</input
</div>
})
}
return (
if (userSelectedProduct) ? updateProducts() : <p>No data found</p>
}
//Form.js
import { useSWR } from 'swr'
const fetcher = (...args) => fetch(...args).then((res) => res.json());
function Form() {
const [url, setURL] = useState('');
const { data, error } = useSWR(url, fetcher);
<input
value={product}
onChange={
dispatch(updateCustomerCSP((JSON.parse(JSON.stringify(e.target.value)))));
setURL(`api/${product}/`); //Attempt to dynamically fetch data, from client side
dispatch(updateSuggestedProducts(data)); //Update data in store returned from client side fetching
}}
</input>
}
// pages/api/products
import fs from 'fs/promises';
export default function handler(req, res) {
const filePath = path.join(process.cwd(),`/data.js'); // /data.js contains JSON array [{"product-1": "value"}, {"product-2": "value"}], which is why I need to use map function.
try {
const fileData = fs.readFile(filePath);
const data = JSON.parse(JSON.stringify(fileData));
res.status(200).json(data);
} catch (err)
{
res.status(500).json({ error: 'Failed to load data' })
}
}
// This error throws ref.map is not a function from the Products.js component.
Here is the only info that I could find in regards to ref's in React.
Also, the JSON.parse and JSON.stringify wrappers, are to keep Redux happy with object going into the store. Thanks for reading community, looking forward to your input. Should any oneed more info, please let me know. Also, here is info in regards to refs, that I could find:
https://reactjs.org/docs/refs-and-the-dom.html
I figured it out. I actually had the input wrapped in a HOC, and the event handler wasn't properly registered to the input element itself., therefore failing to load in the array object into the store to use.
So be careful of that, when building your own components.
For instance,
function Component() {
function loadArrayInStore() {
loadInStore()
}
const MyButton = (props) => {
<input onChange={props.handler} // I had an inline handler here such as onChange={ (e)= { doNotLoadArrayInStoreAndDontWork()}
}
return (
<MyButton handler={loadArrayInStore} />
)
}
So be watchful, when creating HOC's :)
I'm facing a problem with a React project I'm working on: I'm trying to upload multiple images to a Node Express API. I'm using a formData object and I used the append() method to append the form fields from the component State.
In the Express code I'm using multer, all the attributes in the req.body are there but req.files is empty.
I changed the code to upload a single image also using formData() and it works; the problem seems to be only when I try with multiple files using the formData object. I also tested using a regular form (not react) and that also worked!
I'm wondering if there is something I'm missing when I use formData with a file input with multiple files?
import React, { Component } from "react";
import axios from "axios";
class Form extends Component {
constructor() {
super();
this.state = { images: {} };
}
onChangeImages = e => {
this.setState({ images: e.target.files })
};
onSubmit = e => {
e.preventDefault();
const { images } = this.state;
const formData = new FormData();
formData.append("images", images);
axios
.post("/api/post/create", formData)
.then(res => console.log(res.data))
.catch(err => console.error(err));
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input
onChange={this.onChangeImages}
type="file"
name="images"
multiple
accept="image/png, image/jpeg, image/jpg"
/>
<br />
<input type="submit" value="Send" />
</form>
);
}
}
export default Form;
Express code
const express = require('express');
const router = express.Router();
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
router.post('/create', upload.array('images', 2), (req, res) => {
console.log(req.files);
console.log(req.body);
res.status(200).json(req.body);
});
module.exports = router;
formData.append("images", images);
You need to append each file in turn. FormData doesn't support a FileList object.
for (let i = 0 ; i < images.length ; i++) {
formData.append("images", images[i]);
}
there are a lot of other alternatives than using the backend. You can use cloudinary or firebase to upload all images and get their urls and let your backend store those URLs in the database.
I am trying to upload an image to mongodb using angular and nodejs. The code is below. I got the backend working but the problem is with the html input i get 'C:fakepath/file.xyz'. I was looking online and saw that there is not a way to get the relative path of the file. Can someone please tell me how i can change my front end code to get and send the file path to the backend to then save. I read that the browser doesnt allow relative path of the file but then how can I upload. Thanks!
The nodejs image save method is:
async function SaveImage(userParam) {
const entry = new imageEntries(userParam);
entry.image.data = fs.readFileSync(userParam.imagePath);
entry.image.contentType = 'image/png';
await entry.save();
}
The html code is:
<div class="upload-btn-wrapper">
<button class="btn">Upload a file</button>
<input type="file" name="myfile" id="myFile" />
</div>
What I pass as the path in the backend is:
ImageJournal.imagePath = (<HTMLInputElement>document.getElementById('myFile')).value;
but with the code above i get the following error:
ENOENT: no such file or directory, open 'C:\fakepath\chapter9problemsandanswers.doc'
Ok here it is:
HTML:
<div class="form-group">
<label for="pdf">PDF</label>
<input type="file" id="pdf" (change)="onFileChange($event)" #fileInput>
<button type="button" class="btn btn-sm btn-default" (click)="clearFile()">clear file</button>
</div>
TS:
import { Component, OnInit, ElementRef, ViewChild } from '#angular/core';
#ViewChild('fileInput') fileInput: ElementRef;
public image;
onFileChange(event) {
let reader = new FileReader();
if(event.target.files && event.target.files.length > 0) {
let file = event.target.files[0];
reader.readAsDataURL(file);
let value = <String>reader.result;
reader.onload = () => {
this.image = {
filename: file.name,
filetype: file.type,
value: value.split(',')[1]
};
};
}
}
Then send the image with http post with a service.
On the server:
// send file
app.post('/api/sendFile', function (req, res) {
File.create({
filename: req.body.filename,
filetype: req.body.filetype,
value: req.body.value
}, function (err, file) {
if (err)
res.send(err);
else {
const response = {
name: file.filename
}
res.send(response);
}
});
});
This is written with mongoDb and mongoose and I have a model named File.
This is all it need to save it.
Update
This is happening because of hot-reloading comes with Creact React App.
Related issues:
https://github.com/expressjs/multer/issues/566
https://github.com/facebook/create-react-app/issues/4095
I am trying to learn file upload with Nodejs, Express, Multer and React for frontend. I achieved to upload files. There is a problem I struggle, not always but most of the time the whole app refreshes after upload. Here is the relevant code.
My simple form
<form onSubmit={this.handleFormSubmit}>
<input
type="file"
id="file"
onChange={this.handleFileChange}
/>
<button type="submit">Submit</button>
</form>
handleFileChange and handleFormSubmit
handleFormSubmit = () => {
const formData = new FormData();
formData.append( "file", this.state.file );
axios.post( "/api/upload", formData );
}
handleFileChange = ( e ) => {
this.setState( { file: e.target.files[ 0 ] } );
}
Related express route code
const express = require( "express" );
const multer = require( "multer" );
const storage = multer.diskStorage( {
destination( req, file, cb ) {
cb( null, "client/public/images" );
},
filename( req, file, cb ) {
cb( null, `${ Date.now() }-${ file.originalname }` );
},
} );
const upload = multer( { storage } );
router.post( "/upload", upload.single( "file" ), ( req, res ) => {
res.send();
} );
I searched a little bit but not luck. I've seen this post. Before seeing this I had already tried event.preventDefault(). Also, I've tried many things like uploading directly with onChange() without setting a state then handling it with onSubmit(). Before simplifying the code (like posting directly in handleFormSubmit) I was trying to do this via Redux actions but for debugging purposes I moved it here.
It is the first example here.
handleFormSubmit = async (e) => {
e.preventDefault() // <-- missing this
const formData = new FormData();
formData.append( "file", this.state.file );
const response = await axios.post( "/api/upload", formData );
}
handleFileChange = ( e ) => {
this.setState( { file: e.target.files[ 0 ] } );
}
This is happening because of hot-reloading comes with Creact React App.
I ran into this issue too, but using plain webpack without CRA. I was uploading the files to a static dir served by webpack-dev-server. I fixed it by setting devServer.static.watch to false (webpack docs).
your are using
<button type="submit">Submit</button>
this is why refreshing
so do it like this
<button onClick={this.handleFormSubmit}>Submit</button>
remove this from multer
destination( req, file, cb ) {
cb( null, "client/public/images" );
},