# 實作紀錄:將 express 部署到 Heroku
## 前置準備
Heroku account, Node.js, npm, Git, Heroku CLI 以及使用 CLI 登入 Heroku 帳號 `heroku login`。
其他:
* 部署在 Heroku 上面的程式,Heroku 都會用環境變數來指定一個 port 來接收 request,例如`PORT=4444 node index.js`,所以需要讓程式碼使用接收到的環境變數來作為 port,Node.js`process.env.PORT`來取得環境變數。
* sequelize migration,搭配 script 來自動建好 table,Heroku 會先判斷有沒有 Procfile 存在,不存在則預設會跑 package.json 的 "start" script,這次部署完之後,要做 migration 和執行 `node index.js`,所以在 package.json 加上`"start": "npx sequelize db:migrate && node index.js"`。
> To determine how to start your app, Heroku first looks for a Procfile. If no Procfile exists for a Node.js app, we will attempt to start a default web process via the start script in your package.json.
> [Doc](https://devcenter.heroku.com/articles/deploying-nodejs)
## Deploy the app
`heroku create`,創造一個 app (部署的空間)
`heroku git:remote -a your-app-name`,Heroku 使用 Git 來做部署,這邊將 Heroku 的 app 和本地端得 Git 做遠端連結
安裝資料庫(clearDB),`heroku addons:create cleardb:ignite -a your-app-name`
`git push heroku branch-name`,接著把已經在本地端做好測試的程式 push 到 Heroku,就完成部署了。
Ps, 要部署的程式如果有用到資料庫,可以去 Heroku [申請使用免費的 clearDB(要綁信用卡)](https://elements.heroku.com/addons)
## 一些用 express, sequelize 時遇到的 error 和一些不熟的知識點
### 引入 CSS 失敗
這次使用 ejs 這個 template engine,會從`./views/` 來尋找 .ejs 檔,如果要用到 css 檔或是其他路徑的檔案,要告訴 express 該檔案的路徑,`app.use(express.static('./views'))`,如此就可以找到`/css/stylesheet.css`(完整路徑是`./views/temlate/stylesheet.css`)。
`app.use(express.static(__dirname + '/views'))// 另一種方法,絕對路徑`
> http://expressjs.com/en/5x/api.html#express.static
https://dylan237.github.io/nodejs-dirname-and-filename.html
https://stackoverflow.com/questions/18629327/adding-css-file-to-ejs
https://github.com/froala/angular-froala/issues/170
## npm install --save-dev sequelize-cli?
sequelize migration 使用 sequelize-cli 來操作,官方文件給的指令是安裝在package.json 的"devDependencies",`npm install --save-dev sequelize-cli`。
npm 文件有提到
> "dependencies": Packages required by your application in production.
> "devDependencies": Packages that are only needed for local development and testing.
因為這次部署上 Heroku 之後才做 migration,所以要安裝在"dependencies"。
## tinyint?
`is_deleted: DataTypes.BOOLEAN// TINYINT(1)`,sequelize 的 boolean存的是0/1,不是true/false
## Sequelize: add new column in migration
https://dev.to/nedsoft/add-new-fields-to-existing-sequelize-migration-3527
## model require
const db = require('./models')
const admin = db.blog_admin
why?
## migration
`npx sequelize-cli model:generate --name blog_category --attributes category:string,is_deleted:boolean`
上面指令還會預設產生額外的 column : `id`, `created_at`, `updated_at`,可以自己去 model/ 底下手動刪除這些欄位讓 DB 不要有它們。
...但是 sequelize 預設在做 `.findAll()` 的時候還是會查詢這些欄位(id, created_at, updated_at),如果自己手動把他們刪除的話,做 `.findAll()` 的時候要記得加上 `attributes: { exclude: ['updatedAt'] }` 之類的程式碼。
> [解法](https://sequelize.org/master/manual/model-basics.html),Timestamps 部分
> 更: 發現上面的解法不是針對 migration 的情況,實測之後沒效,還是會有那兩個欄位。
## sequelize association
```javascript=
blog_article.belongsTo(models.blog_category, {
foreignKey: 'category_id'
})
blog_category.hasMany(models.blog_article, { // notice
foreignKey: 'category_id'
})
```
`blog_category`的 id 欄位(遇上會有的欄位) left join `blog_article`的 'category_id' 欄位(自己新增的欄位)。
## sequelize 的 desc, order by
```javascript=
data = await articleDb.findAll({
include: [{ model: categoryDb, attributes: { exclude: ['updatedAt'] } }],
attributes: { exclude: ['updatedAt'] },
order: [['id', 'DESC']]
})
```
SQL 語法
SELECT `blog_article`.`id`, `blog_article`.`title`, `blog_article`.`content`, `blog_article`.`category_id`, `blog_
article`.`is_deleted`, `blog_article`.`createdAt`, `blog_category`.`id` AS `blog_category.id`, `blog_category`.`category` AS `blog_cate
gory.category`, `blog_category`.`is_deleted` AS `blog_category.is_deleted`, `blog_category`.`createdAt` AS `blog_category.createdAt` FR
OM `blog_articles` AS `blog_article` LEFT OUTER JOIN `blog_categories` AS `blog_category` ON `blog_article`.`category_id` = `blog_categ
ory`.`id` ORDER BY `blog_article`.`id` DESC;
## connect-flash 一直不會在 ejs 顯示出來
* 要確認有要傳進 ejs
* ejs 也要記得顯示它 `<%= flash-message %>`
## get/post parse url
const { id } = req.head -wrong
const { id } = req.params -right
## logout
```javascript=
app.use((req, res, next) => {
res.locals.isLogin = req.session.isLogin || false
res.locals.errMessage = req.flash('errMessage') || null
res.locals.username = req.session.username || null
next()
})
```
是要對 session 做 false 操作而不是 locals 做操作
## attributes: { exclude: ['updatedAt'] }, 不要忘記 attributes
#### 其他就是需要注意這周自我檢討提到的幾點