# Build a Website Maker that creates and deploys a decentralized webpage using Web3.storage

We will build a frontend with HTML, CSS, and Vanilla JS where users can upload a picture along with the alt text and write a website title.

We will build a backend with Express.JS that receives the uploaded files and store them in Web3.storage, decentralized storage backed by Filecoin.

The final result will be a website maker, ([demo link](https://web3-website-maker.netlify.app/)), that will create a decentralized website -  [example](https://bafybeibet3nvwktjavnuk6icxnn7rts6rheyzhpbxh6iynpr66nshvfzvq.ipfs.dweb.link/files/index.html). 

# My Table of content
- [Intended Audience](#id-section1)
- [What are we Building?](#id-section2)
- [Prerequisite](#id-section3)
- [Folder Structure](#id-section4)
- [Create Frontend Website](#id-section5)
- [Create Express.JS Backend Server](#id-section6)
<div id='id-section1'/>

## Intended Audience

- Experience or familiarity with creating a full-stack app
- Newbies are welcome. Follow along and google (I use DuckDuckGo) anytime you need more clarity. Intermediate/Advanced programmers do this all the time.
<div id='id-section2'/>

## What are we Building?

- A website ([demo link](https://web3-website-maker.netlify.app/)) can create and deploy a one-page decentralized website      
- We will be using HTML, CSS, and Vanilla JavaScript to build the frontend website.
- We will be using the expressJS framework to create our backend server.
- The  [frontend code](https://github.com/coding-island-js/web3-static-website-maker-frontend) and [backend code](https://github.com/coding-island-js/web3-static-website-maker-backend) is publicly available at GitHub.
   - They are split up into two repos. For some reason, I don't like mono-repos, but I'm willing to change my mind.
- We won't be covering the code step by step with an explanation.
   - Instead, I will explain what the code is doing at a high to medium level
<div id='id-section3'/>

## Prerequisite

- Any code editor (I use VSCode)
- Node.js - here is a link to [install](https://nodejs.org/en/) 
<div id='id-section4'/>

## Folder Structure

### Frontend

```
project folder
│   index.html
│   script.js
│   style.css  
```

### Backend

```
project folder
│   .env
│   .gitignore
│   server.js
│
└───controller
│   │   createFolders.js
│   │   createHtmlFile.js
│   │   storeFilesToWeb3Storage.js
│   │   uploadFilesFromClient.js
└───routes
│   │   receiveFilesFromClient.js
```
<div id='id-section5'/>

## Create Frontend Website

Open up your code editor (I use VSCode). Create a folder. I called mine ```web3-static-website-maker-frontend```.

Create an HTML file called ```index.html```

Below is the HTML code. I will explain some of the parts that I think are important after the code.

```
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta
      name="description"
      content="make web3 decentralized websites using Web3.storage"
    />
    <meta name="keywords" content="web3 decentralized web3.storage" />
    <meta name="author" content="codingraj" />
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Merriweather+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="/style.css" />
    <script src="/script.js" defer></script>
    <title>Web3 Static Website Maker</title>
  </head>
  <body>
    <header>
      <div class="logo-title">Web3 Decentralized Website Maker</div>
    </header>
    <main>
      <h1>Create a one page website hosted on Web3.storage</h1>
      <br /><br />
      <p>
        Get your
        <a href="https://docs.web3.storage/#get-an-api-token" target="_blank">
          Free Web3.storage API Token here</a
        >
      </p>
      <br />
      <div class="card">
        <label for="website-title">Web3.storage API Token</label>
        <span class="inputRequired"></span>
        <span class="incorrectAPI" id="incorrectAPI-id"></span>
        <br />
        <input
          type="text"
          name="api-token"
          id="api-token-id"
          placeholder="API token not saved on the server"
          required
          autofocus
        />
        <br />
        <br />
        <label for="header-title">Header Title</label>
        <span class="inputRequired"></span>
        <br />
        <input
          type="text"
          name="header-title"
          id="header-title-id"
          placeholder="ex - I love pineapple on pizza"
          required
        />
        <br />
        <br />
        <label for="">Upload Image</label>
        <span class="inputRequired"></span>
        <br />
        <input
          type="file"
          accept="image/*"
          name="image-file"
          id="image-file-id"
          required
        />
        <br />
        <br />
        <label for="image-alt-text">Image Alt Text</label>
        <br />
        <textarea
          name="image-alt-text"
          id="image-alt-text-id"
          placeholder="ex - a slice of pizza with pineapple toppings on a paper plate"
        ></textarea>
        <br />
        <div class="buttonRequired" id="buttonRequired-id"></div>
        <br />
        <button id="generate-button-id" data-cy="generate-button">
          Create Website on Web3.storage
        </button>
        <br /><br /><br />
        <a class="web3-website-link" id="web3-website-link-id" target="_blank”">
          your new website
        </a>
      </div>
    </main>
    <footer>made with <span class="heart">&hearts;</span> @codingraj</footer>
  </body>
</html>
``` 

Important Parts:
- 3 sections
   - Header
   - Main
        - Card div for the input fields and button
             - I like the card layout
   - Footer
- Input fields
  - Web3.Storage API Token
    - This is needed to use their API to store files
    - Required field
  - Header Title
    - Displayed above the image
    - Required field
  - Upload Image
     - Restricted to selecting only image files
     - Required field
  - Image Alt Text
      - Ability to add alt text for the image file
      - Optional field
  - Upload Image
      - Restricted to selecting only image files
      - Required field
  - Span tags
      - To display error messages for
        - Incorrect API Token
        - Incomplete Fields
   - Button
      - Submit user input
   - New Website Link
      - After clicking the button, the server will send a URL for the new decentralized website

Add some CSS to format the layout with Grid and add some color.


Create a CSS file called: ```style.css```


```
/* Box sizing rules */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
.logo-title {
  margin: 0;
}

/* Set core root defaults */
html {
  scroll-behavior: smooth;
}

/* Set core body defaults */
body {
  text-rendering: optimizeSpeed;
  line-height: 1.5;
}

:root {
  --font-family: "Merriweather Sans", sans-serif;

  --color-background-header-footer: #d1bdf7;
  --color-button-background: #1a45df;

  --color-primary-font: #000036;
  --color-secondary-font: #ffffff;
  --color-complimentary-font: #1a45df;
  --color-placeholder-font: #4e5b62;

  --font-size-sm: clamp(0.5rem, 0.5rem + 1.5vw, 2rem);
  --font-size-100: 0.775rem;
  --font-size-200: 0.875rem;
  --font-size-300: 1rem;
  --font-size-400: 1.125rem;
  --font-size-500: 1.25rem;
  --font-size-600: 1.5rem;
  --font-size-700: 1.875rem;
  --font-size-xl: clamp(2rem, 1rem + 3vw, 5.75rem);
}

h1 {
  font-size: var(--font-size-700);
  line-height: 1.1;
}

.logo-title {
  font-size: var(--font-size-700);
}

/* Set Grid Layout, min-height, some color, and padding on the top and bottom */

body {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto 5fr auto;
  min-height: 100vh;
  gap: 2vw;
  font-family: var(--font-family);
  text-align: center;
}

header {
  font-size: var(--font-size-700);
  grid-row: 1 / 2;
  padding-block: 1vw;
  background: var(--color-background-header-footer);
}

main {
  grid-row: 2 / 3;
}

footer {
  padding-block: 1vw;
  grid-row: 3 / 4;
  background: var(--color-background-header-footer);
}

/* # card   */

.card {
  background: var(--color-background-header-footer);
  outline: none;
  padding: 1.5rem;
  border-radius: 8px;
  width: 90vw;
  margin: auto;
}

@media only screen and (min-width: 50em) {
  .card {
    width: 60vw;
  }
}

input {
  padding: 0.75em 0.5em;
  border: 2px solid;
  border-radius: 4px;
  font-size: var(--font-size-300);
  width: 90%;
}

label {
  font-size: 1.125rem;
  font-weight: 500;
  line-height: 2;
}

/* # input and textarea fields   */

input:required {
  border-color: #1a45df;
  border-width: 2px;
}
textarea {
  width: 90%;
  height: 15%;
  padding: 0.75em 0.5em;
  border: 2px solid;
  border-radius: 4px;
}

@media only screen and (min-width: 50em) {
  input,
  textarea {
    width: 70%;
  }
}

::placeholder {
  color: var(--color-placeholder-font);
  font-size: var(--font-size-100);
}

/* # buttons  */

button {
  border: none;
  background-color: var(--color-button-background);
  border-radius: 10px;
  padding: 0.4rem 0.6rem;
  font-size: var(--font-size-sm);
  color: var(--color-secondary-font);
  cursor: pointer;
  text-align: center;
}

.inputRequired:after {
  content: "required";
  font-size: var(--font-size-100);
  color: var(--color-complimentary-font);
  margin-left: 0.5vw;
}

/* # error message display and style   */

.buttonRequired {
  display: none;
}

.buttonRequired:after {
  font-size: var(--font-size-100);
  color: var(--color-complimentary-font);
  margin-left: 0.5vw;
}

.incorrectAPI {
  font-size: var(--font-size-100);
  color: red;
  margin-left: 0.5vw;
}

/* # decentralized website link returned from server   */

.web3-website-link {
  border: 5px solid orange;
  padding: 1vw;
  width: fit-content;
  margin: auto;
  animation: blink 1.5s infinite;
  display: none;
}

/* # loading animation after user clicks button   */

.loading {
  position: relative;
  overflow: hidden;
}

.loading:before {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;
  content: "";
  z-index: 1;
  background: pink;
  animation: slide 1.5s ease infinite;
}

@keyframes slide {
  0% {
    transform: translateX(-100%);
  }
  100% {
    transform: translateX(340%);
  }
}

@keyframes blink {
  0% {
    border-color: lightgreen;
  }
  50% {
    border-color: darkgreen;
  }
  100% {
    border-color: lightgreen;
  }
}

/* # heart color for footer   */

.heart {
  color: red;
}
``` 

Important Parts:
- CSS Grid for layout
   - 3-row layout
   - Used semantic tags instead of div tags for layout
- Card div to group input fields
   - Media Query to make the Card width smaller for larger screens
- Input + Label styling
   - Placeholder text styling
   - Required text label styling
   - Media Query to make the input width smaller for larger screens
- Button styling
   - Loading animation to display after clicking the button
- Span tag styling
   - The span tags is where the error message will be displayed
- New Website Link styling

Create a file called ```script.js``` in the root folder.

```
// declare environment
// to determine API url
let env = "dev";
let url;

if (env === "dev") {
  url = "http://localhost:8080/upload-files";
}

if (env === "prod") {
  url = "https://web3-website-maker.herokuapp.com/upload-files";
}

// select button Id
const createWebsiteButton = document.getElementById("generate-button-id");

// add click event listener that
// will get Input Ids, Input Values
createWebsiteButton.addEventListener("click", async () => {
  const apiTokenInput = document.getElementById("api-token-id");
  const fileInput = document.getElementById("image-file-id");
  const headerTitleInput = document.getElementById("header-title-id");
  const altImageInput = document.getElementById("image-alt-text-id");

  // check if Input fields is complete
  // if complete then call Function uploadFile
  // pass file selected, api token value, alt image value, and header title value
  if (
    apiTokenInput.value != "" &&
    headerTitleInput.value != "" &&
    fileInput.value != ""
  ) {
    uploadFile(fileInput, apiTokenInput, altImageInput, headerTitleInput);
  } else {
    // if the fields are incomplete
    // display error message
    document.getElementById("buttonRequired-id").style.display = "block";
    document.getElementById("buttonRequired-id").style.color = "orange";
    document.getElementById("buttonRequired-id").innerText =
      "fill in required fields";
  }
});

async function uploadFile(
  fileInput,
  apiTokenInput,
  altImageInput,
  headerTitleInput
) {
  // create random 4 digit number to create folder on server with a unique name
  var folderName = String(Math.floor(1000 + Math.random() * 9000));

  // grab website link element ID
  // we will use this to display the link after a successful response from the server
  const websiteLink = document.getElementById("web3-website-link-id");

  // add file and user input to FormData object
  const userFormData = new FormData();
  userFormData.append("folderName", folderName);
  userFormData.append("image", fileInput.files[0]);
  userFormData.append("headerTitle", headerTitleInput.value);
  userFormData.append("token", apiTokenInput.value);
  userFormData.append("altImage", altImageInput.value);
  console.log([...userFormData]);
  createWebsiteButton.classList.add("loading");
  createWebsiteButton.innerText = "wait";
  websiteLink.style.display = "none";
  document.getElementById("buttonRequired-id").innerText = "";
  document.getElementById("incorrectAPI-id").innerText = "";
  document.getElementById("api-token-id").style.borderColor = "#1a45df";

  // send `POST` request
  await fetch(url, {
    mode: "cors",
    method: "POST",
    headers: {
      Accept: "application/json",
    },
    body: userFormData,
  })
    .then((res) => {
      // if status is OK from server, then set the button to its original text
      if (res.status === 200) {
        createWebsiteButton.classList.remove("loading");
        createWebsiteButton.innerText = "Create Website on Web3.storage";
        return res.json();
      }
    })
    .then((data) => {
      // display the website link with the URL from the backend server
      websiteLink.style.display = "block";
      websiteLink.href = data.url;
      headerTitleInput.value = "";
      fileInput.value = "";
    })
    .catch((err) => {
      // catch error and display a message
      // apply border styling
      // set the button to its original text
      console.log("error: " + err);
      document.getElementById("api-token-id").style.borderColor = "red";
      document.getElementById("incorrectAPI-id").innerText =
        "Something went wrong. Your API token might be incorrect";
      createWebsiteButton.classList.remove("loading");
      createWebsiteButton.innerText = "Create Website on Web3.storage";
    });
}
``` 

Important Parts:
- Fetch call to send and receive from backend server
   - send different data types as Form Object
- Added wait text and loading animation after clicking the button
- Added error handling if
    - Origin is incorrect
         - The backend server will only accept the API request if it comes from the frontend website origin.
    - API token is incorrect
    - The form is incomplete
- Display new website link after a successful response from backend server
<div id='id-section6'/>

## Create Express.JS Backend Server

Open up a new project folder to create your backend server. I called mine ```web3-static-website-maker-backend```.

In the terminal, type to initialize our repository.

```npm init```

Now install NPM packages we will use to build our backend:

```
npm i express express-fileupload express-validator web3.storage cors dotenv
``` 

```express``` - Node.js framework for creating our backend server and API endpoints

```express-fileupload``` - To handle the files being uploaded from the frontend

```express-validator``` - To sanitize user input coming from the frontend

```web3.storage``` - Library that will upload and store files in decentralized storage

```cors``` - To restrict the use of our backend APIs to request coming from our frontend

```dotenv``` - Use a .env file to store the origin URL in our backend. It is optional since the origin URL does not need to be private. I used it, so I don't have to keep changing the origin for dev and prod environments.

Now we will install ```nodemon``` as a dev dependency - To restart our dev server when there are any changes.

```
npm i nodemon -D
``` 

After the npm install, this will create a ```package.json``` file. Open the file, remove the original scripts section (we will be adding our own), and add the following attributes.

```
"type": "module",
"scripts": {
    "devTest": "nodemon server.js",
    "start": "node server.js"
  },
```

We will be using ES6 module imports for the backend server, so the ```type``` is required.

The scripts' attributes are the commands to start our dev and prod backend server.

Create a file called ```server.js``` and put it in the root directory.

```
// server setup
import dotenv from "dotenv";
dotenv.config();
import express from "express";
import fileUpload from "express-fileupload";

// API routes
import receiveFilesFromClient from "./routes/receiveFilesFromClient.js";

const app = express();

// enable express-filesupload
app.use(fileUpload());

// bring the API routs
app.use(receiveFilesFromClient);

// start server

const port = process.env.PORT;

app.listen(port || 8080, () => {
  console.log("all systems are a go on port " + port);
});

```

Important Parts:
- Created backend server with Express.JS

Create a folder called ```routes```. Create a file in that folder called ```receiveFilesFromClient.js```.

Type the following code:

```
import dotenv from "dotenv";
dotenv.config();
import { Router } from "express";
import cors from "cors";
// import all the logic for the API routes
import { createFolders } from "../controller/createFolders.js";
import { createHtmlFile } from "../controller/createHtmlFile.js";
import { storeFilesToWeb3Storage } from "../controller/storeFilesToWeb3Storage.js";
import { uploadFilesFromClient } from "../controller/uploadFilesFromClient.js";

// used to validate the user input from the frontend
import { body } from "express-validator";

// create Express router for API routes
const router = Router();

// using CORS to restrict the origin requests to come from the Frontend only
const whitelist = [process.env.ORIGIN];
const corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1) {
      console.log("origin: " + origin);
      callback(null, true);
    } else {
      // throw error and stop API call if the origin is forbidden
      // I'm using a Heroku which restarts the server if it stops
      // so no need to restart server in code for production
      console.log("origin: " + origin);
      throw Error("Not allowed by CORS");
    }
  },
};

router.post(
  // the route of the API
  "/upload-files",
  // check request origin
  cors(corsOptions),
  // check and sanitize any user text using express-validator
  body("headerTitle").not().isEmpty().trim().escape(),
  body("altImage").not().isEmpty().trim().escape(),
  // call these functions in order
  createFolders,
  createHtmlFile,
  uploadFilesFromClient,
  storeFilesToWeb3Storage
);

export default router;
```

Important Parts:
- Checking CORS origin first
   - throw an error if the origin is forbidden
   - If you are using a Platform as a Service (PaaS), like Heroku, then you don't have to worry about restarting the server if an error occurs and crashes your backend server. A PaaS will restart the server for you automatically.
- Using express-validator to check and sanitize code instead of writing custom logic
- Separated the backend logic to the ```Controller``` folder to make the code cleaner are easier to read.

Create a folder called ```controller```. Create a file in that folder called ```createFolders.js```.

Type the following code:

```
import fs from "fs";

export async function createFolders(req, res, next) {
  //create directory
  //create temp dir
  // use the folder name received from the Frontend
  const tempDir = "./upload" + req.body.folderName;

  if (!fs.existsSync(tempDir)) {
    fs.mkdirSync(tempDir);
  }

  // create a sub directory to store the file received from the Frontend
  const childTempDir = tempDir + "/files/";

  if (!fs.existsSync(childTempDir)) {
    fs.mkdirSync(childTempDir);
  }
  // call next() to go to the next function on the route list
  next();
}
```

Important Parts:
- The ```fs``` module is included in Express. There is no need to npm install it separately.
- Create a parent folder using the unique name coming from the Frontend
- Create a subdirectory to store the file received from the Frontend
- Call next() to go to the next function on the route list

Create a file in the ```controller``` folder called ```createHtmlFile.js```.

Type the following code:

```
import fs from "fs";

export async function createHtmlFile(req, res, next) {
  // Assign variables to the received data
  let headerTitle = req.body.headerTitle;
  // express-fileupload will put any file received from the frontend as req.files
  let imageFile = req.files.image;
  let srcImage = "/files/" + imageFile.name;
  let altImage = req.body.altImage;
  // define folder path to store the files and user input
  let path = "./upload" + req.body.folderName + "/files/";

  // create HTML file from the user input
  // This is the website the app is making
  // included CSS styling in the style tag
  let htmlContent = `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Merriweather+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap"
      rel="stylesheet"
    />
    <title>Web3 decentralized website</title>
  </head>
  <body>
    <header>
      <div class="logo-title">Web3 Decentralized Website</div>
    </header>
    <main>
      <h1>${headerTitle}</h1>
      <br />
      <img src=${srcImage} alt="${altImage}" />
    </main>
    <footer>made with <span class="heart">&hearts;</span></footer>
  </body>
  <style>
    /* Box sizing rules */
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    /* Make images easier to work with */
    img,
    picture {
      max-width: 80%;
    }

    :root {
      --font-family-header: "Merriweather Sans", sans-serif;

      --color-background-header-footer: #d1bdf7;
      --color-background-body: #ffffff;
      --color-complimentary-two-background: #fff4f9;

      --color-primary-font: #000036;
      --color-secondary-font: #ffffff;

      --font-size-sm: clamp(0.5rem, 0.5rem + 1.5vw, 2rem);
      --font-size-100: 0.775rem;
      --font-size-200: 0.875rem;
      --font-size-300: 1rem;
      --font-size-400: 1.125rem;
      --font-size-500: 1.25rem;
      --font-size-600: 1.5rem;
      --font-size-700: 1.875rem;
      --font-size-xl: clamp(2rem, 1rem + 3vw, 5.75rem);
    }

    h1 {
      font-size: var(--font-size-xl);
      line-height: 1.1;
    }

    .logo-title {
      font-size: var(--font-size-700);
    }

    /* Set Grid Layout, min-height, some color, and padding on the top and bottom */
    body {
      display: grid;
      grid-template-rows: auto 1fr auto;
      min-height: 100vh;
      font-family: var(--font-family-header);
    }

    main {
      margin: auto;
      text-align: center;
      padding-block: 2vw;
    }

    header {
      font-size: var(--font-size-700);
    }

    header,
    footer {
      text-align: center;
      padding-block: 2vw;
      background: var(--color-background-header-footer);
    }

    .heart {
      color: red;
    }
  </style>
</html>`;

  //create html page and save html page to file folder using the FS module
  fs.writeFile(path + "index.html", htmlContent, function (err) {
    if (err) throw err;
  });

  // call next() to go to the next function on the route list
  next();
}
```

Important Parts:
- Assign variables to the received data
- ```Express-fileupload``` will put any file received from the frontend as req.files
- Define folder path to store the files and user input
- Create an HTML file from the user input
- Save HTML file to the file folder
- Call next() to go to the next function on the route list

Create a file in the ```controller``` folder called ```uploadFilesFromClient.js```.

Type the following code:

```
export async function uploadFilesFromClient(req, res, next) {
  try {
      //Use the name of the input field to retrieve the uploaded file
      let imageFile = req.files.image;

      //Use the mv() method to place the file in upload directory (i.e. "uploads")
      imageFile.mv(
        "./upload" + req.body.folderName + "/files/" + imageFile.name
      );
      // call next() to go to the next function on the route list
      next();
  } catch (err) {
    res.status(500).send(err);
  }
}
```

Important Parts:
- Move the files using ```express-fileupload``` to the files directory
- Call next() to go to the next function on the route list

Create a file in the ```controller``` folder called ```storeFilesToWeb3Storage.js```.

Type the following code:

```
// import the web3.storage library
import { Web3Storage, getFilesFromPath } from "web3.storage";
import fs from "fs";

export async function storeFilesToWeb3Storage(req, res) {
  // define folder path where the files are located
  let parentPath = "./upload" + req.body.folderName;
  let path = "./upload" + req.body.folderName + "/files/";

  const token = req.body.token;

  // Use API token to start the storage process
  const storage = new Web3Storage({ token });

  // get the files that the user uploaded
  const files = await getFiles(path);

  console.log(`Uploading ${files.length} files`);
  try {
    // store files
    // Store the files in web3.storage, if successful it will return a cid - address where the files are stored
    const cid = await storage.put(files);
    console.log("Content added with CID:", cid);
    // remove the temp folders so we don't store anything the user sent on the backend server
    await removeDir(parentPath);
    //send new website URL in response
    res.json({
      message: "File is uploaded",
      url: "https://" + cid + ".ipfs.dweb.link/files/index.html",
    });
  } catch (error) {
    // if the token is incorrect, catch the error and send status to client
    res.status(403).json({ error: "incorrect token: " + error });
    console.error("i found error: " + error);
    await removeDir(parentPath);
    return false;
  }
}

// function that gets the files stored in the file folder
async function getFiles(path) {
  const files = await getFilesFromPath(path);
  console.log(`read ${files.length} file(s) from ${path}`);
  return files;
}
// function to remove file directory
async function removeDir(path) {
  try {
    //  fs.rmdirSync(removePath);
    fs.rmSync(path, { recursive: true });
  } catch (error) {
    console.error(error);
  }
}
```

Important Parts:
- Use web3.storage library to store files in decentralized storage
- If the API token is incorrect, then it will throw an error
    - The frontend site will display an error
    - The backend site will crash but will get restarted in prod if using a PaaS, like Heroku
- remove the temp directory
   - this is important because we don't want to store any files from the user on our backend server
- Send the URL of the new website that was created and deployed onto decentralized storage.

You have now created a full-stack app that makes and deploys a one-page decentralized website.
