Update Bunny Edge Storage files with NodeJS
Tags: node, api, opensource, programming
Published: 2024-04-30 10:00:00


## Intro 

I wanted to migrate a static website from a VPS to a CDN to improve website loading time and SEO performance. After a few searches, I discovered a new sleek CDN called [BunnyCDN](https://bunny.net/), which beats all performance charts in latency with an average of 40ms. That's what I was looking for! 

> **Note:** In 2020, it was a young and fresh company, tho, and in 2024, it is still winning the global latency performances.

It provides the following key advantages:
* Automatic replication in 15 Storage Region
* Delivers the content through 153 CDN POP servers (Content Delivery Network Point of Presence)
* Cheap price
* Company based in Europe (Slovenia) 🇪🇺

To set up a project on BunnyCDN, you need to create a **Storage Zones**, similar to an S3 Bucket or Open Stack Swift Container. Then, you have to link the storage to a CDN, also named a **Pull Zone**.

There are three methods to upload/download files to the Bunny Storage:
1. **Manage files on the web interface:** not the best to upload hundreds of files
2. **Manage files through an FTP:** When you create a storage zone, the interface gives you credentials to upload. That's better to bulk upload, but it's not the best solution for automating the process.
3. **Manage files through the Bunny API:** It is the best solution for handling thousands of files and automating the process.

The last option is the way. ~~[Mandalorian flute sound](https://www.youtube.com/watch?v=Rmyu78hTF9c)~~

## Request the Bunny Storage API

As my project was powered by NuxtJS, I created a Node SDK to communicate with the Bunny Storage API, and it is named [bunnycdn-storage-api-node-sdk](https://github.com/steevepay/bunnycdn-storage-node-sdk).

> Yes, that's a super long name; I couldn't find better; I should have done something like: **BSANSDK** 

When I update and build the static website for production, the SDK automatically uploads new files to the storage. Then BunnyCDN replicate files into each Storage Region:

![Map of all BunnyCDN Storage Regions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oe6dwq39kgagty2hewx8.png)

The Node SDK was created in 2020, the most important about it:
* 🦄 **Simple to use:** Only 4 methods: `putFile`, `getFile`, `deleteFile` and `getFiles`.
* 🚀 **Vanilla Javascript with only one dependency:** [rock-req](https://github.com/carboneio/rock-req) to make HTTP requests. Rock-req is faster than `fetch` or `axios`.
* ✅ **100% tested**

First, install the package with:
```bash
npm install --save bunnycdn-storage-api-node-sdk
```
Then instantiate a storage instance by providing your Bunny API Credential, and the API Storage Zone:
```js
const storage = require('bunnycdn-storage-api-node-sdk')(API_STORAGE_KEY, API_STORAGE_ZONE);
```

To upload a file, use the `putFile` function and provide a Callback as an argument:
```js
const fs = require('fs');

const PATH = '/path/';
const FILE_NAME = 'superPicture.jpeg';
const FILE_BUFFER = fs.readFileSync('./' + FILE_NAME);

// callback version
storage.putFile(PATH, FILE_NAME, FILE_BUFFER, (err, resp, data) => {
  console.log(data.toString()); // { "HttpCode" : 201, "Message" : "File uploaded." }
});
```
Each function supports Callback functions and Promise; The following `putFile` is receiving the API result as a Promise:
```js
// Promise version
storage.putFile(PATH, FILE_NAME, FILE_BUFFER).then(data => {
  console.log(data.toString()); // { "HttpCode" : 201, "Message" : "File uploaded." }
}).catch(err => {
  console.log(err);
});
```
To download a file, execute `getFile` and pass a Callback, or Promise:
```js
// callback version
storage.getFile(PATH, FILE_NAME, (err, resp, data) => {
  // data is a buffer
});
```
To Delete a file, call the `deleteFile` function:
```js
// callback version
storage.deleteFile(PATH, FILE_NAME, (err, resp, data) => {
  console.log(data); // { HttpCode: 200, Message: 'File deleted successfuly.' }
});
```
Finally, to get a list of files and directories, call the `listFiles` function:
```js
storage.getFiles('/').then(data => {
  console.log(data) // data is an array of objects, a list of files
}).catch(err => {
  console.log(err);
});
```

The Node SDK is limited to the Edge Storage, and it does not support for the moment [the Bunnet.net API](https://docs.bunny.net/reference/bunnynet-api-overview)
and [the Stream API](https://docs.bunny.net/reference/api-overview); It could be a great evolution!

Finally, to automate the update of the website when a new build is created, I wrote a little JS script that is executed at the end of the build process:
```js
require('dotenv').config()
const fs = require('fs')
const path = require('path')
const storage = require('bunnycdn-storage-api-node-sdk')(
  process.env.CDN_STORAGE_API_KEY,
  process.env.CDN_STORAGE_ZONE
)

uploadNuxtFiles()

async function uploadNuxtFiles() {
  const PATH = path.join(__dirname, '..', 'dist')

  await clearStorage()

  const files = getFiles(PATH)

  files.forEach((filePath) => {
    const FILE_PATH = path.dirname(filePath.replace(PATH, ''))
    const FILE_NAME = path.basename(filePath)
    const FILE_BUFFER = fs.readFileSync(filePath)
    storage.putFile(FILE_PATH, FILE_NAME, FILE_BUFFER, (err, resp, data) => {
      if (err) {
        throw new Error(err)
      }
      // eslint-disable-next-line no-console
      console.log(JSON.parse(data.toString()), filePath.replace(PATH, ''))
    })
  })
}

/** UTILS */

function getFiles(dir, files_) {
  files_ = files_ || []
  const files = fs.readdirSync(dir)
  for (const i in files) {
    const name = dir + '/' + files[i]
    if (fs.statSync(name).isDirectory()) {
      getFiles(name, files_)
    } else {
      files_.push(name)
    }
  }
  return files_
}

function clearStorage() {
  return storage
    .deleteFile('/')
    .then((data) => {
      // eslint-disable-next-line no-console
      console.log(data)
    })
    .catch((err) => {
      throw new Error(err)
    })
}
```
Code break-down:
1. First, it loads the environment variable file `.env`.
2. The Node SDK to request the Bunny API is initialised
3. The **uploadNuxtFiles** is executed:
  * Old files from the Bunny Storage are deleted with the `clearStorage` function.
  * The `build` directory is read to get the list of new files.
  * New files are uploaded using the `putFile` function.

Voilà 🎉

## Conclusion

The Node SDK and BunnyCDN are performing great in delivering my static website worldwide, simply and automatically.