[Heroku] 佈署你的 Node.js + Express + Sequelize migration 專案到 Heroku 上吧 !


Posted by Powerfultraveling 's Blog on 2021-11-15

前言

之前在 Heroku 部屬了幾個 Express App,雖然設定都差不多,但因為有點複雜(部屬哪個不複雜的?),所以每次還是都會查一次資料,所以想要趁現在記憶猶新的時候記錄下來,以後有需要可以直接回來調閱。

目錄

我使用的技術與工具

  • 專案使用的
  • Heroku 上增加的

環境建置

  • 註冊 Heroku 帳號
  • 安裝 heroku-cli
  • 登入 heroku
  • 設定 package.json 和 port

佈署

  • git init
  • 連結並在 Heroku 上面建立 app
  • git push 佈署

設定資料庫

  • 安裝 ClearDB
  • 取得 Database 相關資料
  • 設定專案資料庫設定檔
  • 在 package.json 新增 script
  • 佈署完成

我使用的技術與工具

部屬的過程會因為使用的工具而有細部的差別(有時候差別很大),這邊是我列出專案所使用的技術與工具:

專案使用的

  • Node.js
  • Express
  • MySQL
  • Sequelize migration

Heroku 上增加的

  • 加上 ClearDB 其他都一樣

環境建置

註冊 Heroku 帳號

官網直接註冊一個帳號吧。

下載 heroku-cli

heroku-cli 是一個 heroku 提供的一款 terminal tools,透過 heroku-cli,使用者可已在 terminal 上面完成所有佈署到 heroku 相關的工作。

這個頁面所有相關資訊寫得很詳細,你也可以直接輸入這些指令:

$ npm install -g heroku // 安裝 heroku-cli
$ heroku --version // 查看是否成功安裝

登入 Heroku

在 terminal 輸入以下指令,會跑出資訊提示你隨便按一個按鍵登入,按了之後就會跳到瀏覽器上的登入頁面囉。

$ heroku login

在瀏覽器上登入完成後,terminal 會跑出登入成功的資訊,按一下 ctrl + c kill 這個 process 之後,就可以接下來的步驟了喔。

設定 package.json 和 port

package.json

Heroku 會依照不同的 runtime 建置不同環境,所以要先設定 package.json 讓他知道目前的 runtime:

//新增這一項讓 heroku 知道你的 runtime 資訊
  "engines": {
    "node": "14.x"
  },
//設定完會長這樣
{
  "name": "menu",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "heroku-postbuild": "sequelize db:migrate",
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "engines": {
    "node": "14.x"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^5.0.1",
    "connect-flash": "^0.1.1",
    "ejs": "^3.1.6",
    "express": "^4.17.1",
    "express-flash": "^0.0.2",
    "express-session": "^1.17.2",
    "flash-express": "^1.0.4",
    "multer": "^1.4.3",
    "mysql": "^2.18.1",
    "mysql2": "^2.3.0",
    "sequelize": "^6.6.5",
    "sequelize-cli": "^6.2.0",
    "swup": "^2.0.14"
  },
  "devDependencies": {
    "sequelize-cli": "^6.2.0"
  }
}

port

不像在 localhost,我們可以自由指定專案要跑在那一個 port 上,heroku 會根據環境變數來決定專案跑的 port,所已在這邊要特別設定一下,打開 entry file (通常叫 index.js),設定 環境變數 const port = process.env.PORT || 3000;:

const express = require("express");
const session = require("express-session");
const flash = require("connect-flash");
const multer = require("multer");
const bodyParser = require("body-parser");
const path = require("path");
const app = express();
const port = process.env.PORT || 3000; 


//body parser
app.use(express.urlencoded({ extended: false }));
app.use(express.json());


//local variables
app.use((req, res, next) => {
  res.locals.user = req.session.user;
  res.locals.errorMessage = req.flash("errorMessage");
  res.locals.successMessage = req.flash("successMessage");
  next();
});

//router
app.get("/", productController.index);
app.get("/delete/:id", productController.productDeleteHandeler);

//listen to port
app.listen(port, () => {
  console.log(`listen on port ${port}`);
});

這個設定可以讓檔案在跑動的時候先尋找跑的環境有沒有環境變數有指定 port,沒有的話就跑在 port 3000 上。

佈署

在專案中 git init

Heroku 最棒的一個部分就是他採用類似 Github 的運作模式,所有的資料都可以透過 git push 的方式直接推到 Heroku 上的遠端空間,剩下的 Heroku 都會幫你完成,所以在正式佈署之前,要先在專案中導入 git。

$ git init

連結並在 Heroku 上面建立 app

進到你的專案資料夾的跟目錄,輸入:

$ cd "專案資料夾"
$ heroku create

就成功在 heroku 上面建置了一個放置這個專案的空間,並且已經連結好本地專案和遠端專案,接下來只要使用 git 推上去就好了。

