Build an image gallery with Uploadcare JS client SDK and Nuxt.js

In this tutorial, you will create a Nuxt.js image gallery. You’ll use Uploadcare’s File Uploader to upload files and JS client SDK to retrieve and delete images.

Prerequisites

Before you begin, you need:

Obtain a Public and a Secret API keys

Head over to your Uploadcare dashboard and create a new Secret key. Copy the Secret key and store it somewhere safe, as you can only view it once upon creation.

The Public and Secret keys of a project are located in your Uploadcare Project Dashboard -> API keys.

Uploadcare Public and Secret API key in the Uploadcare dashboardUploadcare Public and Secret API key in the Uploadcare dashboard

You will use the keys in the next steps of this tutorial.

Create Nuxt.js application

To create a new Nuxt.js application, run the following command in your terminal:

npx nuxi@latest init uc-nuxt

Select the package manager of your choice during installation and install the required dependencies.

In your terminal, install the Uploadcare File Uploader and JS SDK:

npm install @uploadcare/blocks @uploadcare/rest-client

In the root of the project, create a .env. file and add the following environment variable:

UPLOADCARE_PUBLIC_KEY=YOUR_PUBLIC_KEY
UPLOADCARE_SECRET_KEY=YOUR_SECRET_KEY

Replace YOUR_PUBLIC_KEY and YOUR_SECRET_KEY with the public and secret keys from the Uploadcare dashboard.

Update the nuxt.config.ts file to use Uploadcare’s custom tags in Vue components and import Uploadcare’s API Keys into Nuxt.js runtime:

export default defineNuxtConfig({
  devtools: { enabled: true },
  vue: {
    compilerOptions: {
      isCustomElement: (tag) => tag.startsWith('lr-'),
    },
  },
  runtimeConfig: {
    uploadcareSecretKey: process.env.UPLOADCARE_SECRET_KEY,
    public: {
      uploadcarePublicKey: process.env.UPLOADCARE_PUBLIC_KEY,
    },
  },
});

Create a composable state for storing files

In the root of the project, create a composables directory, and inside of it, create a useFiles.ts with the content:

export const useFiles = () => {
  return useState('files', () => []);
};

The code above creates a useFiles state variable to store uploaded files and share them across different Vue components.

Create a file uploader component

Create a components directory and file named file-uploader.vue with the content:

<script setup>
import * as LR from '@uploadcare/blocks';
import '@uploadcare/blocks/web/lr-file-uploader-regular.min.css';

LR.registerBlocks(LR);
const ctxProviderRef = ref(null);
const files = useFiles();

const handleFileUpload = (event) => {
  if (event.detail) {
    files.value = [event.detail, ...files.value];

    console.log(files.value);
  }
};
const runtimeConfig = useRuntimeConfig();
const pubkey = runtimeConfig.public.uploadcarePublicKey;
</script>

<template>
  <main>
    <lr-config ctx-name="my-uploader" :pubkey="pubkey" img-only="true" />
    <lr-file-uploader-regular ctx-name="my-uploader" />
    <lr-upload-ctx-provider
      ctx-name="my-uploader"
      ref="ctxProviderRef"
      @file-upload-success="handleFileUpload"
    />
  </main>
</template>

The code above does the following:

  • Imports the File Uploader blocks library and registers it.
  • Imports the CSS styles for the File Uploader block.
  • Calls the useFiles state variable to store files that will be uploaded into the file uploader.
  • Creates a handleFileUpload function that handles an @file-upload-success event triggered when a file is successfully uploaded to the file uploader.
  • Uses the useRuntimeConfig composable to strive the uploadcarePublicKey you added in the previous step of this tutorial
  • Configures the file uploader using the lr-config block, renders the uploader with the lr-file-uploader-regular block, and configures actions for the uploader using the lr-upload-ctx-provider block.
  • Logs the files stored in the useFiles state variable to the console.

Update the app.vue content to render the file uploader component:

<template>
  <div>
    <h1>Image Gallery with Uploadcare and Nuxt.js</h1>
    <FileUploader />
  </div>
</template>

In your terminal, run the command npm run dev to start the Nuxt.js server, and you should have a page that looks like this in http://localhost:3000:

Landing page with File Uploader componentLanding page with File Uploader component

Display images from Uploadcare using the JS SDK

Try uploading a file and in the console, you can see the files uploaded to Uploadcare.

When you refresh the application in your browser, it loses all the files stored in the useFiles state. To fix that, let’s create a functionality to fetch all files uploaded to Uploadcare using the JS Client SDK.

Create an API route to fetch files

In the server directory, create sub-directory named api and inside of it create a list-files.ts file with the content:

import { UploadcareAuthSchema, listOfFiles } from '@uploadcare/rest-client';

export default defineEventHandler(async () => {
  const runtimeConfig = useRuntimeConfig();
  const UCAuth = new UploadcareAuthSchema({
    publicKey: runtimeConfig.public.uploadcarePublicKey,
    secretKey: runtimeConfig.uploadcareSecretKey,
  });
  try {
    const data = await listOfFiles(
      { ordering: '-datetime_uploaded' },
      { authSchema: UCAuth },
    );
    return data.results;
  } catch (error) {
    return error;
  }
});

The code above:

  • Using the useRuntimeConfig composable, retrieves publicKey and secretKey and creates a UploadcareAuthSchema object
  • Uses the listOfFiles API to fetch all the files in the current project, ordering them from the latest to the oldest.

Inside of the app.vue component, add a script that uses the api/list-files created:

<script setup>
const files = useFiles();
await callOnce(async () => {
  files.value = await $fetch('/api/list-files');
});
</script>

