Uploading images on the front end, using a form in React, is fairly easy, when compared with the back end set up. I learned how to do it in Ruby on Rails first, and now using Node.js and Express. In both, despite pouring over numerous tutorials and tons of documentation, I found it wasn't exactly as straightforward as all these resources made it look. Through tons of debugging though, I finally found my solution. I wanted to share some thoughts and some of the differences I found when learning how to configure Cloudinary with different back end languages and frameworks.
FRONT END FIRST
<form onSubmit={handleSubmitNewImage} encType="multipart/form">
<label htmlFor="image">
<input
type="file" name="image" accept="image/*"
/>
</label>
<input type="submit" value="submit" />
</form>
Here I have set up up a form which will render a file chooser. The enctype="multipart/form"
is necessary because when we are attaching an image to the request object, we lose the ability to use JSON.stringify()
, and we must use new FormData()
and append our image file. Then in the body we just send the formData as it is.
const formData = new FormData();
formData.append("image", image);
const response = await fetch(`<your-endpoint>`, {
method: "POST",
body: formData,
});
await response.json().then((data) => {
console.log(data);
});
CLOUDINARY
Cloudinary is an exceptionally easy to use cloud storage solution for uploading images and storing a url path in a database. I used postgreSQL, a relational database, when I uploaded images using Rails, and I used mongoDB, a non relational database, when I did the same with Node and Express. Cloudinary worked great in both environments. I created an account for free on Cloudinary's website, and they even provide a large amount of storage for free! After that I was navigated to my dashboard where I found the SDK Setup, which gave you code snippets for many languages, that help the configuration process.
RAILS
For our Rails setup, I ran gem install cloudinary
, and then in a storage.yml
file, added cloudinary: service: Cloudinary
. Rails comes fully loaded with Active Storage as a built in method for handling image uploads, and running rails active_storage:install
and rails db:migrate
, abstracts away the set up for a polymorphic join table between active_storage_blobs
and active_storage_attachments
, which you will notice now exists in the schema. We won't touch these tables going forward, but this stores lots of information about the upload, and an attachment to it.
In the model, we will use this reference to an attachment, where we set has_one_attached :image
instead of creating a column in the database. We add Rails.application.routes.url_helpers
to the code at the top of the model, which can be used to generate URLs given a set of parameters. In the serializer, I will call the get_image_url
method as an attribute to send the client a url generated for self.image
.
class User < ApplicationRecord
include Rails.application.routes.url_helpers
has_one_attached :image
def get_image_url
url_for(self.image)
end
end
Then on my React front end, I can simply set an <img src={user.get_image_url}>
!
NODE & EXPRESS
Using Node, Express, and MongoDB (with Mongoose) has been a fun process. I ran into a lot of errors and got to have some fun debugging by going over lots of juicy documentation. I landed on using a few packages: npm install cloudinary multer multer-storage-cloudinary
.
In a non relational database, we still use a Schema to organize and validate data. Using mongoose seemed like a good way to go. A new Schema is quickly available by importing mongoose
and {Schema}
. The same way we only stored information about the uploaded image, instead of the image data in the database, we set the image object to contain a url, with a type of String
.
const UserSchema = new Schema({
image: {
url: {
type: String,
},
},
});
After creating the Schema, we are ready to configure Cloudinary. In the code below, cloudinary.config
refers to the section above on the SDK Setup.
Multer is a piece of middleware for handling multipart/form-data
, primarily for uploading files. It creates a body object, and a file object and adds them to the request object. The file object is what we will use to upload the image, but the body will make sure that other pieces of data sent in the request are processed properly for their data types as well. Multer-storage-cloudinary is a storage engine that easily allows you to configure storage, using options passed in as params
. Choose from an extensive list of optional parameters. Params
itself is also optional, so the only requirement is the Cloudinary API object.
const multer = require("multer");
const cloudinary = require("cloudinary").v2;
const { CloudinaryStorage } = require("multer-storage-cloudinary");
cloudinary.config({
cloud_name: "eleni",
api_key: "<your-api-key>",
api_secret: "<your-api-secret>",
});
const storage = new CloudinaryStorage({
cloudinary: cloudinary,
params: {
folder: "users",
},
});
const parser = multer({ storage: storage });
We created the parser
which is the last part of our middleware, and it is inserted into the route, where it will capture the incoming request, attach a file, and pass it along. Here in the route we create an image
object, and set the url (which we created in the Schema) to one of the properties of the file object, the path, which is a url pointing to where the image is stored on Cloudinary! And then we create the rest of the new user and send it to the client, where it can be accessed by adding <img src={user.image.url}>
router.post("/<your-endpoint>", parser.single("image"), (req, res, next) => {
const image = {};
image.url = req.file.path;
const user = new User({ ...req.body, image });
user.save();
res.json(user)
ONE LAST TIP
My biggest & silliest hang up working this, was because in my server.js
file, I was using body-parser
and running app.use(bodyParser.urlencoded({ extended: false }))
. When I switched to using app.use(bodyParser.urlencoded({ extended: true }))
... it was a pretty glorious moment!
THE RESULTS
I honestly don't know which I prefer. I wrote this with hopes that it would help me decide if I preferred either, and the answer to that is still undefined
😂. Both languages and environments handle image uploads beautifully. Rails and Active Storage require less packages and configuration, but Node and Express allowed more flexibility as far as control of the data. Comment and let me know which you like better!
Top comments (1)
Great article! I recently created a chat app and used Cloudinary + MERN stack (with Mongoose). It was clear to follow Cloudinary's documentation, but I am trying to get more comfortable with MERN thou.
I will let you know once I try with Rails.