iT邦幫忙

2022 iThome 鐵人賽

DAY 14
1

Day14 自己做一個價值幾十萬的動態網站

第十四課:Api串接 CRUD的練習與熟悉 part2

前一天我們搞定了mongoDB的連接,與了解到了Api基本的架設與運行,今天將來更進一步的了解相關CRUD如何應在在各個產品與產業上。

CRUD 讀取、更新、修改、刪除資料

接下來我們要實作的是Api更新資料,會使用到的函數是push,並將我們上傳的第一筆資料做修改練習,首先要知道要修改資料的話你要知道資料的id,並透過其id來,針對此資料做唯一修改。

知道要修改的資料id後,我們將先串接get method 來讀取我們剛創建的單筆資料。

所以在insomnia先創立我們需要的新的request,並回到vscode ApiRoutes hotels.js

這邊可以先創立insomnia裡面的環境變數,也就是我們很常會需要打到
一樣我們會用到router.get method,並這邊都會搭配上async+await昨天提過的同步函數(非同步轉型成同步),與應用到資料庫使用findById函數來找hotel裡面的相關id資料,這邊的findById因為是函數,所以B要大寫I要大寫,如果沒有做將無法使用,並可以注意到.params.id這邊,運用到的是我們的api url中 :id(冒號id) params=: 將會抓取冒號後面的值,如 今天假設api url設為"/find/:city",到時候你就可以輸入相關的的值來查詢,如"/find/台北市"並在get裡面 抓取req.params.id 就是抓取到:city = 台北市 的台北市的值 這樣

//抓取第一筆資料練習
router.get("/find/:id",async(req,res)=>{
    const id = req.params.id;
    try{
       const getHotel = await Hotel.findById(id)
        res.status(200).json(getHotel)
    }catch(error){
        res.status(500).json(error)
    }
})

所以這邊的req.params.id 會抓到我完整的630f31c45a343468649687f4 hotel.id 並儲存在id 裡面讓findById依照這個去找。完成這個後,會發現我們很常需要建立新的requset,跟常需要複製整串url來更改,http://localhost:5000/api/v1/hotels像這個就很常要一直重複打,然後我們又有這麼多ApiRoute要打

insomnia 環境變數建置與建立分類建置Api folder




創建好後我們來處理上面提到的http://localhost:5000/api/v1/hotels url 設置環境變數,來降低當作剪貼簿概念就不要打那麼多次,所以這邊重複性高的地方為http://localhost:5000/api/v1我們可以將它設立為base_url 並在insomnia的操作如下



環境變數裡面就是object的紀錄方式

"base_url": "http://localhost:5000/api/v1"

小東西搞定後我們就可以繼續往下,來製作Api updated hotel,

//將第一筆資料做修改練習
router.put("/:id",async(req,res)=>{
    const id = req.params.id;
    const body = req.body
    try{
       const updatedHotel = await Hotel.findByIdAndUpdate(id,{$set:body},{new:true})
        res.status(200).json(updatedHotel)
    }catch(error){
        res.status(500).json(error)
    }
})

我們可以依循著抓 :id 的概念來繼續對這筆資料進行更新與刪除,


這邊如果想要更了解$set 或是$push的函數意思以下附官方連結在下方
https://www.mongodb.com/docs/manual/reference/operator/update/
與為什麼要加{new:true}的原因,以上傳實際資料來展示差異。


這邊在操作insomnia時記得要一開始把body換成json來輸入更新部分,忘記的可以去看前幾天,並更新內容可以用直接複製我們創建的資料加以更改測試即可。如完成後這邊就搞定了updated 與弄懂它,再來就是刪除 delete api
delete 就相對簡單只要使用到findByIdAndDelete 並回傳個刪除資料成功 訊息等等就可以了解是否操作成功。

//刪除資料
router.delete("/:id",async(req,res)=>{
    const id = req.params.id;
    try{
        await Hotel.findByIdAndDelete(id)
        res.status(200).json("刪除資料成功")
    }catch(error){
        res.status(500).json(error)
    }
})


完成後我們就可以來特別處理一下可能會發生錯誤的錯誤訊息,並將其紀錄,這樣一來,當我們未來發生錯誤時,就可以明確知道說是哪裡發生錯誤,而不會回報錯誤結果卻不知其發生錯誤的地點在哪裡。

(req, res, next) next等多種用法

express這邊附上官方的api使用方式
https://expressjs.com/zh-tw/4x/api.html
那這邊我們會用到的是加進next,並用next來處理我們error message
將可能會一直重複寫到的 res.status(500).json(error) 發生錯誤訊息,運用next funciton 就可以不用寫那麼多次。



這邊圖表上最後一張index.js的error,res,req,next的req忘記補上,後續會導致還是可以回傳但無法以json包裹回傳,所以可以幫我在這邊把req補上,並繼續將在hotel.js中將所有的res.status(500).json(error)替換掉,如下

