Upload Files to S3 Object Storage (or MinIo) with Express.js
Tags: javascript, node, tutorial, webdev
Published: 2022-05-01 21:52:35

In the past I've done articles on how to upload images to Cloudinary, however this may not be the reality of many people and most likely most people will have other needs such as uploading images/files to an Object Storage in the Cloud and the most popular service is S3. In addition to being the most popular service, many other cloud providers have S3 compatibility, that is, you can migrate from one cloud provider to another in a more comfortable way.

In today's article, to make it possible for a greater number of people, I will use MinIo. However, the client that will be used is exactly the AWS SDK and MinIo will only be used as Object Storage. If you need to install minio locally you can access [this link](https://min.io/download#/docker), I recommend using docker if you know.

## Let's code

First let's install the necessary dependencies:

```sh
npm install express multer multer-s3 aws-sdk
```

Then we can start by configuring our middleware, in our case as we are going to use the multer we have to specify that our storage will be S3 (in this case the MinIo). But before all that, we have to configure our storage:

```js
// @/src/middleware/bucket.js
import multer from "multer";
import multerS3 from "multer-s3";
import AWS from "aws-sdk";

export const bucket = "dev-multer-s3-bucket"

export const s3 = new AWS.S3({
  endpoint: "http://127.0.0.1:9000",
  accessKeyId: "ly1y6iMtYf",
  secretAccessKey: "VNcmMuDARGGstqzkXF1Van1Mlki5HGU9",
  sslEnabled: false,
  s3ForcePathStyle: true,
});

const storage = multerS3({
  s3,
  bucket,
  contentType: multerS3.AUTO_CONTENT_TYPE,
  metadata: (req, file, cb) => {
    cb(null, { fieldName: file.fieldname });
  },
  key: (req, file, cb) => {
    cb(null, Date.now().toString());
  },
});

export const upload = multer({ storage });
```

One important thing I want to point out is that the S3's Access Key corresponds to the MinIo's root user, just as the S3's Secret Access Key corresponds to the root password.

As you may have noticed, the bucket to which we are going to upload the images is called `dev-multer-s3-bucket`, and the file metadata will only contain the filename and the key (which is the file identifier ) will be a timestamp.

With our middleware finished we can start working on our api, today I will give two examples and each one of them will correspond to a route. The first example will be the image upload and the second will be the deletion of an image.

Then let's create a simple API:

```js
// @/src/main.js
import express from "express";

const startServer = async () => {
  const app = express();

  app.use(express.json());

  app.get("/", (req, res) => {
    return res.json({ message: "Hello World" });
  });

  return app;
};

startServer()
  .then((app) => app.listen(3333))
  .catch(console.error);
```

With the api created we can import our upload middleware as well as create a route to upload the image and in the body of the response we will have a single property that will correspond to the URL of the image in the bucket.

```js
// @/src/main.js
import express from "express";

import { upload } from "./middleware/bucket.js";

const startServer = async () => {
  const app = express();

  app.use(express.json());

  app.get("/", (req, res) => {
    return res.json({ message: "Hello World" });
  });

  app.post("/upload", upload.single("file"), (req, res) => {
    return res.json({ message: req.file.location });
  });

  return app;
};

startServer()
  .then((app) => app.listen(3333))
  .catch(console.error);
```

Then we can start working on the file deletion route and for that we need to import our s3 instance and bucket name. Our route will receive a single parameter that is the key (which is the image identifier) and then we have to check if that file exists in the bucket and if it exists we will proceed to remove it.
```js
// @/src/main.js
import express from "express";

import { s3, bucket, upload } from "./middleware/bucket.js";

const startServer = async () => {
  const app = express();

  app.use(express.json());

  app.get("/", (req, res) => {
    return res.json({ message: "Hello World" });
  });

  app.post("/upload", upload.single("file"), (req, res) => {
    return res.json({ message: req.file.location });
  });

  app.delete("/remove/:key", async (req, res) => {
    const params = { Bucket: bucket, Key: req.params.key };

    let file

    try {
      file = await s3.headObject(params).promise();
    } catch (error) {
      return res.status(404).json({ message: "File not found" });
    }

    if (file) {
      try {
        await s3.deleteObject(params).promise();
      } catch (error) {
        return res.status(500).json({ message: "Could not delete file" });
      }
    }

    return res.json({ message: "File deleted" });
  });

  return app;
};

startServer()
  .then((app) => app.listen(3333))
  .catch(console.error);
```

If you want to have access to the code, [here](https://github.com/FranciscoMendes10866/minio-s3-upload) is the link to the repository.

## Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! 👌