# 🆓 [Open-Source] Strapi  Node.js Headless CMS 🌏

🎯 An Open-Source NodeJS-based Content Management System with a fully customizable API. You can save time and effort by creating production-ready Node.js APIs in hours rather than weeks. 🚀

🎯  Deliverables:
  * Strapi CMS Backend Template: https://www.npmjs.com/package/strapi-blog
  * Strapi CMS Backend: https://github.com/OceanSoftIO/cms-blog/tree/main/backend
    * `Dockerfile`
    * `Dockerfile.prod`
    * `docker-compose.yml`

🌥️ The *cloud journey* generally involves migrating and modernizing websites and apps, including building and hosting websites, developing web and mobile apps, and monitoring and managing them. This hands-on series illustrates how to build a production-ready **Headless CMS** and **Headless eCommerce** using Jamstack (stands for JavaScript, API, and Markup) on Cloud (Heroku, AWS ...).

1. ✅ ⚡ [Building a Production-Ready Headless CMS with Jamstack (Gatsby and Contentful)](https://blog.oceansoft.io/headless-cms-with-gatsby-contentful) 🎁
2. ✅ 🐳 [Dockerizing Strapi - Open-Source NodeJS Headless CMS](https://blog.oceansoft.io/strapi-nodejs-headless-cms)
3. ☑️ 🐳 [Medusa Headless-eCommerce - Open-Source Shopify alternative](https://blog.oceansoft.io/medusa-headless-ecommerce-shopify-alternative) ⚡

---

## 1. Overview of Strapi Headless CMS

The Strapi Headless CMS is an Open-Source, Node.js platform for creating, managing, exposing and sharing content-rich experiences. 

Traditional or monolithic Web-first CMS, such as WordPress, combine the frontend (website design and layout) and backend (the interface for editing and creating content) into a single application.
The next-generation Content-first Headless CMS use an API for content delivery and allow complete separation of the backend (creation and storage) and frontend (design and deployment). Headless architecture not only rewards better performance and flexibility but also provides stronger security by making it nearly impossible for end-users to access the backend.

![Strapi Architecture.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1665496191527/OBDXVr0mH.png align="left")

Developers are free to focus on designing amazing frontends that meet the needs of their customers because of all these benefits.

* _Frontend_: Developers can use their preferred frontend technology to deliver high-quality content experiences while quickly integrating features like authentication (via social media logins), content delivery, and payment processing into a full-fledged business application. 
* _REST & GraphQL API_: The Strapi backend API makes content accessible and displayable on any device via a GraphQL or REST API. However, the exclusive focus is GraphQL API.
* _Database Independent_: A variety of database systems can be configured, including PostgreSQL, MySQL, MongoDB, and SQLite. However, we only configure Strapi for use with PostgreSQL.


## 2. Installing [Strapi CMS-Backend](https://github.com/OceanSoftIO/cms-blog.git)

* 🚦 Prerequisites

  * [👨‍💻 Setup Development and Testing Environment on MacOS](https://academy.job4u.io/setup-development-and-testing-environment-on-macos/)
  * ✅ Node.js v16
  * ✅ Yarn v1
  * ☑️ Gatsby CLI

* _Option 1._ Git clone from https://github.com/OceanSoftIO/cms-blog.git

  ```
  git clone https://github.com/OceanSoftIO/cms-blog.git
  cd cms-blog
  ```

* _Option 2._ Install [Strapi CMS Backend](https://www.npmjs.com/package/strapi-blog) with the blog schema template outside of the frontend directory on your machine.

  ```
  echo "Strapi V4 template: https://github.com/OceanSoftIO/cms-blog/tree/main/template"
  yarn create strapi-app backend --quickstart --template strapi-blog

  echo "Strapi V3 OLD-version !!!"
  # yarn create strapi-app cms --quickstart --template https://github.com/OceanSoftIO/cms-blog
  ```

* Following the installation, Strapi’s control panel will open in your browser, where you can register the admin user and create content.
* [Frontend (Gatsby or Next.js) integration](https://github.com/Academy4U/strapi-blog)

### 2.1. Powerful CMS-Backend GraphQL APIs

```sh
cd cms-blog/backend

yarn install
yarn develop
```

* 🌏 http://localhost:1337/admin

* [🎁 Installation Service >> 🔒 Postman-GraphQL 💲](https://github.com/OceanSoftIO/cms/tree/main/postman)

### 2.2. Testing and Deploying the Strapi API


## 3. Docker Setup

> Developers are faced with the task of launching a development environment that has different software packages of certain versions. Fortunately, Docker solves this problem in the modern development world.

> Creating `Dockerfile` & `docker-compose.yml`

  ```
  # cd cms-blog/backend
  # cat Dockerfile
  # cat docker-compose.yml
  ```

* [🐳 Dockerfile](https://github.com/Academy4U/docker/blob/docker/strapi/strapi/Dockerfile)

  > 🐳 Creating a new `Dockerfile`: If you are using YARN, please use the following 👇

  ```docker
  FROM node:16
  ## Installing libvips-dev for sharp Compatability
  RUN apt-get update && apt-get install libvips-dev -y

  # FROM node:16-alpine
  ## Installing libvips-dev for sharp Compatability
  # RUN apk update && apk add  build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev

  ARG NODE_ENV=development
  ENV NODE_ENV=${NODE_ENV}
  WORKDIR /opt/
  COPY ./package.json ./yarn.lock ./
  ENV PATH /opt/node_modules/.bin:$PATH
  RUN yarn config set network-timeout 600000 -g && yarn install
  WORKDIR /opt/app
  COPY ./ .
  RUN yarn build
  EXPOSE 1337
  
  CMD ["yarn", "develop"]
  ```

* [🐳 Dockerfile.Prod](https://github.com/Academy4U/docker/blob/docker/strapi/strapi/Dockerfile.npm)

  > 🐳 Optimizing your `Dockerfile` ☠️, then please use the following 👇

  The following are the methods by which we can achieve docker image optimization.

  ![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1666085241940/qS61h1rdU.png align="left")

  * Using distroless/minimal base images
  * Multistage builds
  * Minimizing the number of layers
  * Understanding caching
  * Using Dockerignore
  * Keeping application data elsewhere

  ```dockerfile
  FROM node:16-alpine as build
  ## Installing libvips-dev for sharp Compatability
  RUN apk update && apk add build-base gcc autoconf automake zlib-dev libpng-dev vips-dev && rm -rf /var/cache/apk/* > /dev/null 2>&1
  ARG NODE_ENV=production
  ENV NODE_ENV=${NODE_ENV}
  WORKDIR /opt/
  COPY ./package.json ./yarn.lock ./
  ENV PATH /opt/node_modules/.bin:$PATH
  RUN yarn config set network-timeout 600000 -g && yarn install
  WORKDIR /opt/app
  COPY ./ .
  RUN yarn build

  FROM node:16-alpine
  RUN apk add vips-dev
  RUN rm -rf /var/cache/apk/*
  ARG NODE_ENV=production
  ENV NODE_ENV=${NODE_ENV}
  WORKDIR /opt/app
  COPY --from=build /opt/node_modules ./node_modules
  ENV PATH /opt/node_modules/.bin:$PATH
  COPY --from=build /opt/app ./
  EXPOSE 1337
  CMD ["yarn", "start"]
  ```  

* Quick tour of `Dockerfile`: 👇

  > 🐳 To get started, let's take a quick tour of Dockerfile: 👇

   + Initially, we'll use `node:16` (~330 MB) or `node:16-alpine` (~39 MB) as our base image. 
   + We'll install some libraries, like `libvips-dev` for sharp compatibility, with `-y`, so say yes to everything.   
   + The node environment **AVG** will be set to `development` by default so we don't have to provide this each time. 
   + The **ENV** allows us to override it if we want to switch from `development` to `production`.
   + We'll define our file paths and whatnot in the `/opt` WORKDIR working folder inside our container.
   + We copy package.json and yarn.lock (or package-lock.json if you're using npm) into our work directory. Docker caches each layer, so doing this first will speed up our build process.
   + Docker then knows where to find our node_modules
   + In case of network problems or a bit of slow internet, we will set a large timeout 600000 to allow extra time.
   + Afterwards, `yarn install` installs all dependencies.
   + Then we change the directories to /opt/apps
   + Next, we copy the project we created in step 1, `cms-backend`, into this folder.
   + We then run `yarn build` to build our MEAN project.
   + Finally, we expose port `1337` and tell Docker to run `yarn develop`


* `.dockerignore` 👇

  > 🐳 Docker Ignore: Create a file called `.dockerignore`: 👇

  ```.dockerignore
  .tmp/
  .cache/
  public
  .git/
  build/
  node_modules/
  data/
  ```

  ✍️ These folders in `.dockerignore` will be skipped ⛔️ by Docker 🐳 since they are not necessary.

---

## 4. Building & Running the Docker Image

* 1. Building the Docker Image

  `docker build -t cms-backend:latest .`

  + The name of the docker image is `cms-backend`, and it's tagged with `:latest`
  + Lastly, grab a cup of coffee ☕️, normally a few minutes , and sit back while Docker does its magic 🪄

  `docker system prune --all --force`

* 2. Running the Docker Image

  `docker run -d -p 1337:1337 cms-backend`

   + Docker will run the image cms-backend, or whatever you called your project, 🤔 on port 1337.
   + `-d` means detached and is a fancy way of saying "Runs in the background"
   + Tip: To use strapi on another port while developing, change the first part of the run port.

    `docker run -d -p 8888:1337 cms-backend`

    [run on port 8888](http://localhost:8888/admin/) 👍

  + Finally, [run on port 1337](http://localhost:1337/admin/) 👍 

> ✍️ We are currently using an SQLite database, which is always inside the container. Whenever we stop a container, we lose all changes. Using `docker-compose`, we can use a Postgres database and run multiple instances of Docker if needed.

---

## 5. Utilizing `docker-compose` for the next level

* ⬆️ https://github.com/Academy4U/docker/blob/docker/strapi/README.Strapi.md

* 🪄 Think of `docker-compose` as a way to make different steps or services that we want to run.

* 🔔 https://github.com/Academy4U/docker/blob/docker/strapi/strapi/docker-compose.yml

1. `config/database.js`

  > ⚙️ `config/database.js` 👇

  ```javascripts
  const path = require('path');

  // module.exports = ({ env }) => ({
  //   connection: {
  //     client: 'sqlite',
  //     connection: {
  //       filename: path.join(__dirname, '..', env('DATABASE_FILENAME', '.tmp/data.db')),
  //     },
  //     useNullAsDefault: true,
  //   },
  // });

  /** PostgreSQL Database */
  module.exports = ({ env }) => ({
    connection: {
      client: env("DATABASE_CLIENT", "postgres"),

      connection: {
        host:     env("DATABASE_HOST", "127.0.0.1"),
        port:     env.int("DATABASE_PORT", 5432),
        database: env("DATABASE_NAME", "cms"),
        user:     env("DATABASE_USERNAME", "cms"),
        password: env("DATABASE_PASSWORD", "cms"),
      },
      debug: false,
    },
  });

  ```

2. `.env`

  <details open>
  <summary>⚙️ `.env` 👇</summary>

  ```env
  HOST=0.0.0.0
  PORT=1337

  ...

  DATABASE_HOST=localhost
  DATABASE_PORT=5432
  # DATABASE_PORT=3306
  DATABASE_NAME=cms
  DATABASE_USERNAME=cms
  DATABASE_PASSWORD=cms
  NODE_ENV=development
  DATABASE_CLIENT=postgres
  # DATABASE_CLIENT=mysql
  ```

  </details>

  > 🐳 In the root of the project, create a file called `docker-compose.yml`. Due to the YAML format, spacing matters, so I've used spaces rather than tabs 👇

  ```dockerfile
  version: "3"
  services:
    cms:
      container_name: cms
      build: .
      image: cms:latest
      restart: unless-stopped
      env_file: .env
      environment:
        DATABASE_CLIENT: ${DATABASE_CLIENT}
        DATABASE_HOST: cmsDB
        DATABASE_NAME: ${DATABASE_NAME}
        DATABASE_USERNAME: ${DATABASE_USERNAME}
        DATABASE_PORT: ${DATABASE_PORT}
        JWT_SECRET: ${JWT_SECRET}
        ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
        DATABASE_PASSWORD: ${DATABASE_PASSWORD}
        NODE_ENV: ${NODE_ENV}
      volumes:
        - ./config:/opt/app/config
        - ./src:/opt/app/src
        - ./package.json:/opt/package.json
        - ./yarn.lock:/opt/yarn.lock ##Replace with package-lock.json if using npm
        - ./.env:/opt/app/.env
      ports:
        - "1337:1337"
      networks:
        - cms
      depends_on:
        - cmsDB

    cmsDB:
      image: postgres:12.0-alpine
      container_name: cmsDB
      platform: linux/amd64 ##for platform error on Apple M1 chips
      restart: unless-stopped
      env_file: .env
      environment:
        POSTGRES_USER: ${DATABASE_USERNAME}
        POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
        POSTGRES_DB: ${DATABASE_NAME}
      volumes:
        - cms-data:/var/lib/postgresql/data/ ##using a volume
        #- ./data:/var/lib/postgresql/data/  ##if you want to use a bind folder
      ports:
        - "5432:5432"
      networks:
        - cms

  volumes:
      cms-data:

  networks:
    cms:
      name: cms
      driver: bridge
  ```

 > 📚 I'll explain what all of this means: 👇

   + `version` - [Docker-compose version 3](https://docs.docker.com/compose/compose-file/compose-versioning/)
   + `services` - We are defining two services cms and cmsDB
   + `cms` - The name of the service we defined
   + `container_name` - The name of the container. You can call it whatever you want.
   + `build` - Telling cms to build the image in our project folder `.`.
   + `image` - The image name we want to build
   + `restart` - Unless we STOP or take down the container, it will keep restarting.
   + `env_file` - Providing a .env file containing the environmental variables we should keep secret. 
   + `environment` - Here we define all the variables we want to use. Our `.env` file will have `$[THISISOURNAME]` as a placeholder.
   + `volumes` - mounting files into the container. Now this could be ./:/opt/app, but we might want to develop locally and just run our development server locally we are binding folders and some files to not bind node_modules There is some info about that here. 
   + `ports` - What ports we want to expose. Note: You can change the left side to another port, such as 8080:1337, but remember that the right side needs to be 1337, which is the port inside the container where CMS is running.
   + `networks` - Set up a docker network so that our containers can communicate together. The Docker-Network tells Docker that before running the cms container, we need to run the postgresDB container first. This saves us some errors when we start the CMS container without a database.

   + Similarly, we give Postgres a name, but we use the official `postgres:12.0-alpine` image instead of building it ourselves. In addition, we are creating a volume called `cms-data` to hold our database.

   + ✍️ When installing Docker Desktop for MacOS, docker-compose is also automatically installed; however, for Linux, you must separately install it.


### 🚀 Running our project

* [ ] 🐳 **Local**: This will now spin up just a Postgres database, and we can run and change files just like working on Strapi anywhere.

  `docker-compose up -d cmsDB && yarn develop`

* [x] 🐳 **Full**: This will run Strapi inside a Docker Container and the database in its own container.

  __`docker-compose up -d`__

---

## Next Steps

* 1️⃣ Backend Deployment using Render, Heroku, GCP, `AWS`
* 2️⃣ Frontend: Gatsby Cloud, Netlify, `AWS Amplify`
* 3️⃣ Infrastructure as Code: Build and Deploy Application to `AWS App Runner || ECS/EKS` using `Terraform` and `AWS CodePipeline`
