Introduction
In this article, we are going to go over the process of storing images on the server side; one of the right ways. I said "one of the right ways" because there are many good approaches to solving programming problems. But be rest assured, this approach will be effective for your use case.
Tools we will use
In this article, we will emphasize on two major tools. ExpressJs is our server-side framework of choice. We will make use of:
Multer
Sharp
Multer
Multer is a node.js middleware for handling multipart/form-data. It is primarily used for uploading files. Although we are looking at image uploads in this article, Multer handles other types of files as well. A multipart/form-data is one of the values of the enctype HTML attributes. This enctype attribute is used in forms to specify how the form-data should be encoded when submitting it to the server. The multipart/form-data is used when your form has a file input.
Sharp
Sharp is a high-speed node js module used to convert large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF, and AVIF images of varying dimensions. With Sharp you can perform image processing operations like Color Manipulation, Channel manipulation, resizing images, convert images to different image types. But in this article. But in this article, we are going to look at resizing and image conversions.
Installing packages
Well, well, well. Having gone over the tools we will be using, let's go into how to use them.
First, we will install the packages with the command below, in our terminal.
yarn add multer sharp
Let's create some basic assumptions before we proceed:
- We have an existing app
- It is a social media app. And we want to handle social media posts.
- We have our models set up and we are working with the post model.
- We have our post route and post controllers set up already.
Handling image uploads with Multer
In our post controller, we import multer:
const multer = require('multer');
Then we create a multer storage. We can store files with Multer in two ways. One in Diskstorage, and the other in memory. Storing in memory is good because other image processors can use the images. Remember we are using Sharp which is an image processor. So we are going to store it in memory.
const multerStorage = multer.memoryStorage();
After creating the multer storage, we then create a multer filter. The filter is a callback used to indicate which files should be uploaded.
const multerFilter = (req, file, cb) => {
if (file.mimetype.startsWith('image')) {
cb(null, true);
} else {
cb(new Error('Not an image! Please upload only images.', 400), false);
}
};
In the above code, we are checking if the mimetype starts with an image. If it does, then we upload, if not, then we get an error and false. Mimetype means media type. MIME stands for Multipurpose Internet Mail Extensions.
A media type indicates the nature and format of a document, file, or assortment of bytes.
Now that we have created the filter and storage, we will use them to create an upload.
In the upload, we will pass the filter and storage functions into the options object:
const upload = multer({
storage: multerStorage,
fileFilter: multerFilter,
});
Then we create our export for the multer middleware we just created. This export will be used in our create new post router. We can do it all in the router file, but for the sake of organization, we put these functions in separate files.
The export:
exports.uploadPostImages = upload.array('img', 3);
We use uploads.array when we want to upload multiple images and upload.single for uploading one image. The 3 indicates the maximum number of images that can be uploaded.
In our create post router, we will pass the uploadPostImages like:
router.post(
'/new',
postController.uploadPostImages,
postController.makePost
);
The postController.makePost is the function used to create a new post.
Processing our image with Sharp
Now, let's move on to image processing. Remember that our image was stored in memory with Multer. We will use sharp to save it to our express application.
We import Sharp to our post controller
const sharp = require('sharp');
Then we create a function to export to our create post route
exports.resizePostImages = async (req, res, next) => {
if (!req.files) return next();
req.body.img = [];
await Promise.all(
req.files.map(async (file, i) => {
const filename = `post-${file.originalname.split('.')[0]}-${Date.now()}-${
i + 1
}.jpeg`;
await sharp(file.buffer)
.resize(2000, 1333)
.toFormat('jpeg')
.jpeg({ quality: 90 })
.toFile(`public/image/posts/${filename}`);
req.body.img.push(filename);
})
);
next();
};
What is happening in the above code is explained below:
If there is no file, we call next(). Since we are working with multiple files, we set the req.body.img to an empty array.
The req.files.map returns a promise, so we wrap the sharp process in Promise.all to have our images added before calling the next function.
We loop over the files in our array and give them a unique file name. After giving the images a name, we call sharp to perform its processing on our images. Our image was stored in memory buffer by multer, so that is why we have file.buffer.
What sharp is doing is resizing our image to a width of 2000 and a height of 1333. It converts the images to jpeg format, gives them 90% quality, and then saves it as a file in our directory.
Now, we pass the exports to our route as we did for uploadPostImages:
router.post(
'/new',
postController.uploadPostImages,
postController.resizePostImages,
postController.makePost
);
So there you have it. We can now create posts with optimized image uploads.
Alternative to saving in local directory
In this article, we stored our images in our local directory. There is another route to image storing, which is using cloud servers like Cloudinary. In a future post, I'll write about this method.
Summary.
In this article, we used Multer for image uploads and Sharp to process and optimize the images. We learned we can also use Multer to upload other file types. We learned how to do single and multiple file uploads. We learned how to resize images and set image quality with Sharp.
Thank you for reading. Please, leave a comment and reaction. You can also follow me on github. See you in the next article.