Building a Blog with Next.js, NestJS, and HackMD/CodiMD

8 min read

Introduction

This article introduces a generalized Next.js + NestJS + HackMD (CodiMD) system based on the system used for this blog. It is based on Vercel’s official Blog Starter Kit.

Links

Repository

https://github.com/MuNeNICK/nextjs-nestjs-codimd

Sample site

https://next-app--main--next-nest-codimd--munenick.coder.munenick.me/

Installation

Building Docker environment

Prepare a docker-compose environment first. See the article below: https://www.munenick.me/blog/centos8-stream-docker

Clone the repository

Clone the repository using the following command.

git clone https://github.com/MuNeNICK/nextjs-nestjs-codimd

First time setup

A few manual steps are required during the first setup.

Rewriting entrypoint.sh

There is an entrypoint.sh file in both the frontend and backend folders. Rewrite them as follows.

entrypoint.sh for frontend
#!/bin/bash

cd /usr/src/app

bash

# if grep next package.json >/dev/null; then
#     echo Next exists.
# else
#     cd /usr/src/app
#     yarn create next-app --example blog-starter .
# fi

# while read line
# do
#   if grep "$line" package.json >/dev/null; then
#     echo $line exists.
#   else
#     echo $line does not exist.
#     yarn add $line
#   fi
# done < /packages.txt


# if [ "$STATUS" == "development" ]; then
#   echo Starting in developer mode...
#   yarn dev
# elif [ "$STATUS" == "production" ]; then
#   echo Starting in production mode...
#   yarn build
#   yarn start
# else 
#   echo Invalid environment variable.
# fi
entrypoint.sh for backend
#!/bin/bash

cd /usr/src/app
    
bash

# if grep nest package.json >/dev/null; then
#     echo Nest exists.
# else
#     echo Nest does not exist.
#     git clone https://github.com/nestjs/typescript-starter.git .
#     yarn install
# fi

# while read line
# do
#   if grep "$line" package.json >/dev/null; then
#     echo $line exists.
#   else
#     echo $line does not exist.
#     yarn add $line
#   fi
# done < /packages.txt


# if [ "$STATUS" == "development" ]; then
#   echo Starting in developer mode...
#   yarn prisma studio &
#   yarn start:dev
# elif [ "$STATUS" == "production" ]; then
#   echo Starting in production mode...
#   yarn start
# else 
#   echo Invalid environment variable.
# fi

Building an application

Build the application with the following command.

docker-compose up -d --build

Installing required packages

You need to install packages in the Next.js and NestJS containers. Install packages in the Next.js container:

docker-compose exec next bash
yarn install

Install packages in the NestJS container:

docker-compose exec nest bash
yarn install

Stopping Docker

Stop Docker with the command below.

docker-compose down

Rewriting entrypoint.sh

Restore the original entrypoint.sh files.

Rewriting docker-compose.yml

Rewrite STATUS in the next and nest environment fields of docker-compose.yml as follows.

  • production: used when releasing the site
    • Build and launch with Next.js
  • development: used when developing the site
    • Start in developer mode
version: "3"

services:
  next:
    build:      
      context: .
      dockerfile: ./frontend/Dockerfile
    container_name: next-container
    hostname: next-server
    tty: true
    environment:
      - STATUS=production
      - WATCHPACK_POLLING=true
      - NEXT_PUBLIC_BACKEND_URL=${PUBLIC_BACKEND_URL}
      - NEXT_PUBLIC_HACKMD_URL=${PUBLIC_HACKMD_URL}
    ports:
      - 3000:3000
    volumes:
      - ./frontend/app:/usr/src/app
    restart: always

  nest:
    build:      
      context: .
      dockerfile: ./backend/Dockerfile
    container_name: nest-container
    hostname: nest-server
    tty: true
    environment:
      - STATUS=production
      - DATABASE_URL=${POSTGRES_URL}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - JWT_SECRET=${JWT_SECRET}
    ports:
      - 3001:3000
      - 5555:5555
    volumes:
      - ./backend/app:/usr/src/app
    restart: always

  codimd:
    build:      
      context: .
      dockerfile: ./codimd/Dockerfile
    container_name: codimd-container
    hostname: codimd-server
    environment:
      - CMD_DB_URL=${POSTGRES_URL}
      - CMD_DOMAIN=${PUBLIC_DOMAIN}
      - CMD_URL_PATH=${CODIMD_PATH}
      - CMD_PROTOCOL_USESSL=true
      - CMD_IMAGE_UPLOAD_TYPE=filesystem
      - CMD_ALLOW_EMAIL_REGISTER=false
      - CMD_ALLOW_ANONYMOUS=true
    depends_on:
      - postgres
    ports:
      - "3002:3000"
    volumes:
      - upload-data:/home/hackmd/app/public/uploads
    restart: always

  postgres:
    image: postgres:11.6-alpine
    container_name: postgres-container
    hostname: postgres-server
    environment:
      - POSTGRES_USER=codimd
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=codimd
    volumes:
      - "database-data:/var/lib/postgresql/data"
    restart: always

