• Project Online Preview
  • Github source repository

Many of the articles are configuration, you can directly copy the past directly, or directly download the source code to run, first run through the project.

1. Major package version and advance preparation

  1. package version
package version
koa ^ 2.13.4
sequelize ^ 6.14.1
vite ^ 2.7.13
vue ^ 3.2.25
pinia ^ 2.0.6
  1. Prepare in advance
  • 1 online server, Baidu Cloud, Ali cloud and so on, and install a good Docker (tutorial self-search, very simple). If you don’t have one, use a local VIRTUAL machine instead and do your own research
  • Register a Dockerhub account, ali cloud server will own container image service can be used, you can not register dockerhub
  • Sign up for a Github account and create a repository. This article takes Github as an example, gitee’s own research

2. Deconstruction of the project catalog

The structure of the project directory is simple, the front-end application is placed in the Web directory, the database is placed in the mysql directory, and the other is the background KOA application

├ ─ ─ docker - compose. Yml # docker - compose configuration file ├ ─ ─ Dockerfile # background of koa container mirror configuration file ├ ─ ─ the log # log storage | ├ ─ ─ Access. The log - 2022-01-28. The log | ├ ─ ─ application. Log - 2022-01-28. The log | └ ─ ─ mysql. The log - 2022-01-28. The log ├ ─ ─ middleware # koa middleware | ├ ─ ─ handleGlobalError. Js | ├ ─ ─ index. The js | └ ─ ─ logClientDevices. Js ├ ─ ─ # mysql mysql configuration file | ├ ─ ─ create_db. # to create SQL database SQL | ├ ─ ─ Dockerfile # mysql container mirror configuration file | ├ ─ ─ initial_data. SQL # initialization data table SQL | ├ ─ ─ privileges. SQL # modify permissions on the mysql account password | └ ─ ─ setup. Sh # container startup execution of shell ├ ─ ─ package - lock. Json ├ ─ ─ package. The json ├ ─ ─ public # koa - static static file configuration directory | ├ ─ ─ the favicon. Ico | └ ─ ─ Index2. HTML ├ ─ ─ the README. Md ├ ─ ─ routes # @ koa/router interface routing | ├ ─ ─ todoList. Js | └ ─ ─ uuid. Js ├ ─ ─ for server js # koa boot file ├ ─ ─ Util | ├ ─ ─ the js # mysql connection configuration | └ ─ ─ logger. The logging configuration of js # koa - log4 └ ─ ─ # vue web front-end application ├ ─ ─ Dockerfile ├ ─ ─ index. The HTML ├ ─ ─ Nginx. Conf ├ ─ ─ package. Json ├ ─ ─ public ├ ─ ─ the README. Md ├ ─ ─ the SRC | ├ ─ ─ App. Vue | ├ ─ ─ assets | ├ ─ ─ components | ├ ─ ─ main. Js | | ├ ─ ─ services # interface └ ─ ─ store # Pinia state management ├ ─ ─ vite. Config. Js └ ─ ─ yarn. The lockCopy the code

3. Front end

Of course, you should write todolist application with Pinia state Management + vue3

3.1 Pinia status management for quick start, masturbating to a document

// src/store/index.js
import { defineStore } from "pinia";
import * as types from "./types";

export const useTodosStore = defineStore("todos", {
  state: () = > ({
    / * *@type {{ msg: string, id: string, is_finished: boolean, create_time: date }[]} * /
    todos: [].filter: types.ALL,
    nextId: 0,}).getters: {
    finishedTodos(state) {
      return state.todos.filter((todo) = > todo.is_finished);
    },
    unfinishedTodos(state) {
      return state.todos.filter((todo) = >! todo.is_finished); },filterTodos(state) {
      if (this.filter === types.FINISHED) {
        return this.finishedTodos;
      } else if (this.filter === types.UNFINISHED) {
        return this.unfinishedTodos;
      }

      return this.todos; }},actions: {
    addTodos({ id, msg, create_time }) {
      this.todos.unshift({ id, msg, create_time, is_finished: false });
    },
    finishedOneTodo(obj) {
      const index = this.todos.findIndex((item) = > item.id === obj.id);
      this.todos.splice(index, 1, {
        ...obj,
        is_finished: true}); },deleteOne(id) {
      this.todos = this.todos.filter((item) = >item.id ! == id); },setInitialData(arr) {
      this.todos = [...arr]; ,}}});Copy the code

Error while updating Dependencies vite+element-plus

Error part:

