Making User Generated Video Slideshows in Redwood with Cloudinary
Tags: javascript, react, cloudinary, video
Published: 2021-08-03 13:01:28

---
title: Making User Generated Video Slideshows in Redwood with Cloudinary
published: true
description: Users like it when they have access to customization for their videos so it's important that we give them things that make their experience better.
tags: #javascript, #react, #cloudinary, #video
cover_image: https://cdn.sanity.io/images/5ad74sb4/production/c5ee96bfde457afabcf0fbbb0fa59724cfde0c20-1000x420.png?w=2000&h=2000&fit=max
---

Videos are one of the most popular forms of content online. They help people start new professions and they keep us endlessly entertained. That's why you have to make sure any video-based apps you work on give users a good experience.

Users like it when they can customize videos to display what they need. In this tutorial, you'll learn how to let your users make custom video slideshows with Redwood and Cloudinary.

## Setting up media

We're going to be working with Cloudinary to handle our media because they make it easy to work with. To start, make sure you have a [Cloudinary account](https://cloudinary.com/users/register/free). Then go to the Media Library and upload any images and videos you want users to be able to select from.

You'll also need to upload the template for a slideshow to your Media Library. Right now, you have to [download this slideshow template](https://res.cloudinary.com/demo/video/upload/slideshow.clt) because it's the only one supported by the slideshow generation functionality.