volumes:
  database-data: {}
  upload-data: {}

Rewriting .env file

Rewrite each parameter to suit your own environment. This site requires a JWT token. You can generate a token using the following command.

node -e "console.log(require('crypto').randomBytes(256).toString('base64'));"
## Arbitrary parameters. Rewrite as appropriate.

## POSTGRES-related parameters
POSTGRES_PASSWORD=ChangeMe
POSTGRES_URL=postgres://codimd:${POSTGRES_PASSWORD}@postgres/codimd

## CODIMD-related parameters
CODIMD_PASSWORD=ChangeMe
## If subdirectories are used, describe their paths
CODIMD_PATH=

## URL-related parameters
PUBLIC_DOMAIN=
PUBLIC_FRONTEND_URL=
PUBLIC_BACKEND_URL=
PUBLIC_HACKMD_URL=

## JWT token. To generate a token:
## node -e "console.log(require('crypto').randomBytes(256).toString('base64'));"
JWT_SECRET=""

Starting Docker

Start Docker with the command below.

docker-compose up -d --build

The repository is now ready to use.

How to use the sample site

This section explains how to use the sample site.

Login

https://next-app--main--next-nest-codimd--munenick.coder.munenick.me/login Open the URL and enter the following email and password.

[email protected]
testtesttest

Enter them to log in.

Dashboard

We have a dashboard page. Clicking “new posts” takes you to the new post page. You can move to each page by clicking the links displayed in the list.

New post

You can create a new post by clicking “new posts” from the dashboard or by accessing the link below. https://next-app--main--next-nest-codimd--munenick.coder.munenick.me/new

When you open CodiMD, the following is written by default.

---
title: 'Enter title-slug here'
displayTitle: 'Enter display title here'
excerpt: 'Blog Description'
coverImage: '/assets/blog/hello-world/cover.jpg'
date: '2020-03-16T05:35:07.322Z'
author:
  name: Tim Neutkens
  picture: '/assets/blog/authors/tim.jpeg'
ogImage:
  url: '/assets/blog/hello-world/cover.jpg'
---
  • title: slug name, used in URL. Example: hello-world
  • displayTitle: Display title, corresponding to the blog title.
  • excerpt: Article description
  • coverImage: cover image path
  • date: Posting date and time
  • author: author’s name and photo path
  • ogImage: Image path used for OG image

After editing each item and writing the main text, you can post using the Post button at the bottom right.

Editing

When you open an article while logged in, an Edit button will be displayed at the bottom right.

Before login

After login

Clicking the Edit button moves you to the article editing screen.

After editing, click the View button at the bottom right to finish.

Delete

Go to the article editing screen.

A Delete button will appear at the bottom right.

Clicking the Delete button displays a confirmation screen for deleting the article. You can delete the article by clicking OK.

Logout

You can log out by returning to the dashboard and clicking the LOGOUT button.

After logging out, the Edit button will no longer be displayed.

Background

I will briefly explain why I created a blog system with Next.js + NestJS + HackMD (CodiMD). When creating blogs with Next.js, many examples use cloud-based headless CMS services such as microCMS. However, I wanted to create a blog site without using cloud services. Then I remembered an OSS project called Growi. It is wiki software that can be edited with HackMD (CodiMD). Inspired by this, I thought it should be possible to embed CodiMD in an iframe and manage blog articles without using a headless CMS, so I created this blog.