Morning 11:02:22 [vite] new dependencies found: element - plus/es, element - plus/es/components/option/style/CSS, updating... 11:02:22 am [vite] Failed to load source map for /node_modules/. Vite/chunk-tpoprdhf.js? v=e12284c2. > error: Failed to write to output file: open D:\my\resource\Vue.js\vue3+Pinia\node_modules\.vite\element-plus.js: Access is denied. 11:02:37 [vite] error while updating dependencies: error: Build failed with 1 error: error: Failed to write to output file: open D:\my\resource\Vue.js\vue3+Pinia\node_modules\.vite\element-plus.js: Access is denied. at failureErrorWithLog (D:\my\resource\Vue.js\vue3+Pinia\node_modules\esbuild\lib\main.js:1493:15) at D:\my\resource\Vue.js\vue3+Pinia\node_modules\esbuild\lib\main.js:1151:28 at runOnEndCallbacks (D:\my\resource\Vue.js\vue3+Pinia\node_modules\esbuild\lib\main.js:941:63) at buildResponseToResult (D:\my\resource\Vue.js\vue3+Pinia\node_modules\esbuild\lib\main.js:1149:7) at D:\my\resource\Vue.js\vue3+Pinia\node_modules\esbuild\lib\main.js:1258:14 at D:\my\resource\Vue.js\vue3+Pinia\node_modules\esbuild\lib\main.js:629:9 at handleIncomingPacket (D:\my\resource\Vue.js\vue3+Pinia\node_modules\esbuild\lib\main.js:726:9) at Socket.readFromStdout (D:\my\resource\Vue.js\vue3+Pinia\node_modules\esbuild\lib\main.js:596:7) at Socket.emit (events.js:400:28) at addChunk (internal/streams/readable.js:290:12)Copy the code

(deleting node_modules and reinstalling, upgrading to the latest version, and checking whether the folder path is Chinese…) Later, I found a solution:

Upgrade Node.js to the latest version (the latest version is 16.13.2), delete the node_modules folder, install dependencies again, and start normally

If you are using NVM, nodeJS can be upgraded quickly like this

NVM install 16.13.2 NVM use 16.13.2Copy the code

3.3 nginx. Conf

