Making a Media Library with Redwood
Tags: javascript, react, graphql
Published: 2021-08-13 13:09:01

---
title: Making a Media Library with Redwood
published: true
description: Having a customizable media library can help you keep track of relevant content and manage settings around it. In this post, we'll create a media library that can be extended to cover a lot of different needs.
tags: #javascript, #react, #graphql
cover_image: https://cdn.sanity.io/images/5ad74sb4/production/2f445a1119750cd17ecc6945f332c4385a51ce35-1000x420.png?w=2000&h=2000&fit=max
---

You might have a blog or a site that specializes in giving users video and image content. You can make yourself a media library to manage all of your content and keep it relevant to your users. That way you can see the exact data you need and you can arrange your images and videos in ways that don't affect users.

In this tutorial, we'll make a small media library to handle videos and images for us. You'll be able to upload the videos and images to Cloudinary and then show them in your own library. We'll be storing references to that content in our own Postgres database that we could use to reference images in other places.

## Setting up a Cloudinary account

The first thing we'll do is set up a Cloudinary account. You can [sign up for a free account here](https://cloudinary.com/signup).

Then you'll have a single place to upload all of your users' media content. That way you don't have to worry about storing all of your content and you can just fetch and upload your videos.

## Setting up the Redwood app

Now you can go to a terminal and we'll make our new Redwood app with the following command.

`yarn create redwood-app media-library`

When this finishes, you'll have a full-stack app that just needs a little updating. In the `api` folder you'll find all of the code to handle your database and GraphQL back-end. In the `web` folder you'll find the code for the React front-end. We'll start with the database schema because Redwood uses this to handle a lot of work for us.

### Making the models

Open `schema.prisma` in the `api > db` directory. Make sure to update your `provider` to `postgresql` instead of `sqlite` since we'll using a Postgres database. Take a moment to update the `.env` file in the root of the project. You'll need to uncomment the `DATABASE_URL` value and update it to your Postgres connection string. It might look like this.

`DATABASE_URL=postgres://postgres:admin@localhost:5432/media_library`

If you don't have Postgres installed locally, [you can download it here](https://www.postgresql.org/download/) and get your connection string from pgAdmin once the installation is finished.

You can close `.env` now and go back to `schema.prisma` because we need to add our models. You can delete the example model in the file and add the following ones.

```javascript
model Video {
  id       Int    @id @default(autoincrement())
  name     String
  duration Float
  url      String
}

model Image {
  id     Int    @id @default(autoincrement())
  name   String
  url    String
}
```

We have the database schema ready so we can run a quick migration to set up the database.

`yarn rw prisma migrate dev`

This will create the tables with the columns and constraints we defined in the models. Just to have some data to look at, we'll seed our database.

### Seeding the database

Inside of `api > db`, open `seed.js` and delete all of the commented out code in the `main` function. We'll be adding our own seed calls with the following code.

```javascript
await db.video.create({
  data: { name: 'Beth_vid', duration: 765.34, url: 'example.com' },
})
await db.image.create({
  data: { name: 'Beth_img', url: 'example.com/beth.jpg' },
})
```

Now you can run this command to seed your database.

```bash
yarn rw prisma db seed
```

With your fresh data applied, we can move on to the best part of Redwood.

## Using Redwood to generate the front-end and back-end

Now we'll use my favorite Redwood command to generate the CRUD to work with videos for the front-end and back-end of this project.

`yarn rw g scaffold video`

This one command generates a React front-end to handle everything we need to add video records to the database. If you look in `api > db > src > graphql`, you'll see a new sdl file that contains all of the types for our queries and mutations for handling videos. In `api > db > src > services`, you'll see a new `videos` directory. This folder has all of the resolvers to handle the database updates. It also has a test that you can add on to.

On the front-end of the app, take a look at `web > src > pages`, you'll see a lot of new directories for videos. These contain pages that show different aspects of the CRUD functionality.

Take a look in `web > src > components` and you'll see even more directories for videos. These directories contain the files that handle the data on the front-end and the form we could use to handle adding videos to the database.

We're going to run the `scaffold` command one more time to generate all of the files for images.

```bash
yarn rw g scaffold image
```

This is everything we need to start working with the Cloudinary upload widget.

## Adding the Cloudinary upload widget

We're not actually going to use the form to add videos to the database. We're going to do that automatically after we upload the videos to Cloudinary. To start with, we'll add the Cloudinary widget to the `NewVideo` component. We can use a package to add this widget so we'll install that in the `web` directory first.

```bash
yarn add react-cloudinary-upload-widget
```

Let's go to `web > src > components > NewVideo` and edit the file there. This is where the edit form for the video gets pulled in. We'll add the import for the upload widget components we need along with the other imports.

```javascript
import { WidgetLoader, Widget } from 'react-cloudinary-upload-widget'
```

Then we'll add the uploader widget components. In the code right above the `VideoForm`, add this code.

