DEV Community

Cover image for How to Build an Astrojs Image Gallery App with Strapi 5
Strapi for Strapi

Posted on • Originally published at strapi.io

How to Build an Astrojs Image Gallery App with Strapi 5

Introduction to Strapi's Media Library

Strapi is an open-source, Node.js-based Headless CMS that can easily build customizable APIs and manage web content with any front-end framework. One of the most prominent features it has is the Media Library plugin; with this, you can upload various media files (images, videos, documents) and organize them as well. This is great for making media-heavy apps such as galleries.

In this tutorial, you'll learn how to build an Astrojs Image Gallery using Astro and Strapi's Media Library plugin.

Here is a demo of the project we'll be building throughout this tutorial:

project demo

Tutorial Objectives

Here’s a quick summary of what we will learn in this tutorial:

  • Create a back-end Strapi CMS to take care of image uploading.
  • Fetch media files via Strapi’s REST API and display them in a responsive grid layout.
  • Implement lazy loading, hover effects, and models for improved performance and user experience using Astro.js.
  • Upload images securely from Astro frontend.
  • And finally, build an Astrojs Image Gallery.

Key Features of Strapi Media Library:

One of the best things about Strapi is its powerful Media Library, which provides a smooth way to manage your media files and organize content with ease. Here are some of the main features:

  • Add and manage images, videos, and documents easily via the admin panel.
  • Supports local storage, AWS S3, Cloudinary, and more.
  • Easily organize and label media files for quick reference.

Benefits of Integrating Strapi Media Library with Astro:

By integrating Strapi Media Library, we get the best of both worlds, Powered by Astro. Some of the advantages include:

  • Manage media and content separately from the front-end.
  • Handle unlimited files with cloud storage.
  • Leverage Astro static generation for high-speed and optimized media delivery.

Prerequisites

Before we begin, ensure you have the following:

  • Node.js v18 or higher
  • A code editor installed on your machine
  • Node.js package manager (npm) v6 and above
  • Python
  • Set up your Astro environment if you haven’t already

The complete code for this tutorial can be found in this GitHub repository. Clone the repo and follow along!

Strapi Installation

To create a new Strapi 5 project, run the following command:

npx create-strapi@latest gallery-project
Enter fullscreen mode Exit fullscreen mode

The above command will prompt you to select the preferred configurations for your Strapi 5 project. Your selection should look like the screenshot below:

strapi installation.png

After selecting the above configurations, it will scaffold a new Strapi CMS project and install the necessary Node.js dependencies. Once setup is complete, create your admin account by filling out the required forms.

strapi admin registration.png

Configure the Strapi Media Library Plugin

Next, stop the server using Ctrl + C and Configure the upload plugin in the config/plugins.js file. By default, Strapi 5 provides a provider that uploads files to a local directory, which by default will be public/uploads/ in your Strapi project. Strapi 5 supports various storage options for uploads, such as Firebase, Cloudflare, Amazon S3, and Cloudinary. For simplicity, we will use the Local storage provider in this example:

export default ({ env }) => ({
  upload: {
    config: {
      providerOptions: {
        localServer: {
          maxage: 300000
        },
      },
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Enable Public Access for Upload API

To allow public access to upload media files, navigate to the Settings panel from the Strapi dashboard. Under USERS & PERMISSIONS PLUGIN, select Roles. Then click on Public from the roles table.

go to roles then public to allow image upload.png

Scroll down to Upload, tick Select find, findOne, upload, and click Save.

enable upload access in Strapi.png

Uploading Media Files to Strapi

Once the API access is configured, you can upload media files to the Media Library. Here’s how:

In the admin panel, click on the Media Library section from the left sidebar. Click Add new assets -> FROM COMPUTER -> Browse Files to select files from your computer.

upload media files.png

Upload at least three image files for this demo.

Creating an Astro Project: Building an Astrojs Image Gallery

Now that Strapi is up and running with your media content, we’ll move on to setting up the front-end using Astro. Astro is a modern web framework that makes building fast websites a breeze by supporting component-based frameworks like React, Svelte, Vue, and others.

Setting Up a New Astro Project

To start, let’s create a new Astro project. Run the following commands in your terminal:

npm create astro@latest media-app
Enter fullscreen mode Exit fullscreen mode

The above command will also prompt you to select the configuration for your project. For the demonstration in this tutorial, your selection should look like the screenshot below:

create an astro project.png

Then navigate into the new project directory and start the Astro development server:

cd ./my-media-app 
npm run dev
Enter fullscreen mode Exit fullscreen mode

This will spin up a development environment and open your site in the browser. You'll see a default Astro welcome page, but don't worry; we'll replace this with our image gallery soon!

new astro app.png

Integrating Astro with Strapi Through REST API

Now, let’s integrate Strapi and Astro by fetching image data from Strapi’s API. Strapi exposes a flexible REST API out of the box, but you can also use GraphQL if you’ve installed the GraphQL plugin in Strapi. For this guide, we’ll use the REST API.

Now, in your Astro project, create a new API utility in the utils/api.ts file to fetch gallery data from Strapi:

import { API_URL } from "../constants";

export async function fetchGallery() {
  const response = await fetch(`${API_URL}/api/upload/files`);
  if (!response.ok) {
    throw new Error("Failed to fetch gallery data");
  }
  const data = await response.json();
  console.log(data);
  return data;
}

Enter fullscreen mode Exit fullscreen mode

This function makes a simple HTTP GET request to Strapi’s /gallery endpoint. You can adjust the API_URL if you’re hosting Strapi on a different URL. The function returns a JSON object that contains your gallery images.

Create a constant.ts file in the src folder to define the app content variables. Here, we'll define the API_URL so we can reuse it across the application.

export const API_URL = "http://localhost:1337";
Enter fullscreen mode Exit fullscreen mode

Setting Up a Responsive Front-end Layout for the Image Gallery

Now that we can fetch data from Strapi, let’s display it in a responsive front-end layout. We'll use a basic CSS Grid to create a gallery layout that adapts to different screen sizes.

First, update your pages/index.astro file to create a page where the gallery will live:

---
import { fetchGallery } from "../utils/api";
import { API_URL } from "../constants";

const gallery = await fetchGallery();

interface Gallary {
  url: string;
  id: number;
  name: string;
}
---

<html>
  <head>
    <title>Astro Image Gallery</title>
    <style>
      .gallery {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        gap: 20px;
      }
      .gallery-item {
        border: 1px solid #ccc;
        padding: 10px;
        text-align: center;
      }
      .gallery-item img{
        width: 100%;
      }
    </style>
  </head>
  <body>
    <h1>Image Gallery</h1>
    <div class="gallery">
      {
        gallery.map((item: Gallary) => (
          <div class="gallery-item" key={item.id as any}>
            <img src={API_URL + item.url} alt={item.name} />
            <p>{item.name}</p>
          </div>
        ))
      }
    </div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

In the above code snippet, we use fetchGallery() in the front matter section of the Astro page to retrieve image data. The CSS grid is used to create a responsive gallery layout where each image is displayed in a grid item. The map() function loops through the fetched gallery data, rendering each image with its name below it.

image gallery in Astro.png

Creating a Single Astrojs Image Gallery Page

Now, create a dynamic page to preview each image dynamically. Create an image/[id].astro file in your pages directory. Add the code snippet below:

---
import { API_URL } from "../../constants";

const { id } = Astro.params;

let imageData = null;
let error = null;

try {
  const response = await fetch(`${API_URL}/api/upload/files/${id}`);

  if (!response.ok) {
    throw new Error(`Failed to fetch image data for ID ${id}`);
  }

  imageData = await response.json();
} catch (err: any) {
  error = err.message;
}
---

<html lang="en">
  <head>
    <title>
      {imageData ? imageData.name : "Image Not Found"} - Image Gallery
    </title>

    <style>
      .container {
        max-width: 800px;
        margin: 0 auto;
        padding: 20px;
      }
      .image-container {
        text-align: center;
      }
      .image-container img {
        max-width: 100%;
        height: auto;
      }
      .image-info {
        margin-top: 20px;
      }
      .back-link {
        display: inline-block;
        margin-top: 20px;
        padding: 10px 15px;
        background-color: #007bff;
        color: white;
        text-decoration: none;
        border-radius: 5px;
      }
      .error {
        color: red;
        font-weight: bold;
      }
    </style>
  </head>
  <body>
    <div class="container">
      {
        error ? (
          <div class="error">
            <h1>Error</h1>
            <p>{error}</p>
            <a href="/" class="back-link">
              Back to Gallery
            </a>
          </div>
        ) : (
          <>
            <h1>{imageData.name}</h1>
            <div class="image-container">
              <img src={`${API_URL}${imageData.url}`} alt={imageData.name} />
            </div>
            <div class="image-info">
              <p>
                <strong>File name:</strong> {imageData.name}
              </p>
              <p>
                <strong>Upload date:</strong>{" "}
                {new Date(imageData.createdAt).toLocaleString()}
              </p>
              <p>
                <strong>File size:</strong> {(imageData.size / 1024).toFixed(2)}{" "}
                KB
              </p>
            </div>
          </>
        )
      }
      <a href="/" class="back-link">Back to Gallery</a>
    </div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The code above sets up a dynamic page in an Astro project that displays a single image from Strapi’s media library based on the image's unique ID. The getStaticPaths function automatically generates routes for each image, allowing you to click on an image and view its details on a separate page. Along with displaying the image, the page shows useful metadata like the image name, upload date, and file size. If there’s an issue fetching the image (for example, if the image doesn’t exist), an error message is displayed. A "Back to Gallery" button is also included to make navigation easy.
Then, since our code uses server-side rendering, update Astro configuration in the astro.config.mjs file to allow it:

import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig({
    output: 'server',
});
Enter fullscreen mode Exit fullscreen mode

Update the code in your pages/index.astro file to be able to navigate to this new page:

   ...
   <div class="gallery">
      {
        gallery.map((item: Gallary) => (
          <div class="gallery-item" key={item.id}>
            <a href={`/image/${item.id}`}>
              <img src={API_URL + item.url} alt={item.name} />
              <p>{item.name}</p>
            </a>
          </div>
        ))
      }
    </div>
    ...
Enter fullscreen mode Exit fullscreen mode

Now click on any of the images to navigate to view more details about the image:

image details page.png

Handling Media Uploads from the Astro Frontend

Finally, let’s handle image uploads directly from the Astro front-end and send them to Strapi.

Implementing Image Uploads from Astro to Strapi

We’ll create a form in Astro that allows users to upload images. The form sends a POST request to Strapi’s /api/upload endpoint. Update the code in your pages/index.astro to:

---
import { fetchGallery } from "../utils/api";
import { API_URL } from "../constants";

const gallery = await fetchGallery();

interface Gallery {
  url: string;
  id: number;
  name: string;
}
---

<html lang="en">
  <head>
    <title>Astro Image Gallery</title>
    <style>
      .gallery {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        gap: 20px;
      }
      .gallery-item {
        border: 1px solid #ccc;
        padding: 10px;
        text-align: center;
      }
      .gallery-item img {
        width: 100%;
        transition: transform 0.3s ease;
      }
      .gallery-item img:hover {
        transform: scale(1.05);
      }
      .gallery-header {
        display: flex;
        justify-content: space-between;
      }
      .modal {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5);
        justify-content: center;
        align-items: center;
      }
      .modal-content {
        background-color: white;
        padding: 20px;
        border-radius: 8px;
        width: 300px;
        text-align: center;
      }
      .modal.show {
        display: flex;
      }
      .close {
        position: absolute;
        top: 10px;
        right: 10px;
        cursor: pointer;
        font-size: 24px;
      }
    </style>
  </head>
  <body>
    <div class="gallery-header">
      <h1>Image Gallery</h1>
      <button id="addImageBtn">Add New Image</button>
    </div>

    <div class="gallery">
      {
        gallery.map((item: Gallery) => (
          <div class="gallery-item" key={item.id}>
            <a href={`/image/${item.id}`}>
              <img src={API_URL + item.url} alt={item.name} />
              <p>{item.name}</p>
            </a>
          </div>
        ))
      }
    </div>

    <div class="modal" id="uploadModal">
      <div class="modal-content">
        <span class="close" id="closeModal">&times;</span>
        <h2>Upload New Image</h2>
        <form id="uploadForm">
          <input type="file" accept="image/*" id="fileInput" />
          <br /><br />
          <button type="submit">Upload Image</button>
        </form>
      </div>
    </div>

    <script define:vars={{ API_URL }}>
      let showModal = false;
      let selectedFile = null;

      function toggleModal() {
        showModal = !showModal;
        document
          .getElementById("uploadModal")
          .classList.toggle("show", showModal);
      }

      function handleFileChange(event) {
        selectedFile = event.target.files[0];
      }

      async function handleSubmit(event) {
        event.preventDefault();

        if (!selectedFile) {
          alert("Please select an image to upload");
          return;
        }

        const formData = new FormData();
        formData.append("files", selectedFile);

        try {
          const response = await fetch(`${API_URL}/api/upload`, {
            method: "POST",
            body: formData,
          });
          console.log(response);
          if (response.ok) {
            alert("Image uploaded successfully!");
            toggleModal();
            location.reload();
          } else {
            alert("Failed to upload image.");
          }
        } catch (error) {
          console.error("Error uploading image:", error);
          alert("An error occurred while uploading the image.");
        }
      }

document
        .getElementById("addImageBtn")
        .addEventListener("click", toggleModal);
      document
        .getElementById("closeModal")
        .addEventListener("click", toggleModal);
      document
        .getElementById("fileInput")
        .addEventListener("change", handleFileChange);
      document
        .getElementById("uploadForm")
        .addEventListener("submit", handleSubmit);
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

To allow users to add new images from the app, we added a modal with a file field and upload button, then added event listeners to show, close the modal, and upload the selected image to the Strapi media library.

You can now add new images to your gallery by clicking the Add New Image button.

astro upload image modal.png

Conclusion

In this tutorial, we have learned how to build an image gallery app with Astro.js and Strapi. With Astro and Strapi, we built an Astrojs image gallery.

With this, we have demonstrated how Strapi and Astro can be seamlessly integrated for media-rich applications.

Top comments (0)