server { listen 80; Charset UTF-8; #charset koi8-r; #access_log /var/log/nginx/host.access.log main; Docker-compose = docker-compose = docker-compose = docker-compose = docker-compose = docker-compose = docker-compose = docker-compose = docker-compose # rewrite ^/api/(.*)$ /$1 break; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; client_max_body_size 100m; } location / { root /usr/share/nginx/dist; Index.html index.htm; Try_files $uri /index.html; # prevent browser refresh after page 404 client_max_body_size 100m; } location =/admin { deny all; } location ~\. Java ${deny all; } error_page 500 502 503 504/50x.html; Error_page 404/404.html; #404 page location = /50x. HTML {root /usr/share/nginx/html; } gzip on; # enable gzip gzip_buffers 32 4k; Gzip_comp_level 6 set the size of the buffer required for compression, in 4k, if the file size is 32K, apply 32* 4K buffer. # gzip_min_length 4000; # gzip_min_length 4000; # gzip_min_length 4000; #gizp compression starting point, files larger than 4K to compress; Vary: accept-encoding (gzip_static on); #nginx will search for files that end with.gz and return them directly. Gzip_types text/ XML text/javascript application/javascript text/ CSS text/plain Application /json application/x-javascript; # Type of file to compress}Copy the code

3.4 Dockerfile

Use multi-stage builds to reduce container size

FROM alpine:3.15 AS base
ENV NODE_ENV=production \
  APP_PATH=/app
WORKDIR $APP_PATH
Install nodejs and YARN using the apk command
RUNApk add --no-cache --update nodejs=16.13.2-r0 YARN

FROM base AS install
COPY package.json yarn.lock $APP_PATH/
RUN yarn install

FROM base AS build
Copy the node_modules folder to the final working directory
COPY --from=install $APP_PATH/node_modules $APP_PATH/node_modules
# copy files from current directory to working directory (except ignored in.dockerignore)
COPY . $APP_PATH/
RUN yarn run build

FROM nginx:alpine
WORKDIR /usr/share/nginx/dist
# Add your own configuration default.conf below
ADD nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist .
EXPOSE 80
Copy the code

4. Backstage

4.1 Import File

// server.js
const path = require("path");
const Koa = require("koa");
const Router = require("@koa/router");
const ROOT = path.resolve(process.cwd(), ". /");
const { connectMySQL } = require(path.resolve(ROOT, "./util/db"));
const todoList = require(path.resolve(ROOT, "./routes/todoList"));

const app = new Koa();
const router = new Router();

// Load all child routes
router.use("/api", todoList.routes(), todoList.allowedMethods());
// Load the routing middleware
app.use(router.routes()).use(router.allowedMethods());

app.listen(4000.async() = > {await connectMySQL();
});
Copy the code

4.2 Sequelize Connects to the mysql database

// util/db.js
const path = require("path");
const { Sequelize } = require("sequelize");

const db = new Sequelize("todolist"."root"."123456", {
  dialect: "mysql".dialectOptions: {
    charset: "utf8mb4".collate: "utf8mb4_unicode_ci".supportBigNumbers: true.bigNumberStrings: true,},Docker-compose = docker-compose = docker-compose = docker-compose = docker-compose = docker-compose
  host:
    process.env.NODE_ENV === "development"
      ? "localhost"
      : "todolist_mysql_server".timezone: "+ 08:00." "./ / east eight area
  port: "3306"});const connectMySQL = async() = > {try {
    await db.authenticate();
    console.log(Mysql connection successful);
  } catch (e) {
    console.log(e);
    console.log("Connection failed. Try again in 3 seconds.");
    setTimeout(connectMySQL, 3000); }};exports.connectMySQL = connectMySQL;
exports.db = db;
Copy the code

4.3 Interface Examples

// routes/todolist.js
const Router = require("@koa/router");
const dayjs = require("dayjs");
const utc = require("dayjs/plugin/utc"); // dependent on utc plugin
const timezone = require("dayjs/plugin/timezone");
const { QueryTypes } = require("sequelize");

const ROOT = path.resolve(process.cwd(), ". /");
const { db } = require(path.resolve(ROOT, "./util/db"));

const todoList = new Router();
// https://dayjs.gitee.io/docs/zh-CN/plugin/timezone
dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault("Asia/Shanghai");

// List query
todoList.get("/todoList/list".async (ctx, next) => {
  const reqParams = ctx.query;
  // https://github.com/demopark/sequelize-docs-Zh-CN/blob/master/core-concepts/getting-started.md#promises-%E5%92%8C-asyncaw ait

  const selects = {
    0: "SELECT * FROM todolist WHERE is_finished='0' ORDER BY create_time DESC;".1: "SELECT * FROM todolist WHERE is_finished='1' ORDER BY create_time DESC;".2: "SELECT * FROM todolist ORDER BY create_time DESC;"};const filterType = reqParams.filterType || "2";

  try {
    let list = await db.query(selects[filterType], {
      type: QueryTypes.SELECT,
    });

    list = list.map((item) = > ({
      ...item,
      create_time: dayjs(item.create_time)
        .tz("Asia/Shanghai")
        .format("YYYY-MM-DD HH:mm:ss"),
      is_finished: item.is_finished === "0" ? false : true,})); ctx.body = {code: 200.data: list || [],
      msg: "ok"}; }catch (e) {
    console.log(e);
  }

  await next();
});

module.exports = todoList;
Copy the code

4.4 Dockerfile

FROM alpine:3.15 AS base

ENV NODE_ENV=production \
  APP_PATH=/www/node-server

WORKDIR $APP_PATH

Install nodejs using the apk command
RUNApk add --no-cache --update nodejs=16.13.2-r0 NPM

Install project dependencies based on the underlying image
FROM base AS install

# copy package.json from the current directory to the working directory
COPY package.json package-lock.json $APP_PATH/

RUN npm install

# Final build based on base image
FROM base

Copy the node_modules folder to the final working directory
# COPY When you COPY a folder, you do not COPY the folder directly, but COPY the contents of the folder to the destination path
COPY --from=install $APP_PATH/node_modules $APP_PATH/node_modules
# copy files from current directory to working directory (except ignored in.dockerignore)
COPY . $APP_PATH/

EXPOSE 4000

CMD ["npm"."run"."server"]
Copy the code

4.5 Precautions

  • Configure the time zone for database connectiontimezone, set it to east 8
  • Set the database character set toutf8mb4For storing emoticons
  • dayjsLibrary adjustment shows the time for east 8 zone
  • sequelizeDuplicate entries (non-duplicate data in the database) are found and need to be configuredtypeforQueryTypes.SELECT

5. MySQL part

The selected version is 5.7.30. Online version 8.0 is not deployed successfully

5.1 the mysql/Dockerfile

FROM mysql:5.7.30

LABEL version="1.0.0" description=Todolist MySQL Server
WORKDIR /mysql

ENV MYSQL_ROOT_PASSWORD=123456
# MYSQL_DATABASE=todolist
# MYSQL_ALLOW_EMPTY_PASSWORD=yes

# startup script
COPY setup.sh /mysql/setup.sh
Create database
COPY create_db.sql /mysql/create_db.sql
Initialize data
COPY initial_data.sql /mysql/initial_data.sql
Set password and permission
COPY privileges.sql /mysql/privileges.sql

EXPOSE 3306

CMD ["sh"."/mysql/setup.sh"]
Copy the code

5.2 mysql/setup.shContainer startup script

#! /bin/bash
set -e
# https://xie.infoq.cn/article/a3c8ffbd34d818de010f2b0f6
Print the status of the mysql service
echo $(service mysql status)

echo '1. Start the mysql... '
# start mysql
# service mysql stop
# service mysql restart
service mysql start

# sleep 3
echo '2. Create database... '
mysql </mysql/create_db.sql
sleep 3

echo '3. Start importing data... '
mysql </mysql/initial_data.sql

sleep 3
echo $(service mysql status)

echo Mysql > alter database; '
mysql </mysql/privileges.sql
sleep 3
echo '4. Permission modification is complete... '

Prevent the Container from exiting after it is started
# http://www.mayanpeng.cn/archives/121.html
tail -f /dev/null
Copy the code

5.3 mysql/create_db.sqlCreating a database

CREATE DATABASE IF NOT EXISTS todolist;
Copy the code

5.4 mysql/initial_data.sqlInitialize the table

-- Use the Todolist library
USE todolist;

Create todolist table
CREATE TABLE IF NOT EXISTS todolist(id VARCHAR(50) PRIMARY KEY, create_time DATETIME UNIQUE, is_finished ENUM('0'.'1') DEFAULT '0', msg VARCHAR(100) DEFAULT The '-') COMMENT='todolist table' ENGINE=InnoDB DEFAULT CHARSET=utf8;

Print database
SHOW TABLES;

Print the table structure
DESC todolist;

Insert 1 default data
INSERT INTO todolist(id, create_time, is_finished, msg) VALUES(1, NOW(), '0'.'hello world');
Copy the code

5.5 mysql/privileges.sqlSetting permission Password

use mysql;
SELECT host, user FROM user;

Grant database permission to user root with password 123456
GRANT ALL PRIVILEGES ON *.* TO 'root'@The '%' IDENTIFIED BY '123456';

-- Refresh permission must have:
flush privileges;
Copy the code

6 Preparing for Deployment

6.1 Prepare in advance

  1. indockerhubAccount orAli Cloud image container serviceCreate 3 repositories for mysql, KOA and Vue applicationstodolist_mysql,koaandvite_vue3_pinia, in the docker-comemage. yml configuration file below

  1. In yourgithubConfigure the dockerhub account and password above in the warehousesecretsTo facilitate the use of github repository actions push later. So what I’m creating here isDOCKERHUB_TOKENPut a password,DOCKERHUB_USERNAMEPut the account

6.2 docker-compose.yml

version: "3"
services:
  mysql: # mysql
    build: ./mysql
    The # image name is the name created in the DockerHub
    image: Your Docker account name /todolist_mysql:latest
    container_name: todolist_mysql_server
    restart: always
    # MYSQL_ROOT_PASSWORD: "123456"

  node: # nodejs service
    depends_on:
      - "mysql"
    build: . # build the directory where Dockerfile resides
    image: Your Docker account name/KOA: Latest
    container_name: koa_server
    # ports:
    # - "8001-4000"
    restart: always Automatic restart
    environment:
      - NODE_ENV=production
    command: npm run server Overrides the commands executed by default when the container is started

  vue:
    depends_on: # vue is guaranteed to start after node
      - "node"
    build: ./web
    image: Your Docker account name /vite_vue3_pinia:latest
    container_name: vite_vue3_pinia
    restart: always
    environment:
      - NODE_ENV=production
    ports:
      - "8001:80"

networks:
  default:
    external:
      name: vue3-koa
Copy the code

6.3 Github CI Configuration File

# .github/workflows/ci.yml
name: MySQL + Koa2 Server + Vue3 todolist

on:
  push:
    branches: [main] # listen on the main branch

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      Make a Docker image and push it to dockerHub
      - name: build and push docker image
        run: | # used here created above the secrets of variable # login you docker account docker login - u ${{secrets. DOCKERHUB_USERNAME}} - p ${{secrets. DOCKERHUB_TOKEN Docker-compose compose: docker-compose compose: docker-compose compose: docker-compose compose: docker-compose compose: docker-compose compose: docker-compose compose: docker-compose composeCopy the code

6.4 Submitting Code

Can’t wait, it’s time to commit code

git add .

git commit -m "feat: init"

git push -u origin main
Copy the code

Not surprisingly, the build was successful in the Actions of the repository.

7. Portainerappearance

After all this trouble, Todolist hasn’t seen it yet. It’s my fault. Last step. Don’t give up.

7.1 Benefits of Portainer

This is just like git, using git GUI tools and git bash command lines to facilitate container-managed operations. If you prefer the command line, skip this section and deploy containers directly.

Docker compose up is composed for your git repository.

7.2 Logging In to your server installationportainerI have installed version 1.24.2 here

If you have Docker installed on your server

  1. download
docker pull portainer/portainer:latest
Copy the code
  1. run

Note: here your server’s security group is open to port 9000

docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --restart=always --name prtainer portainer/portainer
Copy the code

7.3 the portainer speaking

In the browser, open http://public IP address of your server :9000, set your account and password, and configure the local node

7.3.1 Pull the image you just pushed to the DockerHub

I’m going to pull all three mirror images

If it is pushed to ali Cloud image container, click Registry on the left menu, and then click Add Registry to Add your image container URL. At the same time, you can configure Authentication, your account and password, and then you can pull the image as above

7.3.2 Configuring container NetWork for container communication

I can’t take it. How many more steps? You lied to me. It really will be over soon

The essence is to use the Docker network, and then add their own containers to the network

Docker network Create User-defined bridge name Docker Network Connect User-defined bridge name Container nameCopy the code

So here we’re using a graphical interface

7.3.3 Deploying containers

I can’t take it anymore. It’s not over. It’s over this time

Click Container to add a container. You must deploy MySQL first, Koa then vUE application in the same order as docker-compose

Deploy the three containers in sequence, and the container names and ports must be the same as those in the configuration file

7.3.4 Amida Buddha

XDM, it’s really over. I tortured you. If all goes well, all three containers are deployed successfully

Open your browser and visit your todolist, http://your IP :8001

7.3.5 Failed to Save Emoji

The MSG column did not use the UTF8MB4 character set when the table was created. Then why don’t you set it up when you build the meter? I’m sorry, XDM. Punch me

If you feel complicated and uncomfortable, skip it

Docker goes into container combat

If you log in to your server

  1. View running containers
docker container ls
Copy the code
  1. Into the container
docker exec-it Indicates the CONTAINER ID of the mysql CONTAINER shCopy the code
  1. Connect the mysql
mysql -h localhost -u root -p
#Then enter your password
Copy the code

Unsurprisingly, we managed to enter the container and connect to mysql

[root@iZbp19ftqv2b85av0b2d4qZ /]# docker exec -it a206f021c205 sh # mysql -h localhost -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 53 Server version: 5.7.30 MySQL Community Server (GPL) Copyright (C) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help; ' or '\h' for help. Type '\c' to clear the current input statement. mysql>Copy the code
  1. Modify the MSG column attribute of the todolist table
  • Use todolist library
USE todolist;
Copy the code
  • modified
mysql> ALTER TABLE todolist MODIFY msg VARCHAR(100) character set utf8mb4 collate utf8mb4_unicode_ci default The '-';
Query OK, 6 rows affected (0.04 sec)
Records: 6  Duplicates: 0  Warnings: 0
Copy the code
  • Verify that the modification is successful

As you can see, the MSG column has been modified successfully

mysql> SHOW CREATE TABLE todolist\G
*************************** 1. row ***************************
       Table: todolist
Create Table: CREATE TABLE `todolist` (
  `id` varchar(50) NOT NULL,
  `create_time` datetime DEFAULT NULL,
  `is_finished` enum('0'.'1') DEFAULT '0',
  `msg` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT The '-'.PRIMARY KEY (`id`),
  UNIQUE KEY `create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='todolist table'
1 row in set (0.00 sec)
Copy the code
  • Enter emojis in the browser to add a test, no surprise, it is ok. It’s over!!

8. Reference materials

  1. Docker container deployment practice using Portainer

  2. MySql Dockerfile preparation

  3. Pinia document

  4. Sequelize Chinese document

  5. dayjs

  6. 【 field 】 How to write logs in Node service?