Our Content Management System (CMS)

A Content Management System (CMS) is needed to hold all our articles and data for the website to run – we're using Strapi. This seciton can be thought of as the admin side - or back-end, and will show the steps taken to set up Strapi, connect it to our front-end application, and deploy it.

Strapi banner with text: Strapi v4 Is Live

Strapi

Prototypr uses Strapi for the CMS. Strapi is an Open Source headless CMS that acts as the backend of the Prototypr app.

In a 'headless CMS' setup, the front-end is completely separated from the back end, so there are 2 parts of the Prototypr app, both in different GitHub Repos:

Learn more about headless CMS:

A headless CMS is any type of back-end content management system where the content repository “body” is separated or decoupled from the presentation layer “head.” Content that is housed in a headless CMS is delivered via APIs for seamless display across different devices.

Installation

The Strapi installation instructions are here, but below are the steps we took when installing Strapi for Prototypr:

1. Install a compatible Node Version

The important parts are to install the right Node version. We are using 14.17.4. You can install it using nvm, and use it like this: nvm use 14.17.4

2. Use Postgres Database

❗️Before installing Strapi, set up a Postgresql database by following this tutorial.

Next, create the Strapi application using this command (at the time, we are using Strapi v. 4.0.7):

npx create-strapi-app@latest prototypr-backend

Rather than using the recommended settings which includes an sql database, choose a customised setup and use postgres. This choice has been made so it casily be deployed on Digital Ocean App Platform for hosting.

3. Run Strapi

Now you can run Strapi from the project folder you just created (prototypr-backend):

npm run develop. Other commands:

Available commands in your project:
npm run develop
Start Strapi in watch mode. (Changes in Strapi project files will trigger a server restart)
npm run start
Start Strapi without watch mode.
npm run build
Build Strapi admin panel.
npm run strapi
Display all available commands.
You can start by doing:
cd /Users/graeme/Documents/workspace/prototypr-backend
npm run develop

From here, set up your username and password for local use. Next we need to set up the content types and components for the CMS.

Content Types

At this point, you should be able to log into the Strapi dashboard, but it looks empty, like this:

Screenshot of Strapi dashboard

From here, we set up the Prototypr content types.

We already had this done in the previous version of Strapi, but they need recreating to work with Strapi 4.

Components

In Prototypr, we have a few different collection types from our original WordPress site - posts, news, blogs, inspiration, and newsletters. Each of these content types can have similar data, for example, most of them need a 'Gallery' to hold images. That's what components are for:

Components are reusable structures you can share between all your content types. Components can be included in any content type either as a single entry or a list of entries for meta information, links, sections list or any repeatable content.

The components can be seen in our old Strapi version 3 repository, in the components folder: prototypr-backend-old/components/. These include:

  • attributes – these are used for news links, including ogImage, ogDescription and other meta.
  • featured-image – featured images contain a srcset, thumb, and different sizes.
  • gallery – similar to featured image, gallery items have image size attributes
  • legacy-media - we have migrated from WordPress, and this component is for the WP media stuff
  • seo – we have migrated from WordPress, and this component is for the WP SEO stuff
  • post-type (see below)

Collection Types

Strapi: Easily create collection types to repeat the same type of content like blog posts, products, users or any list of content you can think of.

In Wordpress, we had a bunch of 'post types', including: posts, news, blogs, inspiration, and newsletters. But it got overwhelming, looking like this:

Wordpress dashboard

Posts Collection

Therefore we will have 1 collection type, called posts, and a post-types content type to filter them. This means everything can share the same tags too.

Post Types

Content Model

As outlined above, instead of having many different collection types for each type of post (article, newsletter, news, inspiration), they can all fit into a common 'Post' collection since they all share similar properties.

This will keep the Strapi dashboard simpler and easier to navigate. To distinguish between post types, an Enumaration called type is used (read about enums here). The type enumeration is shown in the following screenshot as the top field:

Strapi post model

Next, an author and tags collection are added, a bit like this Strapi tutorial on creating a CSS Tricks clone.

WordPress Migration

We have a separater repository, called wordpress-strapi-migration, which has a README.md on how to migrate our content from Wordpress. It reads content from different post types using the WP-GraphQL endpoints, and inserts them into Strapi with a Post request. The outcome looks like this:

Strapi dashboard showing posts that can be filtered by types such as article or tool

As you can see above, you can filter between article, tool, newsletter etc, and they're all included as regular posts.

Strapi Issues

Database

These are issues that came up a few times when installing Strapi - mostly due to the database connection:

Finding the database name

>graeme@Graemes-MacBook-Air prototypr-frontend % postgres -V

>postgres (PostgreSQL) 14.1