The code above uses a callOnce composable to fetch files from the /api/list-files route and store the request’s result into the useFiles state.

The callOnce request only happens once when the application is loaded in Nuxt.js.

Let’s use the NuxtImg component to display the fetched images. To do this, add the @nuxt/image dependency to your project:

npx nuxi@latest module add image

Next, update the nuxt.config.ts to use Uploadcare CDN as a provider in the NuxtImg component.

export default defineNuxtConfig({
  // the rest options are here

  image: {
    uploadcare: {
      cdnURL: 'ucarecdn.com',
    }
  },
});

In the components directory, create a new file named gallery.vue with the content:

<script setup>
const files = useFiles()
const processedFiles = computed(() => {
  return files.value.map(file => {
    return {
      ...file,
      cdnUrl: file.cdnUrl || `https://ucarecdn.com/${file.uuid}/`,
      fileName: file.name || file.originalFilename,
    };
  });
});
</script>

<template>
  <div class="grid">
    <div v-for="file in processedFiles" :key="file.uuid" class="grid-item">
      <NuxtImg
        :src="file.cdnUrl"
        :alt="file.fileName"
        provider="uploadcare"
        :modifiers="{ quality: 'smart', format: 'auto', border_radius: '20' }"
        width="500"
      />
    </div>
  </div>
</template>

<style scoped>
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  grid-auto-rows: 10px;
  gap: 2rem;
  padding: 10px;
  margin: 0 auto;
}

.grid-item {
  grid-row: span 10;
  position: relative;
  overflow: hidden;
  border-radius: 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

.grid-item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.grid-item:nth-child(2n) {
  grid-row: span 13;
}

.grid-item:nth-child(3n) {
  grid-row: span 15;
}
</style>

The gallery.vue component:

  • Retrieves the files stored in the useFiles state and loops through the files to add a cdnUrl and fileName if there is none in the file object
  • Loops through the files state variable, renders the NuxtImg component using Uploadcare as a provider and adds some modifiers for Uploadcare to transform images on the fly.
  • Uses scoped CSS to style the layout of how the images will be displayed.

Update the app.vue file to include the Gallery component:

<template>
  <div>
    <h1>Image Gallery with Uploadcare and Nuxt.js</h1>
    <FileUploader />
    <Gallery />
  </div>
</template>

Upload a couple of images to the file uploader, and you should see the images displayed in the gallery component like this:

Images displayed in the gallery componentImages displayed in the gallery component

You’ve successfully created an image gallery that fetches data from Uploadcare servers using the JS Client SDK and displays the images using the NuxtImg component.

Implement a delete image functionality

Ideally, you may want to delete an image from the gallery if the wrong image was uploaded.

Let’s implement a functionality to delete images from the gallery with one click.

Create a delete API route

In the server/api directory, create a delete-file.ts file with the code snippet:

import { UploadcareAuthSchema, deleteFile } from '@uploadcare/rest-client';

export default defineEventHandler(async (event) => {
  const { uuid } = await readBody(event);

  const runtimeConfig = useRuntimeConfig();
  const UCAuth = new UploadcareAuthSchema({
    publicKey: runtimeConfig.public.uploadcarePublicKey,
    secretKey: runtimeConfig.uploadcareSecretKey,
  });
  try {
    const data = await deleteFile({ uuid }, { authSchema: UCAuth });
    console.log({ data });
    return {
      statusCode: 200,
      statusMessage: 'File deleted successfully',
    };
  } catch (error) {
    return error;
  }
});

The code above:

  • Gets the uuid of the file that needs to be deleted from the event passed.
  • Uses the deleteFile method from the SDK to send a delete request to Uploadcare’s server.
  • When successful, it returns a statusCode and statusMessage or an error if the request fails.

Next, let’s create a button that will use this API to delete images from the gallery.

Create a delete button

In the gallery.vue component, add a handleDeleteFile function:

const handleDeleteFile = async (uuid) => {
  const response = await $fetch(`/api/delete-file/`, {
    method: 'DELETE',
    body: JSON.stringify({ uuid }),
  });
  console.log(response);
  if (response.statusCode === 200) {
    files.value = files.value.filter((f) => f.uuid !== uuid);
  }
};

The handleDeleteFile function sends a request to /api/delete-file/ in order to delete the file from Uploadcare servers by using the uuid. When the request is successful, it also removes the file from the file state variable.

Within the template section of the gallery.vue component, add a button that triggers the handleDeleteFile function immediately after the NuxtImg component.

<template>
  ...
  <button class="delete-button" @click="handleDeleteFile(file.uuid)">
    delete
  </button>
  ...
</template>

Next, add the following CSS code to style the button:

.delete-button {
  position: absolute;
  bottom: 20px;
  left: 20px;
  padding: 5px 10px;
  border: none;
  border-radius: 5px;
  background-color: red;
  color: white;
  cursor: pointer;
}

This will add a delete button that will appear like this in the browser:

Delete button in the gallery componentDelete button in the gallery component

Clicking the delete button will remove the image from the gallery and also delete the image from Uploadcare servers.

Conclusion

In this tutorial, you’ve successfully built an image gallery, that can upload, delete and display files in Nuxt.js using the Uploadcare File Uploader and JS Client SDK. You were also able to set up Uploadcare as a provider for NuxtImg to transform images on the fly.

To learn more about Uploadcare File Uploader and JS Client SDK, check out the File Uploader docs and JS Client SDK docs.

Build file handling in minutesStart for free

Ready to get started?

Join developers who use Uploadcare to build file handling quickly and reliably.

Sign up for free