Easyread
Published in

Easyread

Changes Storage Destination from Local Disk into Google Cloud Storage in Strapi v4

In production mode, you also could integrate Google Cloud Storage in Strapi v4

Photo by Markus Winkler on Unsplash

Hi everyone, I’m Pandhu and I am a Software Engineer. Welcome to my blog, happy reading guys!

Today, I’ll share with you how to solve upload files in Heroku when connected to Google Cloud Storage.

When I deployed for the first time (without bringing the file upload feature — read: media on Google Cloud Storage) on Heroku, a good result for me when all features worked well. I can do anything over there in production mode until I found the problem of not being able to view and upload media or see pictures there.

In short, I found this in the community that I can do that in Heroku, cause…

When you deploy it, your public/uploads will be deleted automatically. You can try deployment and use your Heroku CLI heroku run bash then ls public/uploads . The images will be empty over there. You must read the FAQ that I have provided above for this reason.

So that’s why you have to change the destination storage in the project. In my case, I modified it into Google Cloud Storage.

Strapi supports to do integration on Google Cloud Storage. Any popular package, the package is strapi-provider-upload-google-cloud-storage by
Vanessa Lith (CMIIW about the name).

That package support Strapi v3 and also Strapi v4. The documentation is also straight to the point and pretty clear. Please check this supercool package out.

Okay, keep scrolling down everyone.

1. Installation

Install the package from your app root directory.

with npm

npm install strapi-provider-upload-google-cloud-storage --save

or yarn

yarn add strapi-provider-upload-google-cloud-storage

2. Create your Bucket on Google Cloud Storage

The bucket should be created with fine-grained access control, as the plugin will configure uploaded files with public read access.

How to create a bucket?

Where my bucket can be located?

3. Setting up Google authentication

If you are deploying to a Google Cloud Platform product that supports Application Default Credentials (such as App Engine, Cloud Run, Cloud Functions, etc.), then you can skip this step.

If you are deploying outside GCP, then follow these steps to set up authentication:

  1. In the GCP Console, go to the Create service account key page. Go to the create service account key page
  2. From the Service account list, select New service account.
  3. In the Service account name field, enter a name.
  4. From the Role list, select Cloud Storage > Storage Admin.
  5. Select JSON for Key Type
  6. Click Create. A JSON file that contains your key downloads to your computer.
  7. Copy the full content of the downloaded JSON file
  8. Open the Strapi configuration file
  9. Paste it into the “Service Account JSON” field (as string or JSON, be careful with indentation)

4. Create file plugins.js in your production directory

Create or Edit config/env/production/plugins.js

const fs = require('fs');
require('dotenv').config();
module.exports = ({ env }) => ({
upload: {
config: {
provider: 'strapi-provider-upload-google-cloud-storage',
providerOptions: {
serviceAccount: JSON.parse(fs.readFileSync(process.env.GCS_SERVICE_ACCOUNT)),
bucketName: env('GCS_BUCKET_NAME'),
basePath: env('GCS_BASE_PATH'),
baseUrl: env('GCS_BASE_URL'),
publicFiles: true,
uniform: false,
gzip: true,
},
},
},
});

Don’t forget to install dotenv for the additional package.

If you have a different upload provider by the environment, you can override plugins.js file by environment :

  • config/env/development/plugins.js
  • config/env/production/plugins.js

This file, under config/env/{env}/ will be overriding default configuration present in the main folder config.

The environment variable can be changed has your way.

How to configure variables?

serviceAccount :

JSON data provided by Google Account (explained before). If you are deploying to a GCP product that supports Application Default credentials, you can leave this omitted, and authentication will work automatically.

Can be set as a String, JSON Object, or omitted.

bucketName :

The name of the bucket on Google Cloud Storage.

  • Required

You can find more information on Google Cloud documentation.

baseUrl :

Define your base Url, first is default value :

basePath :

Define the base path to save each media document.

  • Optional

publicFiles:

Boolean to define a public attribute to a file when it is uploaded to storage.

  • Default value : true
  • Optional

uniform:

Boolean to define uniform access, when uniform bucket-level access is enabled.

  • Default value : false
  • Optional

cacheMaxAge:

Number to set the cache-control header for uploaded files.

  • Default value : 3600
  • Optional

gzip:

Value to define if files are uploaded and stored with gzip compression.

  • Possible values: true, false, auto
  • Default value : auto
  • Optional

metadata:

A function that is executed to compute the metadata for a file when it is uploaded.

When no function is provided, the following metadata is used:

{
contentDisposition: `inline; filename="${file.name}"`,
cacheControl: `public, max-age=${config.cacheMaxAge || 3600}`,
}
  • Default value: undefined
  • Optional