>graeme@Graemes-MacBook-Air prototypr-frontend % psql postgres

psql (14.1)
Type "help" for help.
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | Access privileges
-----------+--------+----------+---------+-------+---------------------
postgres | graeme | UTF8 | C | C |
prototypr | graeme | UTF8 | C | C | =Tc/graeme +
| | | | | graeme=CTc/graeme +
| | | | | graylien=CTc/graeme
strapi | graeme | UTF8 | C | C | =Tc/graeme +
| | | | | graeme=CTc/graeme +
| | | | | graylien=CTc/graeme

Cannot connect to Database

Sometimes, you can get the error:

Building your admin UI with development configuration ...
Admin UI built successfully
[2022-02-07 16:36:55.378] debug: ⛔️ Server wasn't able to start properly.
[2022-02-07 16:36:55.379] error: connect ECONNREFUSED 127.0.0.1:5432
Error: connect ECONNREFUSED 127.0.0.1:5432
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1148:16)

This is because Postgres is not running. To start it, for mac you can try:

pg_ctl -D /usr/local/var/postgres start

And if you're seeing this error now: 2022-02-07 16:03:24.771 GMT [21548] DETAIL: The data directory was initialized by PostgreSQL version 13, which is not compatible with this version 14.1., upgrade postgres:

brew postgresql-upgrade-database

If you get the error, Cannot shutdown old postmaster when upgrading to Postgres 9.2, here is a solution that worked for me.

Strapi Cannot Handle Lots of Posts

There is an issue in Strapi with database relations that crashes the admin dashboard when it contains 1000s of posts. A workaround is to create a new user on the admin dashboard, and remove the permission to edit tags and authors in Posts. This ensures tags and author relations cannot load, so the dashboard doesn't crash. The downside is you can't edit tags/authors.

Deploying Strapi

We're using Digital Ocean App Platform for deploying the Strapi backend. It's possible to use other services though, such as Heroku, AWS, and more – Strapi have deployment guides for all of them here. The outcome of this will be the Prototypr/Strapi backend running at your own URL - it won't include any data, posts or user accounts!

This section will cover some steps and issues when deploying Strapi in development mode on the DO App platform.

Create the project

  1. First, make sure you have the Prototypr backend cloned to your own repository in GitHub
  2. Create a new App in DO App Platform, and choose the Prototypr backend from your GitHub Account as the source:

Screenshot of digital ocean app platform, showing creation of new app with GitHub option

  1. Set it up on the basic tier ($10 a month), with the following in the Environmental variables section (taken from the .env file):
## STRAPI ADMIN
## /config/admin.js
ADMIN_JWT_SECRET=<jwt key>
## DATABASE SETTINGS
## /config/database.js
DATABASE_HOST = ${db.HOSTNAME}
DATABASE_NAME = ${db.DATABASE}
DATABASE_PORT = ${db.PORT}
DATABASE_USERNAME = ${db.USERNAME}
DATABASE_PASSWORD = ${db.PASSWORD}
DATABASE_URL = ${db.DATABASE_URL}
NODE_ENV = production
## STANDARD STRAPI KEYS
APP_KEYS = <put your keys here>
JWT_SECRET = <put your jwt here>
API_TOKEN_SALT = <put your token here>
## Additional for account creation
ADMIN_TOKEN=<token from the strapi dashboard>
STRAPI_URL=http://localhost:1337
## MAILGUN PLUGIN KEYS FOR EMAIL NOTIFICATIONS
MAILGUN_API_KEY=<mailgun api key>
MAILGUN_DOMAIN=<mailgun domain>
## 
DO_SPACE_ACCESS_KEY=<do spaces access key>
DO_SPACE_SECRET_KEY=<do spaces secret>
DO_SPACE_ENDPOINT=sfo2.digitaloceanspaces.com
DO_SPACE_BUCKET=prototypr-media
DO_SPACE_DIRECTORY=strapi
DO_SPACE_CDN=
NODE_TLS_REJECT_UNAUTHORIZED = 0

Notes on Env variables:

  • Wherever you se db. (e.g. db.HOSTNAME), db is your database name that you chose when creating the database.
  • The ADMIN_TOKEN comes from the strapi dashboard itself. When logged in as admin, create a new API token from the dashboard: admin/settings/api-tokens, and paste that in! It's used to for the backend to make requests that only admins are allowed to do.
  • APP_KEYS, JWT_SECRET, and API_TOKEN_SALT can be found in the .env file at the root of your Strapi project. If you don't have one because you cloned our repo (it's excluded from GitHub for security), it looks like this:
HOST=0.0.0.0
PORT=1337
APP_KEYS=key1,key2,key3,key4
JWT_SECRET=32characternumberhere
API_TOKEN_SALT=32characternumberhere

