# 小組學習筆記_淺談Express_基本MVC架構和ORM工具介紹 撰寫人: 組長 許哲誠/terry xu # 介紹MVC架構: * Model : * 定義資料庫料結構和利用CRUD的方式和資料庫進行溝通 ( 有使用ORM工具的情況。 * 撰寫SQL語法並與資料庫進行溝通 ( 使用SQL模組。 * Controller * Controller 在過程中扮演 Model 和 View 中間的協調者,當不同路由(route)接收到 request 時,會呼叫 Controller 執行相對應的 Method。 * View * 負責處理畫面的部分(template),也就是我們看到的網頁內容( 就是上課使用到ejs的部分 * 由於我們是用React,所以就沒有view的部分。 # 介紹ORM工具 * 大專打算用prisma,但市面上也很多種ORM工具,個人只用過Sequelize,但Prisma看起來強一點。 * ORM 在網站開發的架構中,通常是在 **資料庫** 和 **Model** 兩者之間,簡單來說,他就是讓使用者用更簡便、安全去操作資料庫。 ## ORM工具優缺點 * 優點 * 安全性 : * ORM工具通常會自動處理SQL注入問題,因為使用參數化查詢。 * ORM工具會過濾掉可能有害的SQL語句,如未經授權的DELETE操作。 * 提高了可讀性和可維護性 : ORM允許開發者使用面向對象的方式與資料庫交流,無需直接編寫SQL語句。 * 生產力提升 : 現在很多ORM工具都有提供許多現成的功能,如緩存、延遲加載等。 * 版本控制 : 比較好管理資料庫結構的變更。 * 缺點 * 效能問題 : 畢竟他在後面有做轉換成SQL語句等等,所以多多少少會比直接用SQL語句來的慢很多。 * 複雜查詢 : 對於非常複雜的查詢,使用ORM可能會變得繁瑣,有時直接編寫SQL會更高效和清晰,例如有用到跨好幾個表查詢時。 * 容易忘本 : 畢竟ORM都幫你做完了,反而SQL語句忘了就不太OK了。 * 現在國外感覺都比較推崇直接寫SQL,畢竟速度比較快,但我覺得可以視情況而定,像是購物網站這種會大量CRUD需求的的,也許就不太適合ORM。 ## ORM工具 VS SQL語法 時空背景 : 假設我們有一個 'Product' 模型,包含 id, name, price 字段。 ### ORM建構 VS SQL 建構 ```javascript datasource db { provider = "mysql" // MariaDB 使用 MySQL url = env("DATABASE_URL") //記得所有重要的apikey 是帳號都要寫在.env檔喔 //甚麼是env檔,是用來存儲環境變量,很適合拿來放api key之類的個人隱私資料, } // 定義 Prisma 客戶端生成器 generator client { provider = "prisma-client-js" } // 定義 Product 模型 model Product { id Int @id @default(autoincrement()) name String price Decimal @db.Decimal(10, 2) } // 用於原生 SQL MariaDB 客戶端 const mariadb = require('mariadb') const pool = mariadb.createPool({ host: process.env.DB_HOST,//記得所有重要的api key 或是 帳號都要寫在.env檔喔 user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, connectionLimit: 5 }) ``` ### Prisma create VS SQL create ```javascript // 使用 Prisma create產品 async function createProductPrisma(name, price) { const product = await prisma.product.create({//記得要寫async await用法喔 data: { name, price }, }) } //為什麼要async await 這個坑之後再來說~~ //用SQL create 產品 async function createProductSQL(name, price) { let conn; try { conn = await pool.getConnection() const result = await conn.query( 'INSERT INTO Product (name, price) VALUES (?, ?)', [name, price] ) } finally { if (conn) conn.release() } //Database Connection Pool 這是個用來維護的資料庫連接的快取, //這個可學可不學,個人覺得還好QWQ //想學可以來找Terry。 ``` ### Prisma read VS SQL read ```javascript // 使用 Prisma 讀取產品 async function getProductPrisma(id) { const product = await prisma.product.findUnique({//有很多種用法 where: { id: id }, } //findUnique查找特定條件的單個記錄 //findMany查找多個記錄 //有可以配合很多酷酷的用法,ex. where select include 等等等 // 使用原生 SQL 讀取產品 async function getProductSQL(id) { let conn; try { conn = await pool.getConnection() const rows = await conn.query('SELECT * FROM Product WHERE id = ?', [id]) } finally { if (conn) conn.release() } } ``` 其他作者懶得打了,自已去看酷酷的官方文件🦥。 https://www.prisma.io/docs # 大專前後端分離 ![express_MVC](https://hackmd.io/_uploads/Hk9IgiPOR.svg) 流程如下 : 1. View ( React ) 會利用Client ( axios )** 發送一個request給到Route。 2. Route透過使用者輸入特定路徑時要指定呼叫 controller 檔案中哪個指定的函式。 3. Controller呼叫Model去資料庫做CRUD。 4. Controller拿到資料後再response回去Client端。 5. Client ( axios ) 端再把拿到的資料 ( JSON ) 做完處理後傳給View (React)。 * axios會再生一份簡易教材出來~~ ## 後端方面的範例程式 ( 用MVC架構寫的,所以裏頭的view請自動忽略它 ) ### App.JS (主要用來掛載路由跟啟動酷酷的express ```javascript // 掛上todo 路由 const express = require('express'); const bodyParser = require('body-parser'); const todoRoutes = require('./routes/todoRoutes'); // routes路由import const app = express(); const port = 8080; app.use('/todo', todoRoutes); app.use('/member', mRoutes); // 根路由重定向到 /todo app.get('/', (req, res) => res.redirect('/todo')); app.listen(port, () => { console.log(`Server is running on port ${port}`); }); ``` routes.js ❗❗❗❗❗請記得寫成restful API ( 不熟的人請洽組長 ```javascript const express = require('express'); const router = express.Router(); const {todoController} = require('../controllers/todoController'); // get所有 todos router.get('/', todoController.showTodos); // post一個新的 todo router.post('/', todoController.createTodo); // get一個特定的 todo 以便編輯 router.get('/:id/edit', todoController.editTodo); // update一個特定的 todo router.put('/:id', todoController.updateTodo); // delete一個特定的 todo router.delete('/:id', todoController.deleteTodo); module.exports = router;//要記得export出去喔 ``` Controller.js ```javascript const express = require('express'); const {todoModel}= require('../models/todo');//把model require近來 const e = require('express'); const router = express.Router(); const todoController = { showTodos:async (req, res) => {//記得要寫async await !!!!! var todoItem = await todoModel.read();//這裡去呼叫model裡的read用法 todoItem=todoItem.map(item=>item.dataValues);; res.render('index', {todoItem: todoItem}); //這裡由於本來是寫成MVC架構,這裡傳給EJS,正常這裡是要response回去Client端 }, //....後面方法我懶得放了(好多東西 }; exports.todoController = todoController;//要記得export出去喔 ``` Model.js ```javascript const { PrismaClient } = require('@prisma/client');//Prisma模型定義~~~ const prisma = new PrismaClient() const todoModel = {//定義CRUD的用法~~~ //請記得用CRUD的用法,不會的話請洽Terry create: async (title, isComplete) => { return await prisma.todoitem.create({ }); }, read: async () => { return await prisma.todoitem.findMany(); }, update: async (todoTableId, title, isComplete) => { return await prisma.todoitem.update({ }); }, delete: async (todoTableId) => { return await prisma.todoitem.delete({, }); }, }; exports.todoModel = todoModel;//要記得export出去喔 ``` # 結語 以上資訊為TerryXu的經驗,有寫錯誤的話請不吝嗇找我討論。 還沒補齊的知識點 : * async await ( 🌟🌟🌟 * Prisma ( 🌟🌟 * axios (🌟🌟 * CRUD ( 🌟🌟