# 第十一堂:Express middleware 異常狀態處理 - 錄影 - API 應用可參考[線稿圖](https://miro.com/app/board/uXjVLbiDml0=/) - API 開發規格請依照 [此 API 文件](https://liberating-turtle-5a2.notion.site/38e4dd6775894e94a8f575f20ca5b867?v=17c6a246851881208205000cacf67220) - db 欄位設計請依照 [此資料庫欄位圖片](https://firebasestorage.googleapis.com/v0/b/hexschool-courses.appspot.com/o/hex-website%2Fnode%2F1739179853094-fitness_5.png?alt=media&token=a65de209-3ae6-4263-bdc0-d4f08638ff20) 定義 ## express middleware * [middleware 中介介紹](https://expressjs.com/zh-tw/guide/using-middleware.html) ### app.use ``` =JavaScript const express = require('express'); const app = express(); app.get('/',function(req,res){ res.status(200).json({ "status":"success", "message":"你目前造訪到首頁" }) }) app.use(function(req,res,next){ console.log('有人進來了'); kk(); next(); }) app.use(function(req,res,next){ res.status(404).json({ status:"false", message:"您的路由不存在" }) }) app.use(function(err,req,res,next){ console.log(err.name); res.status(500).json({ "err": err.name }) }) // 監聽 port var port = process.env.PORT || 3000; app.listen(port); ``` ### error 處理 * [官網資訊](https://expressjs.com/zh-tw/guide/error-handling.html) ```=JavaScript const express = require('express'); const app = express(); app.get('/',function(req,res){ res.status(200).json({ "status":"success", "message":"你目前造訪到首頁" }) }) app.use(function(req,res,next){ console.log('有人進來了'); next(); }) app.get('/user',function(req,res){ res.status(200).json({ "status":"success", "message":"你目前造訪到 user 頁面" }) }) app.use(function(req,res,next){ res.status(404).json({ status:"false", message:"您的路由不存在" }) }) app.use(function(err,req,res,next){ console.log(err.name); res.status(500).json({ "err": err.name }) }) // 監聽 port const port = process.env.PORT || 3003; app.listen(port); ``` ## 中介設計 ```=javascript const express = require('express'); const app = express(); const checkKeyword = function(req,res,next){ if(req.query.q){ next() }else{ res.status(400).json({ "message":`您並未輸入關鍵字` }) } } app.get('/search',checkKeyword,function(req,res){ res.status(200).json({ "status":"success", "keyword":`你搜尋到的是${req.query.q}` }) }) // 監聽 port const port = process.env.PORT || 3003; app.listen(port); ``` ## 中場休息 * 提醒分組表單填寫 ## 下半場 ## error 處理 * [JS error 介紹](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Error) * try catch 示範 ``` JavaScript const a = new Error("姓名欄位沒有填寫") a.name = "驗證失敗" ``` ## express 404 ```jsx= // 404 錯誤處理 app.use(function(req, res, next) { res.status(404).json({ status: 'error', message: '無此路由資訊', }); }); // Express 錯誤處理 //當有錯誤被 throw 或在 next(err) 時,會自動進入到這個函式,並在此將 err.message 回傳給前端。 app.use(function(err, req, res, next) { res.status(500).json({ err: err.message, }); }); ``` ## 資料表與 router 更新 > 1. 調整 course.js 實體後,執行 `npm run clean`, > 2. 註冊 > 變更為教練身份 > 新增技能> 新增課程 > 檢視所有課程 1.建立是否為教練身份的 middleware 2.取得課程 API 3. 錯誤種類 * App 層(app.js) * 伺服器層(bin/www.js) ## entities > Course.js ``` const { EntitySchema } = require('typeorm') module.exports = new EntitySchema({ name: 'Course', tableName: 'COURSE', columns: { id: { primary: true, type: 'uuid', generated: 'uuid' }, user_id: { type: 'uuid', nullable: false }, skill_id: { type: 'uuid', nullable: false }, name: { type: 'varchar', length: 100, nullable: false }, description: { type: 'text', nullable: false }, start_at: { type: 'timestamp', nullable: false }, end_at: { type: 'timestamp', nullable: false }, max_participants: { type: 'integer', nullable: false }, meeting_url: { type: 'varchar', length: 2048, nullable: false }, created_at: { type: 'timestamp', createDate: true, nullable: false }, updated_at: { type: 'timestamp', updateDate: true, nullable: false } }, relations: { User: { target: 'User', type: 'many-to-one', joinColumn: { name: 'user_id', referencedColumnName: 'id', foreignKeyConstraintName: 'courses_user_id_fk' } }, Skill: { target: 'Skill', type: 'many-to-one', joinColumn: { name: 'skill_id', referencedColumnName: 'id', foreignKeyConstraintName: 'courses_skill_id_fk' } } } }) ``` ## 示意:middlewares > isCoach.js ```jsx= const FORBIDDEN_MESSAGE = '使用者尚未成為教練' const PERMISSION_DENIED_STATUS_CODE = 401 function generateError (status = PERMISSION_DENIED_STATUS_CODE, message = FORBIDDEN_MESSAGE) { const error = new Error(message) error.status = status return error } module.exports = (req, res, next) => { // 週四會分享的驗證教練邏輯 // if (!req.user || req.user.role !== 'COACH') { // next(generateError()) // return // } next() } ``` ### 示意:middlewares > auth.js ```jsx= const isLogin = function(req, res, next) { console.log("確認是否為登入"); // 判斷使用者是否登入的邏輯 // 檢查 session 或 token next(); // 若登入檢查通過,呼叫 next() 進入下一步 }; module.exports = isLogin; ``` ## routes > courses.js ```jsx= const express = require('express') const { IsNull } = require('typeorm') const router = express.Router() const config = require('../config/index') const { dataSource } = require('../db/data-source') const logger = require('../utils/logger')('Course') router.get('/', async (req, res, next) => { try { const courses = await dataSource.getRepository('Course').find({ select: { id: true, name: true, description: true, start_at: true, end_at: true, max_participants: true, User: { name: true }, Skill: { name: true } }, relations: { User: true, Skill: true } }) res.status(200).json({ status: 'success', data: courses.map((course) => { return { id: course.id, name: course.name, description: course.description, start_at: course.start_at, end_at: course.end_at, max_participants: course.max_participants, coach_name: course.User.name, skill_name: course.Skill.name } }) }) } catch (error) { logger.error(error) next(error) } }) module.exports = router ``` ### 密碼加密 * NPM:[bcrypt](https://www.npmjs.com/package/bcrypt):密碼加解密 > npm install bcrypt ```jsx= const bcrypt = require('bcrypt') async function hashPassword() { try { const salt = await bcrypt.genSalt(10) console.log('Salt:', salt) const databasePassword = await bcrypt.hash('1q2w3e4r', salt) console.log('Hashed password:', databasePassword) // 登入填寫的密碼比對 const keyPassword = '1q2w3e4r' const isMatch = await bcrypt.compare(keyPassword, databasePassword); console.log(isMatch); } catch (error) { console.error('Error:', error) } } hashPassword() ```