```javascript
<WidgetLoader />
<Widget
  sources={['local', 'camera']}
  cloudName={process.env.CLOUD_NAME}
  uploadPreset={process.env.UPLOAD_PRESET}
  buttonText={'Open'}
  style={{
    color: 'white',
    border: 'none',
    width: '120px',
    backgroundColor: 'green',
    borderRadius: '4px',
    height: '25px',
  }}
  folder={'test0'}
  onSuccess={successCallBack}
/>
```

While you can check out what each of these props does for the widget in the [README in the repo](https://github.com/bubbaspaarx/react-cloudinary-upload-widget#readme), there are a few we need to highlight. The `CLOUD_NAME` and `UPLOAD_PRESET` need to be defined in your `.env`.

### Making an upload preset in the Cloudinary console

You'll need the values for these two fields and you'll get those from your Cloudinary console. The cloud name is on the dashboard as soon as you log in.

![cloud name](cloud_name.png)

You'll need to go to the [settings page for uploads](https://cloudinary.com/console/settings/upload) and create a new unsigned upload preset. Your upload setting page will look something like this.

![upload settings](https://res.cloudinary.com/jesse-thisdot/image/upload/v1625837578/e-603fc55d218a650069f5228b/vyiyxaahjdaxbqpt1zio.png)

If you scroll down the page a bit, you'll see the "Upload presets" section. Click on "Add upload preset" to create a new unsigned preset. You be taken to a page that looks like this and the only thing you need to do is change "Signed" to "Unsigned" and save that change.

![preset settings](https://res.cloudinary.com/jesse-thisdot/image/upload/v1625837557/e-603fc55d218a650069f5228b/p0y8npxjslxqqv2cuqgk.png)

The value you see for "Upload preset name" is what you will need to set for your `UPLOAD_PRESET` in the `.env`. With these two values, you're `.env` should look similar to this.

```bash
CLOUD_NAME=test0
UPLOAD_PRESET=rftg435ebtre4
```

### Making the success callback

Now that we have those values in place, we need to define the callback for a successful video upload. This is where we'll get the URL to store in the database and where we'll make that GraphQL call now. You can delete the form now!

The success callback will look like this. It'll go right above the `return` statement.

```javascript
const successCallBack = (results) => {
  const videoInfo = results.info
  const input = {
    name: videoInfo.original_filename,
    duration: videoInfo.duration,
    url: videoInfo.url,
  }
  createVideo({ variables: { input } })
}
```

When you run the app with `yarn rw dev` and go to `http://localhost:8910/videos/new` in your browser, you should see a new button above the form that says "Open". This opens the Cloudinary upload widget.

![upload button](https://res.cloudinary.com/jesse-thisdot/image/upload/v1625837461/e-603fc55d218a650069f5228b/crvkstwqwqpmmpl8y85a.png)

![upload widget](https://res.cloudinary.com/jesse-thisdot/image/upload/v1625837490/e-603fc55d218a650069f5228b/g85drai4anpv5gtjm62x.png)

Upload a few videos and you'll see you get redirected to the main videos page that has a table with references to all your videos.

![video table](https://res.cloudinary.com/jesse-thisdot/image/upload/v1625837507/e-603fc55d218a650069f5228b/utyqalhfdskofvzblktx.png)

All that's left now is to show the videos on this page in a library format!

## Showing the media

Now you'll need to go to `web > src > components > Videos` and open `Video.js`. This is the file that has the table we see with our video info listed. We're going to keep the table and add the videos above it.

Since this component already has all of the video data fetched, we're going to add another `.map` over the videos and create new elements. Add this code right above the table element.

```javascript
<div
  style={{
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'space-evenly',
    marginBottom: '24px',
  }}
>
  {videos.map((video) => (
    <video width="320" height="240" controls>
      <source src={video.url} type="video/mp4" />
      Your browser does not support the video tag.
    </video>
  ))}
</div>
```

If you reload your page, you should see something similar to this, but with your videos.

![video library](https://res.cloudinary.com/jesse-thisdot/image/upload/v1625837530/e-603fc55d218a650069f5228b/cpstw0fbzbeypf4bazx1.png)

We're done! Now you have a fully functional media library. You can customize how content is displayed and how users interact with it if you add a little more imagination to what we started. You could add this functionality to the `Image` components we didn't cover. (It's why I put them in this guide.)

## Finished code

You can check out the full code in this GitHub repo in the `media-library` directory. Or you can check out the front-end in this [Code Sandbox](https://codesandbox.io/s/beautiful-shape-n5gjs).

_Note: You won't be able to see everything in the Code Sandbox because it doesn't support monorepos like Redwood._

## Conclusion

Storing media references to your own database can give you the flexibility to monitor changes over time. They can also act as a buffer against people trying to access your Cloudinary account. Some performance aspects might also make media references in your database useful.

Regardless of what your reasons are, now you know how to handle this use case quickly with the help of Redwood.