Simply copy and paste the following command line in your terminal to create your first Strapi project.
npx create-strapi-app
my-project
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:
Here’s a quick summary of what we will learn in this tutorial:
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:
By integrating Strapi Media Library, we get the best of both worlds, Powered by Astro. Some of the advantages include:
Before we begin, ensure you have the following:
The complete code for this tutorial can be found in this GitHub repository. Clone the repo and follow along!
To create a new Strapi 5 project, run the following command:
npx create-strapi@latest gallery-project
The above command will prompt you to select the preferred configurations for your Strapi 5 project. Your selection should look like the screenshot below:
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.
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:
1
2
3
4
5
6
7
8
9
10
11
export default ({ env }) => ({
upload: {
config: {
providerOptions: {
localServer: {
maxage: 300000
},
},
},
},
});
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.
Scroll down to Upload, tick Select find, findOne, upload, and click Save.
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 at least three image files for this demo.
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.
To start, let’s create a new Astro project. Run the following commands in your terminal:
npm create astro@latest media-app
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:
Then navigate into the new project directory and start the Astro development server:
cd ./my-media-app
npm run dev
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!
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:
1
2
3
4
5
6
7
8
9
10
11
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;
}
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.
1
export const API_URL = "http://localhost:1337";
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
---
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>
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.
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
---
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>
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:
1
2
3
4
5
6
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
output: 'server',
});
Update the code in your pages/index.astro
file to be able to navigate to this new page:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<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>
...
Now click on any of the images to navigate to view more details about the image:
Finally, let’s handle image uploads directly from the Astro front-end and send them 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
---
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">×</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>
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.
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.
I'm a Software Engineer and Technical Writer who specializes in Node.js and JavaScript. I'm passionate about technology and sharing knowledge.