git push 佈署到 heroku 吧!

接下來的步驟,跟平常將專案 push 到 Github 的流程幾乎一模一樣:

$ git add .
$ git commit -m"initial commit"
$ git push heroku main

terminal 會跑出這個畫面,代表你正在佈署上去 heroku 啦!



到了這一步,假如你佈署的是一個完全 static 沒有串接 database 的 App 的話,基本上就完成佈署了。

但假如你佈署的跟我一樣是一個串接資料庫的 App 的話,你可能會發現最後跑出一堆錯誤訊息:

這是因為,還必須在 heroku 上面安裝 database 、還要完成一些設定才能夠佈署需要資料庫的 application。

設定資料庫

安裝 ClearDB 到專案

$ heroku addons:create cleardb:ignite

拿取資料庫資訊

資料庫相關的資訊 heroku 會以 URL 的形式全部存在環境變數。所以我們要使用下面的指令拿到環境變數:

$ heroku config | grep CLEARDB_DATABASE_URL
//回傳 CLEARDB_DATABASE_URL => mysql://adffdadf2341:adf4234@us-cdbr-east.cleardb.com/heroku_db?reconnect=true

回傳的這組 URL 裡面的資訊其實是:

mysql://<username>:<password>@<host>/<database>

所以之後如果要在 MySQL workbench 之類的環境連結 heroku 上的這個資料庫可以使用這一組資料登入。

設定環境變數

基本邏輯

在這個步驟,如果使用的不是 Sequelize migration 的話,設定細節會有一些不同,但是基本上的邏輯是這樣的:

  1. 打開專案中設定 database 的那個檔案(通常叫做 db.js 的那個檔案)。
  2. 在裡面設定 database 資訊,這樣子專案才知道要連結哪個資料庫。
使用 Sequelize migration 的話

如果跟我一樣使用 Sequelize migration 的話可以直接照著下面的步驟完成 !

首先先給大家看我的專案結構,假如你是使用 Sequelize migration 的話你也會有一個 config folder 裡面有一個 config.js,這是專門設定 databse 的檔案:

打開之後,到 production 的那一區塊新增一行資料 "use_env_variable": "DATABASE_URL":

{
  "development": {
    "username": "powerfultraveling",
    "password": "1111",
    "database": "menu",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql",
    "use_env_variable": "DATABASE_URL"//新增在這裡
  }
}

接下來,在 heroku 上面把剛剛拿到的那組 URL 放到叫做 DATABASE_URL 的變數裡面

$ heroku config:set DATABASE_URL='mysql://adffdadf2341:adf4234@us-cdbr-east.cleardb.com/heroku_db?reconnect=true'

這麼做的話,專案在佈署上去的時候就會根據 config.js 裡面的 "use_env_variable": "DATABASE_URL" 這段資訊去尋找環境中有沒有 DATABASEA_URL 的變數,進而連結到資料庫。

而檔案之所以會這樣自己去找 config.js 裡面的資訊是因為放在 models 目錄底下的 index.js:

'use strict';

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};

//這一段定義了一段邏輯來決定使用一般的設定還是環境變數
let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}

fs
  .readdirSync(__dirname)
  .filter(file => {
    return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
  })
  .forEach(file => {
    const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
    db[model.name] = model;
  });

Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

這邊結束後,基本上就完成 90 趴了,接下來剩下最後一個部分。

在 package.json 設定 postbuild script

在正式進到 building 的 process 之前,要先在 database 裡面先建置好 tables,才能繼續接下來的步驟,因此要在 package.json 裡新增 "heroku-postbuild": "sequelize db:migrate",,讓他在正式建立之前先跑這個 script 建立 tables.

{
  "name": "menu",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "heroku-postbuild": "sequelize db:migrate",
    "start": "node index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "engines": {
    "node": "14.x"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^5.0.1",
    "connect-flash": "^0.1.1",
    "ejs": "^3.1.6",
    "express": "^4.17.1",
    "express-flash": "^0.0.2",
    "express-session": "^1.17.2",
    "flash-express": "^1.0.4",
    "multer": "^1.4.3",
    "mysql": "^2.18.1",
    "mysql2": "^2.3.0",
    "sequelize": "^6.6.5",
    "sequelize-cli": "^6.2.0",
    "swup": "^2.0.14"
  },
  "devDependencies": {
    "sequelize-cli": "^6.2.0"
  }
}

佈署完成

接下來,push 上去 heroku 之後,就大功告成啦!

$ git push heroku main










Related Posts

component test 問題集4(React18 + TS + Jest + react-testing-library)

component test 問題集4(React18 + TS + Jest + react-testing-library)

WEB 網路基礎概念

WEB 網路基礎概念

邪魔歪道還是苦口良藥?Functional CSS 經驗分享

邪魔歪道還是苦口良藥?Functional CSS 經驗分享


Comments