Building a Censorship Resistant Image Uploader
Tags: ipfs, web3
Published: 2022-11-03 12:48:35

---
title: Building a Censorship Resistant Image Uploader
published: true
description: 
tags: ipfs, web3
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9pr1xffnbman3lbu65c6.jpg
canonical_url: https://blog.developerdao.com/building-a-censorship-resistant-image-uploader
---

*TL;DR: You can check out [a repository with the complete code on GitHub](https://github.com/kay-is/ipfs-image-uploader), and also [a styled version of the app on IPFS](https://w3s.link/ipfs/bafybeiaalncy2lccyyuylvzuciunmcindqe7wd2gwey2ct6rukouylngc4).*

Building applications that are censorship resistant but are still easy to use isn't accessible. 

Meddling around with pinning services or running your own IPFS node usually isn't what the average frontend dev knows how to deal with. Luckily, the good people from Protocol Labs understood that issue and built [web3.storage](https://web3.storage/), a service that works as a layer between your Web2 skills and the shiny new decentralized world.

With **[Fullstack Frontend](https://fllstck.dev)**, I try to teach frontend developers skills that make them full stack in a way that allows them as much of their frontend skills as possible.

web3.storage is a perfect service for this, and with its new w3up API, it got even more flexible!

In this article, I'll show you how to build an image uploader that doesn't cost a dime!

## What?

We will build a web app that lets people register an email and upload images they can then share, either with an HTTP URL or an IPFS Content ID (CID).

web3.storage handles the upload for free, and the account is owned by your user directly, so you don't have to pay for your user's uploads. 

## Prerequisites

You need a current version of Node.js installed because we use the [Preact-CLI](https://github.com/preactjs/preact-cli/) and some NPM packages. 

## Initializing the Project

First, run the following command to create a new Preact project:

    $ npx preact-cli create default quicksave

This command will generate a basic Preact project. A simple PWA structure with a service worker, manifest, and everything.

## Adding W3UI Packages

[web3.storage is currently adding a new API called w3up](https://blog.web3.storage/posts/w3up-beta-launch). It comes with a few improvements over the previous API, but the biggest one is that users can register for it on their own accounts directly via the API.

They even created [a UI library](https://github.com/web3-storage/w3ui) with a few React components out of the box. 

Okay, "components" is a bit of a stretch here; it's mainly a few providers and hooks, but still an excellent time saver. Plus, you can build a UI, but you don't need to provide your users with your own account.

Run this command to install the packages:

    $ npm i @w3ui/react-keyring \
      @w3ui/react-uploader \ 
      @w3ui/react-uploads-list

- The `@w3ui/react-keyring` package links every agent (browser) to an account (email address).
- The `@w3ui/react-uploader ` package takes care of the actual file uploads.
- The `@w3ui/react-uploads-list` package lets you retrieve the CIDs of the uploaded files.


With these three packages, your users can signup for your app, upload their files and share them via an URL or a CID.

## Integrating W3UI

As I mentioned, the w3ui libraries only come with provider components and hooks; this makes them flexible and requires us to build some actual UI around them.

So, let's start by adding the providers to our Preact app.

### Adding Providers

In the `src/components/app.js` file, replace the code with this:

```jsx
import { AuthProvider } from "@w3ui/react-keyring"
import { UploaderProvider } from "@w3ui/react-uploader"
import { UploadsListProvider } from "@w3ui/react-uploads-list"

import Home from "./home"

const App = () => (
  <AuthProvider>
    <UploaderProvider>
      <UploadsListProvider>
        <Home />
      </UploadsListProvider>
    </UploaderProvider>
  </AuthProvider>
)

export default App
```

Nothing exciting here; just wrapping all app components with the providers.

### Creating the `Home` Component

Next is the `Home` component, simply a container for the components that will do all the work.

Create a `src/component/home.js` file and add this code:

```jsx
import Auth from "./auth"
import Uploader from "./uploader"
import UploadList from "./uploadsList"

const Home = (props) => {
  return (
    <div>
      <button type="button" onClick={() => props.unloadAndRemoveIdentity()}>
        Logout
      </button>
      <br />
      <Uploader />
      <br />
      <UploadList />
    </div>
  )
}

export default Auth(Home)
```

The `Home` component is wrapped in a higher order `Auth` component, which blocks access to `Home` until a user registers. `Auth` also injects a `unloadAndRemoveIdentity` function to `Home` we use for a logout button.

### Creating the `Auth` Component

The `Auth` component ensures that a user registers before using the app. Its a higher order component that wraps `Home` and displays the `Register` component when a user isn't registered.

Create it at `src/component/auth` with the following code:

```jsx
import { useEffect } from "preact/hooks"
import { useAuth, AuthStatus } from "@w3ui/react-keyring"
import Register from "./register"

export default function (Wrappee) {
  return function AuthWrapper(props) {
    const { authStatus, loadDefaultIdentity, unloadAndRemoveIdentity } =
      useAuth()

    useEffect(() => {
      loadDefaultIdentity()
    }, [])

    if (authStatus === AuthStatus.SignedIn) {
      const authProps = { ...props, unloadAndRemoveIdentity }
      return <Wrappee {...authProps} />
    }

    return <Register />
  }
}
```

The `loadDefaultIdentity` is called to check if a user has registered.

### Creating the `Register` Component

The register component adds HTML elements around the w3ui hooks to make them usable for humans.

Create it at `src/component/register.js` and fill it with this:

```jsx
import { useReducer } from "preact/hooks"
import { useAuth, AuthStatus } from "@w3ui/react-keyring"

export default function Register() {
  const { authStatus, identity, registerAndStoreIdentity } = useAuth()
  const [state, setState] = useReducer(
    (state, update) => ({ ...state, ...update }),
    { email: "", loading: false }
  )

  const waitingForVerification = authStatus === AuthStatus.EmailVerification

  return (
    <>
      <h1>QuickSave</h1>
      <h2>Distribute your files via IPFS!</h2>

      <form
        onSubmit={(e) => {
          e.preventDefault()
          setState({ loading: true })
          registerAndStoreIdentity(state.email)
        }}
      >
        <div>
          <label>Email Address: </label>
          <input
            type="email"
            disabled={state.loading}
            value={state.email}
            onChange={(e) => setState({ email: e.target.value })}
          />
          <button
            type="submit"
            class="btn btn-primary"
            disabled={state.loading}
          >
            Register
          </button>
          {waitingForVerification && (
            <p>Waiting for verification of "{identity.email}" address...</p>
          )}
        </div>
      </form>
    </>
  )
}

```

Here is where the actual registration happens. We use a reducer to keep track of the state, in this case, an `email` variable for the registration and a `loading` variable to lock up the UI after we submit the email.

### Creating the `Uploader` Component

Now that we have taken care of the registration, we need to implement the app's essential features.

Let's start with the `Uploader` component that lets users ... well ... upload images.

Create the file at `src/components/uploader.js` and add this code:

```jsx
import { useReducer } from "preact/hooks"
import { useUploader } from "@w3ui/react-uploader"

export default function Uploader() {
  const [, uploader] = useUploader()
  const [state, setState] = useReducer(
    (state, update) => ({ ...state, ...update }),
    { file: null, loading: false }
  )

  const handleUploadSubmit = async (e) => {
    e.preventDefault()
    setState({ loading: true })
    await uploader.uploadFile(state.file)
    setState({ loading: false })
  }

  return (
    <form onSubmit={handleUploadSubmit}>
      <input
        type="file"
        accept="image/*"
        onChange={(e) => setState({ file: e.target.files[0] })}
        required
        disabled={state.loading}
      />
      <br />
      <button type="submit" disabled={state.loading}>
        Upload
      </button>
      <br />
      {state.loading && <p>Uploading file...</p>}
    </form>
  )
}
```

Again, we wrap a simple UI around the w3ui hooks. The `<input>` element is used to choose a file and filters the valid files for images.

### Creating the `UploadList` Component

Now that our users can upload images, it would be nice if they could share them. For that, we have to use the w3up API for the CIDs of the images already uploaded and display them in a usable way.

Let's create a new file at `src/components/uploadsList` and add this code to it:

```jsx
import { useEffect } from "preact/hooks"
import { useUploadsList } from "@w3ui/react-uploads-list"

export default function UploadsList() {
  const { data, error, reload } = useUploadsList()

  useEffect(() => {
    const intervalId = setInterval(() => reload(), 5000)
    return () => clearInterval(intervalId)
  }, [])

  if (error) return <p>{error.message}</p>

  console.log(data)

  return (
    <div>
      {data?.results?.length &&
        data.results.map(({ dataCid, carCids, uploadedAt }) => (
          <div>
            <hr />
            <img
              src={`https://w3s.link/ipfs/${dataCid}`}
              alt={`Image CID: ${dataCid}`}
            />
            <p class="card-text">
              Uploaded At
              <br />
              {uploadedAt.toString()}
            </p>
            <p>
              Data CID
              <br />
              <code>{dataCid}</code>
            </p>
            <p>
              Image URL
              <br />
              <a href={`https://w3s.link/ipfs/${dataCid}`}>
                <code>https://w3s.link/ipfs/{dataCid}</code>
              </a>
            </p>
          </div>
        ))}
    </div>
  )
}
```

The list gets updated periodically every five seconds. 

The exciting part is the `dataCid`'s we get from the `useUploadsList` hook. It points directly to the image on IPFS. With an IPFS gateway, we can create a link we can put into an `<img>` tag and let the user share the file.

## Testing the App

To test the app, we can use the Preact-CLI. The following command with run the system in dev mode:

    $ npm run dev

The registration process requires you to enter an email. You have to verify it before you can start uploading.

## Bonus: Deploying the App

Now that everything is up and running locally, it would be nice to deploy it somewhere.

We could build the project and upload it to GitHub Pages, but wouldn't it be cooler to go for something more decentralized?

The w3up tool suite has a CLI that lets you do just that. Point it to a directory and get a CID that you can use to share with your peers.

Let's start by building the code:

    $ npm run build 

This will create a `build` directory, which we will tell `w3up` about, but first, we need to install it.

    $ npm i -g @web3-storage/w3up-cli

We need to register here too:

    $ w3up register <YOUR_EMAIL>

Now, we're ready to go!

    $ w3up upload ./build

This command will respond with two CIDs and a gateway URL if everything goes correctly. We can use this URL to access the app online.

## Summary

Building your own DApp with file hosting features has become easy with w3up and IPFS. 

The **w3up API is currently in beta**, so uploads are free, but if you check out the parent project web3.storage, you see that they offer a free tier with a few gigabytes, and the other plans are cheap, also.

The best thing is that this isn't your subscription; your users register and eventually pay for it if they hit the free tier limits. 

Plus, your app deployment and all the uploaded images are accessible via IPFS, which means anyone who thinks they shouldn't be deleted can pin the CIDs on their IPFS pinning service of choice or their own node and keep them available. 

While this isn't 100% decentralized because we're relying on w3up for central app features, it at least decentralized the burden of hosting and keeping files and apps available to the IPFS network.