# 實作紀錄:將 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 #### 其他就是需要注意這周自我檢討提到的幾點