Before we jump into the code, it'll help to take a look at the URL we'll be working with to generate these custom slideshows. You can also find an [explanation of this URL in the docs](https://cloudinary.com/documentation/video_slideshow_generation).

```curl
https://res.cloudinary.com/`cloudname`/video/upload/fn_render:`global-settings`;vars_(`slide-settings`(`individual-slide`))/`global-transformations`/`template`.`ext`
```

You can find the `cloudname` in your Cloudinary console.

![cloud name in Cloudinary console](https://res.cloudinary.com/jesse-thisdot/image/upload/v1624820952/e-603fc55d218a650069f5228b/pprlbwwdzotci6tpddpf.png)

- `global-settings`: general settings applied to your video, like the height and width that Cloudinary should return as well as the duration that the video should be.
- `slide-settings`: settings that apply to each slide. This will include things like how long the image is shown before switching to the next one, what kind of transition is applied between slides, and how long the transition lasts.
- `individual-slide`: how you add images or videos based on their `public_id` to your slideshow. We'll use this to add the specific images we want a little later.
- `global-transformations`: how you can use the regular transformations Cloudinary has available, like automatically adjusting the quality of the video so that it's optimized for your users.
- `template`: how we let Cloudinary know we're going to create a video slideshow. This is the [template we downloaded earlier](https://res.cloudinary.com/demo/video/upload/slideshow.clt).

This is what all of our work is leading up to: letting the user control their own slideshow.

## Creating the customizations

We're going to let users define which videos are in the slideshow, the duration of the slideshow, the width of the video, the duration of each slide, the height of the video, and the duration of each transition. Let's set up the Redwood app with the following command.

`yarn create redwood-app ./user-generated-slideshows`

This will make a fresh Redwood project for you. We'll be working in the `api` directory to handle the database and back-end and the `web` directory to handle the front-end.

We'll start by making a new schema for our Postgres database.

_If you want to follow along with the database set up, you'll need to [have Postgres installed locally](https://www.postgresql.org/download/)._

In the `api > db` directory, open the `schema.prisma` file. The first thing we'll do in here is update the database provider. Right now it's set to `sqlite`. We need to update this value to `postgresql`.

You'll also see an example of a schema model here and we'll be replacing that with the following.

```javascript
model Property {
  id                 Int      @id @default(autoincrement())
  width              Int
  height             Int
  videoDuration      Int
  slideDuration      Int
  transitionDuration Int
  videos             String[]
}
```

This defines all of the properties of the slideshow a user can set. There are a few more things we need to do to make sure our database gets set up correctly. In the root of the project, open the `.env` file. There's a line assigning a value to `DATABASE_URL` and it's commented out.

Uncomment that line and update the URL to the connection string for your local Postgres database instance. It might look something like this.

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

Now you have everything in place to run a database migration! To do that, run the following command.

`yarn rw prisma migrate dev`

This will get Prisma to generate your migration and update the database according to your schema definition. We can move on to the back-end and front-end now.

## Making the back-end

Now it's time to create a few things to get the back-end working. First, we'll generate a GraphQL schema and a resolver with the following command.

`yarn rw g sdl property`

Going to the `api > src > graphql` folder will show you a new sdl file. This holds all of the GraphQL types you need to get started. Since we're already in this file, let's add a new mutation type. This mutation will create a new set of properties when the user submits the form we'll create on the front-end later.

Below the `UpdatePropertyInput` type, add the following mutation type.

```javascript
type Mutation {
  createProperty(input: CreatePropertyInput): Property
}
```

This adds the create mutation to our GraphQL definitions. Now we need to add the actual functionality that will update the database.

Go to `api > src > services` and you'll see a `properties` folder. The files inside this folder were generated when we ran that `yarn rw g sdl property` command earlier. It has a resolver for fetching all of the properties from the database. Now we need to add a resolver that will handle the creation of a property.

At the bottom of the `properties.js` file, add the following code for the create resolver.

```javascript
export const createProperty = ({ input }) => {
  return db.property.create({ data: input })
}
```

That's it for the back-end! So far we have the database created and connected the GraphQL resolvers to it. All that's left is for us to make a front-end for users to interact with.

## Building the front-end

Let's make a home page that displays the video slideshow and the form with the options they choose. We can let Redwood generate a new page and add the new routing with this command.

`yarn rw g page home /`

If you go to `web > src > pages`, you'll see the `HomePage` folder. The files inside were created with the command we just ran. If you take a look at `Routes.js`, you'll see the new route for the home page is already there.

Then we'll update the `HomePage` component to display the form users will use to make their custom slideshow.

### Adding the form

We'll need a form to get the info we need to make these custom slideshows. Redwood has its own form helpers to make them easier to work with. In the `HomePage` component, we'll import a few things at the top of the file.

_You can delete the existing imports because we won't be using them._

```javascript
import { Form, Label, TextField, Submit } from '@redwoodjs/forms'
import { useMutation } from '@redwoodjs/web'
```

We'll use these imports to make our form and send the new property to the database through the GraphQL server. Let's add the form first.

_You can delete everything inside the `HomePage` component. We'll be adding completely new elements._

```javascript
const HomePage = () => {
  return (
    <Form onSubmit={onSubmit}>
      <Label name="video1">First video</Label>
      <TextField name="video1" />
      <Label name="video2">Second video</Label>
      <TextField name="video2" />
      <Label name="video3">Third video</Label>
      <TextField name="video3" />
      <Label name="width">Video width (px)</Label>
      <NumberField name="width" max={500} />
      <Label name="height">Video height (px)</Label>
      <NumberField name="height" max={500} />
      <Label name="videoDuration">Video duration (ms)</Label>
      <NumberField name="videoDuration" max={11500} />
      <Label name="slideDuration">Slide duration (ms)</Label>
      <NumberField name="slideDuration" max={5500} />
      <Label name="transitionDuration">Transition duration (ms)</Label>
      <NumberField name="transitionDuration" max={5000} />
      <Submit>Save</Submit>
    </Form>
  )
}
```

We've made all of the fields to match the data we need to store in the database. That means it's time to bring in the GraphQL so that we send these values correctly. This is how we'll create a new property. Inside of the `HomePage` component, add this code above the `return` statement.

```javascript
const [createProperty] = useMutation(CREATE_PROPERTY)

const onSubmit = (data) => {
  const videos = [data.video1, data.video2, data.video3]
  createProperty({
    variables: {
      width: data.width,
      height: data.height,
      videoDuration: data.videoDuration,
      slideDuration: data.slideDuration,
      transitionDuration: data.transitionDuration,
      videos: videos,
    },
  })
}
```

Here, we make a mutation out of `CREATE_PROPERTY` which we'll be making right after this and we make the `onSubmit` for the form to handle the GraphQL call. At the bottom of the file, above the `export` statement add this code.

```javascript
const CREATE_PROPERTY = gql`
  mutation CreateProperty(
    $width: Int!
    $height: Int!
    $videoDuration: Int!
    $slideDuration: Int!
    $transitionDuration: Int!
    $videos: [String]!
  ) {
    createProperty(
      input: {
        width: $width
        height: $height
        videoDuration: $videoDuration
        slideDuration: $slideDuration
        transitionDuration: $transitionDuration
        videos: $videos
      }
    ) {
      id
      width
      height
      videoDuration
      slideDuration
      transitionDuration
      videos
    }
  }
`
```

This GraphQL mutation is what we use in the `useMutation` method to create a property. It passes all of the data to the resolver.

If you run the app with `yarn rw dev`, you should see something like this in your browser.

![form on home page](https://res.cloudinary.com/jesse-thisdot/image/upload/v1624821014/e-603fc55d218a650069f5228b/ydl3lf9e9scsgsu13jty.png)

Go ahead and create a new property and save it. This will help us later in the tutorial.

### Adding the video

We can finally add the video to the page! Earlier we looked at the slideshow generation URL, now we're going to use it with a few placeholder values. First, we'll create a new variable under the mutation definition.

```javascript
const properties = {
  width: 500,
  height: 500,
  videoDuration: 15,
  slideDuration: 3000,
  transitionDuration: 1000,
  videos: ['beach-boat', '3dogs', 'reindeer'],
}
```

This gives us some default values to start with so that our video will load. Now we'll actually add the video. Below the form, add the following code.

_You'll need to wrap the form and the video in a fragment so that React stays happy._

```javascript
<video controls>
  <source
    src={`https://res.cloudinary.com/milecia/video/upload/fn_render:w_${
      properties.width
    };h_${properties.height};du_${properties.videoDuration};vars_(sdur_${
      properties.slideDuration
    };tdur_${
      properties.transitionDuration
    };transition_s:circlecrop;slides_(${properties.videos
      .map((mediaDoc) => `(media_i:${mediaDoc})`)
      .join(';')}))/f_auto,q_auto/slideshow_hlhpzw.mp4`}
    type="video/mp4"
  />
  Your browser does not support the video tag.
</video>
```

This URL looks pretty crazy since we've added a bunch of extra parentheses and curly braces, but it's just loading the values into the placeholders we described earlier.

If you run your app again, your home page should look similar to this.

![home page with form and video](https://res.cloudinary.com/jesse-thisdot/image/upload/v1624821081/e-603fc55d218a650069f5228b/knffook2no6ssyx8fudx.png)

So you have the form in place and the video displaying on the page. The only thing left is to load in a property so that the user sees their custom slideshow.

### Loading the saved properties

We'll need to add one more GraphQL query to the front-end and we'll be able to set our initial video state. First, we'll add a new method to an existing import.

```javascript
import { useMutation, useQuery } from '@redwoodjs/web'
```

Then we'll create a query inside of the `HomePage` component, just above the mutation.

```javascript
const { loading, data } = useQuery(PROPERTIES)
```

Next we'll add `PROPERTIES` just above our `CREATE_PROPERTY` mutation at the bottom of the file.

```javascript
const PROPERTIES = gql`
  query Properties {
    properties {
      id
      width
      height
      videoDuration
      slideDuration
      transitionDuration
      videos
    }
  }
`
```

The generates the GraphQL query that we need to fetch all of the properties from the database. Now let's update our default variable to read the latest property that was added.

```javascript
const properties = data.properties[data.properties.length - 1]
```

To make sure we have data to read, we're going to use the `loading` variable we received from `useQuery`. Right above the last line of code we wrote, add this.

```javascript
if (loading) {
  return <div>Loading..</div>
}
```

This is going to prevent the app from crashing while the data is loading. Now if you refresh your app, you should see a slideshow generated with the values you saved earlier! You can try it out with a new value if you want and then reload the page.

_Keep in mind that video slideshow generation is still a beta feature so it might be a little buggy sometimes._

## Finished code

You can check out the finished code in [this Code Sandbox](https://codesandbox.io/s/trusting-wilson-ecfsh) or in [this GitHub repo in the 'user-gen-vids` folder](https://github.com/flippedcoder/blog-examples).

## Conclusion

Adding customizations can make it easier for people to learn and interact with your site. Video content is not slowing down so giving your users more control can be something that gives your site an edge over the others. Plus, working with Redwood will hopefully make it easier for you to keep things up to date!