There are multiple ways to manage images and data within our projects.
In my case, I've chosen Strapi since it offers numerous advantages and convenience for handling our project's persistence. In this guide, I'll show how we can store our project's images using Strapi alongside a free minIO Bucket. To obtain a free minIO Bucket with +80GB storage, you can check out my post How I deployed all my projects on servers with 24GB RAM 4CPU for free.Preamble
Using this structure ensures that we don't have to store images in the same project, taking up valuable space, which translates into increased resource consumption on our server and much slower deployment.
Decoupling multimedia files to a minIO Bucket allows for extensive scalability in our project. It's almost mandatory when needing to create hundreds or thousands of entities with associated images or videos.
The structure we want to achieve would look something like this:
As a prerequisite, we'll need minIO, Strapi, and our front-end deployed.
Required Configuration
Install strapi-provider-upload-ts-minio
npm install strapi-provider-upload-ts-minio
Add to .env:
MINIO_ACCESS_KEY=<minio key>
MINIO_SECRET_KEY=<minio secret key>
MINIO_ENDPOINT=<minio.example.com>
MINIO_BUCKET=<minio bucket name>
Add to our strapi project in: src/config/plugins.ts:
export default ({ (parameter) env: (envKey: string) => stringenv }: {(property) env: (envKey: string) => stringenv: ((parameter) envKey: stringenvKey: string ) => string}) => ({
(property) upload: {
config: {
provider: string;
providerOptions: {
accessKey: string;
secretKey: string;
bucket: string;
endPoint: string;
};
};
}upload: {
(property) config: {
provider: string;
providerOptions: {
accessKey: string;
secretKey: string;
bucket: string;
endPoint: string;
};
}config: {
(property) provider: stringprovider: 'strapi-provider-upload-ts-minio',
(property) providerOptions: {
accessKey: string;
secretKey: string;
bucket: string;
endPoint: string;
}providerOptions: {
(property) accessKey: stringaccessKey: (parameter) env: (envKey: string) => stringenv('MINIO_ACCESS_KEY'),
(property) secretKey: stringsecretKey: (parameter) env: (envKey: string) => stringenv('MINIO_SECRET_KEY'),
(property) bucket: stringbucket: (parameter) env: (envKey: string) => stringenv('MINIO_BUCKET'),
(property) endPoint: stringendPoint: (parameter) env: (envKey: string) => stringenv('MINIO_ENDPOINT'),
},
},
},
});
If we've configured everything correctly, we should see the image automatically uploaded to minIO when we upload it.
And to use it, we simply add a "media" type to our entity.
Select the image from the gallery:
And we can use it like this:
import (alias) namespace React
import ReactReact from "react";
export const const Image: () => React.JSX.ElementImage = () => {
const [const postImage: stringpostImage, const setPostImage: React.Dispatch<React.SetStateAction<string>>setPostImage] = (alias) namespace React
import ReactReact.function React.useState<string>(initialState: string | (() => string)): [string, React.Dispatch<React.SetStateAction<string>>] (+1 overload)Returns a stateful value, and a function to update it.useState("");
type type Post = {
attributes: {
title: string;
featuredImage: {
data: {
attributes: {
url: string;
};
};
};
};
}Post = {
(property) attributes: {
title: string;
featuredImage: {
data: {
attributes: {
url: string;
};
};
};
}attributes: {
(property) title: stringtitle: string;
(property) featuredImage: {
data: {
attributes: {
url: string;
};
};
}featuredImage: {
(property) data: {
attributes: {
url: string;
};
}data: {
(property) attributes: {
url: string;
}attributes: {
(property) url: stringurl: string;
};
};
};
};
};
let let url: stringurl =
"https://mistrapi.com/api/posts?populate[featuredImage][fields][0]=url";
function fetch(input: RequestInfo | URL, init?: RequestInit | undefined): Promise<Response>[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)fetch(let url: stringurl)
.(method) Promise<Response>.then<any, never>(onfulfilled?: ((value: Response) => any) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<any>Attaches callbacks for the resolution and/or rejection of the Promise.then(((parameter) response: Responseresponse) => (parameter) response: Responseresponse.(method) Body.json(): Promise<any>[MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json)json())
.(method) Promise<any>.then<void, never>(onfulfilled?: ((value: any) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>Attaches callbacks for the resolution and/or rejection of the Promise.then(((parameter) rawPosts: anyrawPosts) => {
const const posts: Post[]posts: type Post = {
attributes: {
title: string;
featuredImage: {
data: {
attributes: {
url: string;
};
};
};
};
}Post[] = (parameter) rawPosts: anyrawPosts.anydata;
const const firstPost: PostfirstPost = const posts: Post[]posts[0];
const { const featuredImage: {
data: {
attributes: {
url: string;
};
};
}featuredImage, const title: stringtitle } = const firstPost: PostfirstPost.(property) attributes: {
title: string;
featuredImage: {
data: {
attributes: {
url: string;
};
};
};
}attributes;
const const postImage: stringpostImage = const featuredImage: {
data: {
attributes: {
url: string;
};
};
}featuredImage?.(property) data: {
attributes: {
url: string;
};
}data?.(property) attributes: {
url: string;
}attributes.(property) url: stringurl;
if (const postImage: stringpostImage) {
const setPostImage: (value: React.SetStateAction<string>) => voidsetPostImage(const postImage: stringpostImage);
}
});
return <(property) JSX.IntrinsicElements.img: React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>img (property) React.ImgHTMLAttributes<HTMLImageElement>.src?: string | undefinedsrc={const postImage: stringpostImage} />;
};