Example:

metadata: (file) => ({
cacheControl: `public, max-age=${60 * 60 * 24 * 7}`, // One week
contentLanguage: 'en-US',
contentDisposition: `attachment; filename="${file.name}"`,
}),

The available properties can be found in the Cloud Storage JSON API documentation.

generateUploadFileName:

A function that is executed to generate the name of the uploaded file. This method can give more control over the file name and can for example be used to include a custom hashing function or dynamic path.

When no function is provided, the default algorithm is used.

  • Default value: undefined
  • Optional

Example:

generateUploadFileName: (file) => {
const hash = ...; // Some hashing function, for example MD-5
const extension = file.ext.toLowerCase().substring(1);
return `${extension}/${slugify(path.parse(file.name).name)}-${hash}.${extension}`;
},

5. Setting up strapi::security middlewares to avoid CSP blocked URL

Create or Edit ./config/env/production/middlewares.js

  • In the field img-src and media-src add your own CDN URL, by default it's storage.googleapis.com but you need to add your own CDN URL
module.exports = [
'strapi::errors',
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': ["'self'", 'data:', 'blob:', 'storage.googleapis.com'],
'media-src': ["'self'", 'data:', 'blob:', 'storage.googleapis.com'],
upgradeInsecureRequests: null,
},
},
},
},
'strapi::cors',
'strapi::poweredBy',
'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::favicon',
'strapi::public',
];

6. Deploy to Heroku

Finally, If you have been followed all the instructions, please push your code into the Heroku. Try upload or just get in the media menu, if you keep finding errors, please comment in the comment section.

In my case, I ever got the error Internal Server Error and status code 500.

The way to solve the problem could be because:

  • You haven’t added security middleware
...
{
name: 'strapi::security',
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
'connect-src': ["'self'", 'https:'],
'img-src': ["'self'", 'data:', 'blob:', 'storage.googleapis.com'],
'media-src': ["'self'", 'data:', 'blob:', 'storage.googleapis.com'],
upgradeInsecureRequests: null,
},
},
},
},
...
  • Or you should create file settings.jsonin the extensions/upload/config/
{
"provider": "strapi-provider-upload-google-cloud-storage",
"providerOptions": {
"serviceAccount": {
"type": "service_account",
"project_id": "xxx-xxx-xxxxxxxx",
"private_key_id": "xxxx2b00abxx243b17d8xxd7fe5bf5xx7970cxxx",
"private_key": "xxxxxxxxxxx and so on",
"client_email": "strapi-storage@xxx-xxx-xxxxxxxx.iam.gserviceaccount.com",
"client_id": "xxxxxxnumberxxxxxxxxxx",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x888_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x888_cert_url": "https://www.googleapis.com/robot/v1/metadata/x888/strapi-storage%40xxx-xxx-xxxxxxxx.iam.gserviceaccount.com"
},
"bucketName": "cms-strapi-storage",
"basePath": "",
"baseUrl": "https://storage.googleapis.com/strapi-storage",
"publicFiles": true,
"uniform": false,
"gzip": true
}
}

For the details, you can find on

Yup, that’s a short story to integrate Strapi with Google Cloud Storage.

Thanks to Vanessa Lith and her team cause has been created an awesome project to link Strapi to Google Cloud Storage.

The end. Hopefully, I can help with your issue. Happy reading && Happy Coding. Thank you.

Alert!

If you guys come from Indonesia and want to support me to writing more and more, hopefully you can give a little bit from your wallet. You can share your gift to some ways,

Saweria

https://saweria.co/pandhuwibowo

Trakteer

https://trakteer.id/goodpeopletogivemoney

Reference

Easy read, easy understanding. A good writing is a writing that can be understood in easy ways

Recommended from Medium

Episode 1: Christian Privitelli. English summary.

Style management in Swift

Introduction

Managing production environment using Spring Boot Actuator

My First Book After 10 Years of Blogging — Grokking Java Interviews

Killing Ticks

Ontology Generation and Visualization with Protégé

Error: Get “http://localhost/api/v1/namespaces/xxx": dial tcp [::1]:80: connect: connection refused

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Pandhu Wibowo

Pandhu Wibowo

Assalamu’alaikum. I’m Software Engineer | Tech Enthusiast — Support me on beneteen.com | Follow me : https://www.instagram.com/pandhu.wibowo/

More from Medium

Developing API using NestJS, GraphQL & Oracle DB

Ship a Next.js App with a CI/CD Pipeline.

How to Build a REST API With Express JS and Typescript - Part II (Organising Routes)

Build a real-time Todo App in 30 minutes with ReactJS, TypeScript and Firebase