Screenshot of environmental variables in DO app platform. See below for text version

These are created automatically with any new Strapi project, but if you are cloning the Prototypr repository, you may have to create your own:

Creating Keys

For each of the toBeModified in the snippet below, paste in a string, generated by running openssl rand -base64 32 in your terminal (as reccommended in this section of the Strapi docs).

APP_KEYS="toBeModified,toBeModified"
API_TOKEN_SALT=tobemodified
ADMIN_JWT_SECRET=tobemodified
JWT_SECRET=tobemodified

Here's more ways to generate random keys for fun:

More issues

  • NODE_TLS_REJECT_UNAUTHORIZED - this is added to avoid SSL issues in developement. We were getting the following error:
[2022-02-15 00:24:33] > strapi start
[2022-02-15 00:24:33]
[2022-02-15 00:24:42] [2022-02-15 00:24:42.373] debug: ⛔️ Server wasn't able to start properly.
[2022-02-15 00:24:42] [2022-02-15 00:24:42.377] error: no pg_hba.conf entry for host "139.59.196.138", user "db", database "db", SSL off
[2022-02-15 00:24:42] error: no pg_hba.conf entry for host "139.59.196.138", user "db", database "db", SSL off
[2022-02-15 00:24:42] at Parser.parseErrorMessage (/workspace/node_modules/pg-protocol/dist/parser.js:287:98)

An additional note is that inside config/database.js, the following setting has been added:

ssl: {rejectUnauthorized: false},

This ensures the rejectUnauthorized flag is used when deployed on the App platform. When running locally, we switch it back to:

ssl: env.bool('DATABASE_SSL', false),

Learn more about the config files in this video.

This method must be used in the real production app!

Connect your Database

In addition to the above, you also need to make sure you have a database connected to your project. The default in DO App Platform is Postgresql, which is what we have used when creating the app.

screenshot of connecting a database

You can add a database at any point, but make sure the name corresponds to that used in your Environmental variables (see above re: db.HOSTNAME).

Securing the database user

The default user from this setup is called 'db'. In digital ocean app platform, you can add a new db user from the database management section:

2 Screenshots of Digitalocean dashboard, highlighting the database management url, and then where to add a database user

After you add the user, you must grant them priveleges on your database. The Digital Ocean web interface doesn't have that option, instead you need to connect via a terminal using psql. There is a guide here. You'll notice the permissions in DigitalOcean's guide don't work. Instead, when adding permissions, use the commands in this answer.

If you've changed your database username, you'll also need to update it in your app settings: Screenshot of app settings section in digital ocean

Done

With that, you should be up and running. For more information, check out this blog post, which has useful videos for deploying the backend.

Working with Digital Ocean databases

For Prototypr, we have set up a development app, and a production app on the App platform. That's 2 separate apps, but the developement one is using sandbox resources so is around half the price.

We needed to move data from production to development, here are some ways:

Plugin

This plugin is the easiest way for moving post collections: Import export plugin! In the future, I'll provide some way to seed the database with some sample Prototypr data.

Postgres commands:

Here's how you might export the entire db:

pg_dump -h <your_host> -U <your_username> -p 25060 -Fc <your_database> > <path/to/your_dump_file.pgsql>

pg_restore -d <your_connection_URI> --jobs 4 <path/to/your_dump_file.pgsql>

Authentication

For users to log in, we set up the social login providers in Strapi, and log in from the Next.js front-end.

Set up Strapi Auth Providers

For authentication to work, the providers section must be set up in Strapi. We're using GitHub and Twitter to start with:

Screenshot of strapi dashboard with GitHub and Twitter auth providers highlighted

Social Credentials

Below is a screenshot of the GitHub fields:

  • Client ID and Client Secret will be the same ID and Secret used in the Next.JS front-end .env file!
  • Make sure the redirect URL is correctly set to the Next frontend too, like this:
http://localhost:3000/api/auth/callback/github

Here's a screenshot of the filled out GitHub fields. It's very similar for Twitter, but overall, it's very important to get the callback URL correct first time to save some hours debugging 😅!

Screenshot of strapi GitHub fields

Callback URLs in Social Apps❗️

Another important part is the Callback URL used when creating social apps in Twitter or GitHub. The Callback URL is the NextAuth route from the Next.js application, not anything from the Strapi dashboard! For example, for Twitter, it is:

https://prototypr-frontend-1s0hl5bwy-prototypr.vercel.app/api/auth/callback/twitter

or for localhost:

http://localhost:3000/api/auth/callback/twitter

This is often the cause of the issue when you see the error: Try signing with a different account. in a blue box.