//創建第一筆資料
router.post("/",async(req,res,next)=>{ //新增next
    const newHotel = new Hotel(req.body) 
    try {
        const saveHotel = await newHotel.save()
        res.status(200).json(saveHotel)
    } catch (error) {
        next(error) //return 回來status
    }
})

這邊所有的api都可以先換成這個,與回到index.js並在app.use("/api/v1/users",authApiRoute)等api下方

//如果上述ApiRoute傳接有問題可以來這邊回傳錯誤訊息
app.use((error,res,req,next)=>{
    const errorStatus =error.status || 500 ;
    const errorMessage =error.message || "中間ApiRoute出錯";
    return res.status(errorStatus).json({ //return回去讓他可以被next(error) catch
        status:errorStatus,
        Message:errorMessage
    })
})

這邊errorStatus與errorMessage 會將可以客製化傳入變數,如果都沒有就可以當預設500等錯誤訊息,客製化變數的意思是,如改變特定api如登入失敗訊息與他的錯誤狀態等等的。
所以下面來示範如何將會先來客製化錯誤訊息

ErrorMessage客製化處理

所以首先我們要先創立一個新的file檔來專門處理這個errorMessage到時候可以有的樣式,想法是我們創立一個新的函數,然後這個函數可以導入兩個我們最重要想要客製化的status與message

export const errorMessage=(status, message)=>{
    const error = new Error();
    error.status= status;
    error.message=message;
    return error; 
}

所以這邊要先使用new Error()來製造一個新的error message 回傳,就不吃原本的那個原本的錯誤訊息回傳資料。

並第三種客製化回傳錯誤訊息為

//創建第一筆資料
router.post("/",async(req,res,next)=>{ //新增next
    const newHotel = new Hotel(req.body) 
    try {
        const saveHotel = await newHotel.save()
        res.status(200).json(saveHotel)
    } catch (error) {
        next(errorMessage(500,"資料上傳錯誤請確認格式")) //後來我們想要客製化的
    }
})

上述兩種next() 與原本的res.status(500)都可以使用,可以看到優點是,最原始的錯誤回報可以立即知道錯誤,而第三種新的客製化優點為可以回傳白話訊息,api管理上也更明確。那如果想要都結合更詳細,包括原本的錯誤訊息,與加上我們新加的變數,我們可以再加入傳進原本的error變數如下。

router.post("/",async(req,res,next)=>{ //新增next
    const newHotel = new Hotel(req.body) 
    try {
        const saveHotel = await newHotel.save()
        res.status(200).json(saveHotel)
    } catch (error) {
        next(errorMessage(500,"資料上傳錯誤請確認格式",error)) //後來我們想要客製化的
    }
})
export const errorMessage=(status, message,err)=>{
    const error = new Error();
    const orignalErr = err.message;
    error.status= status;
    error.message=message+`\n錯誤詳細描述: `+orignalErr;
    return error; 
}

最後最後釐清整個next的概念與作用

實作完後統整next()在api裡面的概念圖與應用

因為next跟req,res一樣都是放api的導入變數中,而next可以以第三方插件的概念導入進去幫忙處理並可以將重複性高的地方拉出來做個模組,在配合向errorMessage的方式去做配搭處理

最後這邊api錯誤訊息可以慢慢的自己修改或是繼續使用next(error)
最後整理一下我們的route,與隨著越來越多的api需要串寫,我們可以再將進行分類,與新增RouteController資料夾來將路徑在分的更詳細。

apiControllers不斷分類

目的就像我們一開始做的 app.use("/api/v1/hotels",hotelsApiRoute) 使用app.use讓url做第一步的分離

所以我們會先創建RoutesController 來存放要輸出的純粹Api執行函數


所以folder之間的關係總結是目前index.js => ApiRoutes => routesController
且附上目前的github api版本
api folder連結

結論

今天要先說小抱歉,這邊做完後續才發現errorMessage中間那時候有小打錯,所以回傳資料與客製化資料那邊少打了req讓他回傳不是以json檔,並後續才發現這個問題,但以都更新在github與部分圖文,應該是不影響總體執行,也這邊比較多error的重新命名,所以會容易小搞混,為了怕搞混後續才比較多在講解這個脈絡,客製化error也不一定要拉出來特別寫,單純用res.status回傳加訊息也可以,將會簡單很多,回傳訊息的處理主要是怕UI介面的不當操作與讓整個軟體可以正常運行,所以req,res等middleware的概念,很推薦一起學習,才能更了解使用者的一些行為如何處理,或是要在UI面處理還是給Api這處理。


上一篇
「全端挑戰」 express用法 建立Api schema CRUD 的練習與熟悉 part1
下一篇
「全端挑戰」雙重try{}catch{}、認識Promise.all()與mongoDB母資料與子資料$push串接方式
系列文
自己做一個價值幾十萬的動態網站,學會Mern開發、前台UI設計各式觀念與各式Lib、typescript你該學會的前端技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言