Chrome Pointer

2023年12月15日 星期五

使用MongoDB結合Express和Fetch來實作RESTful API會員系統 (進階)

參考文章

💓Ajax-在node.js使用fetch時,常會碰到之問題"req.body返回值為空"詳解,並附上簡單實作練習

💓Ajax-運用express和fetch的結合,在node.js上實作前端和後端api路由(Get和Post)的教學

💓MongoDB的bin目錄下沒有mongoimport和mongoexport解決方法, 及使用方法教學 

💓初級>使用MongoDB結合Express和Fetch來實作RESTful API簡單網站(包括用戶Post, Get, Update, Delete等功能)

💓進階>使用MongoDB結合Express和Fetch來實作RESTful API會員系統 (進階)


- Root Directory
  ├── models
  │   └── user_shchema.js
  ├── app.js
  ├── public
  │   └── index.html
  │   └── vue.js
  │   └── styles.css


styles.css

/* 基本樣式 */
body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 18px;
  font-size: 1em;
  background-color: #c4bdbd;
}

h1 {
  text-align: center;
  color: #333;
}

/* 表單樣式 */
form {
  font-size: 1em;
  max-width: auto;
  margin: 10px auto;
  padding: 10px;
  background-color: #f8e1e1;
  border-radius: 8px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

label {
  font-size: 1em;
  display: block;
  margin-bottom: 8px;
  font-weight: bold;
}

input[type="text"],
input[type="email"],
input[type="number"]{
  width: 100%;
  padding: 10px;
  margin-bottom: 15px;
  border: 1px solid #ccc;
  border-radius: 5px;
  box-sizing: border-box;
}

input[type="submit"],
button {
  font-size: 1em;
  cursor: pointer;
  background-color: #c6e1ff;
  color: #000000;
  width: 100%;
  padding: 10px;
  margin-bottom: 15px;
  border: 2px solid #ecb5b5;
  box-sizing: border-box;
}

input[type="submit"]:hover,
button:hover {
  background-color: #6598ce;
}

input[type="submit"]:focus,
button:focus {
  outline: none;
  box-shadow: 0 0 5px #007bff;
}

/* 其他表單樣式 */
#error_txt {
  text-align: center;
  color: red;
}

form h3 {
  margin-bottom: 15px;
  color: #333;
}

h3 {
  margin-bottom: 15px;
  color: #7a1c1c;
  text-align: center;
}

span {
  text-align: left;
  word-wrap: break-word; /* 自動換行 */
  white-space: pre-line;
}

span#find_OneOrAll_toggle {
  font-size: 1.1em; /* 调整字体大小 */
  font-weight: bold; /* 加粗字体 */
  color: #333; /* 文本颜色 */
}

/* 悬停效果 */
span#find_OneOrAll_toggle:hover,
h3:hover {
  background-color: #ddd; /* 悬停时的背景颜色 */
}

div[type="value1"] {
  position: fixed;
  z-index: 1;
  top: 1%; /* 將元素的頂部定位在視窗的中間位置 */
  left: 1%; /* 將元素的左側定位在視窗的中間位置 */
  border: 1px dashed rgb(78, 117, 245);
  max-width: 100%; /* 設定最大寬度為視窗寬度的80% */
  background-color: rgb(247, 199, 137);
  padding: 1px;
  border-radius: 5px; /* 圆角边框 */
}
/* 根據您的需求調整其他部分的樣式 */

/* 更多 CSS 樣式可以根據您的需求添加在這裡 */

@media screen and (max-width: 767px) {
  /* 在螢幕寬度小於 767px 時使用以下 CSS 規則 */
  body {
      font-size: 0.8em;
      background-color: #c1f6fa;
  }
}

@media screen and (max-width: 447px) {
  /* 在螢幕寬度小於 447px 時使用以下 CSS 規則 */
    body {
      font-size: 0.5em;
      background-color: #fdf07b;
  }
}

/*CSS 的規則是基於「後來者居上」的原則,
這意味著如果有多個相同規則(例如相同的選擇器),最後一個定義的規則將覆蓋之前的定義。*/


💝

em 是一種相對長度單位,它用於設置 HTML 元素的尺寸。

em 的值根據該元素的字體大小而定。它是相對於父元素字體大小的倍數。


當您設置某個元素的 font-size 為 1em 時,它將等於該元素的父元素的字體大小,

當我們的網頁中的文字沒有做任何的調整時,預設值就是1em 也就是16 px。


如果您將父元素的 font-size 設置為 20px,那麼子元素的 1em 將等於 20px。

假設您將子元素的 font-size 設置為 0.5em,那麼它的文字大小將是父元素文字大小的一半,即 10px。


em 的優點是它可以創建出相對於父元素的彈性設置,可以隨著父元素的改變而調整尺寸。

這對於構建具有可調整大小的 RWD(響應式網頁設計)非常有用,

因為元素的尺寸會根據不同螢幕尺寸和設備而自動調整。


💝

若希望在 h3 元素中的文字遇到容器邊界時自動換行

您可以使用 CSS 中的 word-wrap 或 word-break 屬性來實現這一點。


h3 {
  word-wrap: break-word; /* 自動換行 */
}

h3 {
  word-break: break-all; /* 在任意字符內換行 */
}


index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>User Information</title>
  <link rel="stylesheet" href="styles.css">
  <script src="https://unpkg.com/vue@3.3.0/dist/vue.global.js"></script>
</head>
<body>
  <div class="member_system">
    <h1>Member System Form</h1>
    <h3 id="error_txt">{{ errorText }}</h3>

    <form id="Posting_userForm" @submit.prevent="addUser">
      <h3 @click="checked_toggle(0)">Adding User</h3>
      <div v-show="checked[0]">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" placeholder="Enter username" v-model="formData.username"><br>
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" placeholder="Enter email" v-model="formData.email"><br>
        <label for="age">Age:</label>
        <input type="number" id="age" name="age" placeholder="Enter age" v-model.number="formData.age"><br>
        <input type="submit" value="Submit">
      </div>
    </form>

    <form id="Updating_form" @submit.prevent="updateUser">
      <h3 @click="checked_toggle(1)">Updating User</h3>
      <div v-show="checked[1]">
        <input type="text" id="userid2" name="userid2" placeholder="Enter userid2" v-model="formData2v.userid2v"><br>
        <input type="text" id="username2" name="username2" placeholder="Enter username2" v-model="formData2v.username2v"><br>
        <input type="email" id="email2" name="email2" placeholder="Enter email2" v-model="formData2v.email2v"><br>
        <input type="number" id="age2" name="age2" placeholder="Enter age2" v-model.number="formData2v.age2v"><br>
        <input type="submit" id="submit2" value="Submit2更新"><br>
      </div>
    </form>

    <form id="Deleting" @submit.prevent="deleteUser">
      <h3 @click="checked_toggle(2)">Deleting User</h3>
      <div v-show="checked[2]">
        <input type="text" id="delete_id" name="delete_id" placeholder="Enter delete_id" v-model="deleteId"><br>
        <button id="delete" value="delete">delete</button> <br>
      </div>
    </form>

    <form id="Finding_one_and_Finding_all" >
      <h3 @click="checked_toggle(3)">Finding_one_and_Finding_all User</h3>
      <div v-show="checked[3]">
        <input type="text" id="find_one_id" name="find_one_id" placeholder="Enter find_one_id" v-model="findOneId"><br>
        <!-- <input type="submit" id="find_one_btn" value="find_one_btn" @submit.prevent="findOneUser"> -->
        <!-- <input type="submit" id="find_all_btn" value="find_all_btn" @submit.prevent="findAllUsers">  -->
        <button @click.prevent="findOneUser" type="button" id="find_one_btn">find_one_btn</button> <!-- find_one -->
        <button @click.prevent="findAllUsers" type="button" id="find_all_btn">find_all_btn</button><!-- find_all -->
      </div>
    </form>

    <div type="value1">
      <span id="find_OneOrAll_toggle" @click="value1_toggle()">Finding close</span><br/>
      <span id="find_OneOrAll_txt" v-show="value1_checked" v-html="userText"></span>
    </div>
  </div>
  <!-- 載入vue.js -->
  <script src="vue.js" async></script>
</body>
</html>


💝

加入<script src="vue.js"></script>後,導致原本的<script src="script.js"></script>無法運行

原因有幾個:


加載順序問題: 當瀏覽器加載多個腳本時,腳本的加載順序對於依賴關係很重要。

如果script.js在vue.js之前加載,而您的script.js文件中依賴於 Vue.js 的一些功能或語法,

可能會因為 Vue.js 尚未加載完畢就執行script.js,導致錯誤。


如何解決這個問題?:

1. 確保兩個腳本之間沒有命名衝突或相互依賴的問題。

2. 確保vue.js在script.js之前加載。


💝

寫在.html中的@submit.prevent, v-model, v-model.number 指令講解。

@submit.prevent
v-model
v-model.number


v-model會和vue.js中的data()進行雙向綁定,

類似於username2v: document.getElementById('username2').value

data() {
    return {
      formData: {
          username: '',
          email: '',
          age: '',
      },
      formData2v: {
          userid2v: '',
          username2v: '',
          email2v: '',
          age2v: ''
      },
      deleteId: '',
      findOneId: ''
    };

👉v-model常用於表單及元素來做雙向數據綁定,

EX: 當.html中,有一格<input,且有v-model="formData2v.userid2v 屬性,

則它就會和下面這個vue.js中的data()雙向綁定在一起。

formData2v: {
          userid2v: '',


v-model 是通用的雙向綁定指令,

而 v-model.number 則是專門用於處理數字輸入的特殊情況,自動將值轉換為數字類型。


👉@submit.prevent 是 Vue.js 中的事件修飾符,用於防止表單預設的提交行為,同時觸發特定的事件處理器。

使用 @submit.prevent 可以在提交表單時防止瀏覽器執行預設的提交動作,同時調用 Vue 實例中的指定方法來處理提交事件。

以下是 @submit.prevent 的詳細說明: 1. submit 是一個事件綁定的指令,用於監聽表單提交事件。 2. prevent 是一個事件修飾符,用於阻止事件的默認行為

防止了表單提交時的默認行為,例如頁面刷新或頁面跳轉


EX: 寫在.html中的 @submit.prevent="updateUser">

這個示例中的 @submit.prevent 會在表單提交時觸發vue.js中的 updateUser函式,並防止表單的默認提交行為。

這樣可以方便你使用 Vue 實例的方法來處理表單提交所需的邏輯,而不會導致頁面的刷新或跳轉。


vue.js

const app = Vue.createApp({
    data() {
      return {
        checked: [false, false, false, false], // 包含4个boolin的数组,初始值均为 true
        value1_checked: true,
        errorText: '',
        userText: '',

        formData: {
            username: '',
            email: '',
            age: '',
        },

        formData2v: {
            userid2v: '',
            username2v: '',
            email2v: '',
            age2v: ''
        },

        deleteId: '',
        findOneId: ''
      };
    },
    methods: {
        checked_toggle (index) {
            this.checked[index] = !this.checked[index]; // 切换相应表单的显示状态
        },
        value1_toggle () {
            this.value1_checked = !this.value1_checked; // 切换相应表单的显示状态
            if (this.value1_checked===true){
                document.querySelector("#find_OneOrAll_toggle").textContent = "Finding close";
            }else{
                document.querySelector("#find_OneOrAll_toggle").textContent = "Finding open";
            }
        },
        async addUser() {
            const requestOptions = {
                method: 'POST',
                headers: {
                  'Content-Type': 'application/json',
                },
                body: JSON.stringify(this.formData)
              };
           
              fetch('/users', requestOptions)
                .then(response => {
                  if (!response.ok) {
                    throw new Error('Network response was not ok');
                  }
                  return response.json();
                })
                .then(data => {
                  console.log('New user created:', data);
                  this.errorText = JSON.stringify(data);
                  alert('New user created:' + this.errorText);
                })
                .catch(error => {
                  console.error('Error花:', error);
                  this.errorText = 'Post Error花' + JSON.stringify(error);
                  alert(this.errorText);
                });
          },
        async updateUser() {
            let requestOptions2 = {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(this.formData2v)
            };
           
            fetch(`/users/${this.formData2v.userid2v}`, requestOptions2)
                .then(response => {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then(data => {
                    console.log('New updateUser created:', data);
                    this.errorText = JSON.stringify(data);
                    alert(this.errorText);
                })
                .catch(error => {
                    console.error('Error花:', error);
                    this.errorText = 'Put你ID連輸入都沒輸入' + JSON.stringify(error);
                    alert(this.errorText);
                });
        },
        async deleteUser() {
            const deleteUserOptions = {
                method: 'DELETE',
                headers: {
                    'Content-Type': 'application/json',
                }
            };
       
            fetch(`/users/${this.deleteId}`, deleteUserOptions)
                .then(response => {
                    if (!response.ok) {
                        throw new Error('Network response was not ok');
                    }
                    return response.json();
                })
                .then(data => {
                    console.log('New delete created:', data);
                    this.errorText = JSON.stringify(data);
                    alert(this.errorText);
                })
                .catch(error => {
                    console.error('Error花:', error);
                    this.errorText = 'delete你ID連輸入都沒輸入' + JSON.stringify(error);
                    alert(this.errorText);
                });
        },
        async findOneUser() {
            fetch(`/users/${this.findOneId}`)
            .then(response => {
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                return response.json();
            })
            .then(data => {
                console.log('New find_one created:', data);
                let userText = '';
                data.forEach((res_user) => {
                    userText += `_id: ${res_user._id}; username: ${res_user.username}; email: ${res_user.email}; age: ${res_user.age} </br>`;
                });
                this.userText = userText;
            })
            .catch(error => {
                console.error('Error花:', error);
            });
        },
        async findAllUsers() {
            fetch('/users')
            .then(response => {
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                return response.json();
            })
            .then(data => {
                console.log('New Find created:', data);
                let userText = '';
                data.forEach((res_user) => {
                    userText += `_id: ${res_user._id}; username: ${res_user.username}; email: ${res_user.email}; age: ${res_user.age} </br>`;
                });
                this.userText = userText;
            })
            .catch(error => {
                console.error('Error花:', error);
            });
    }}
})
app.mount('.member_system');

/*
checked: [true, true]。在這個陣列中,checked[0] 和 checked[1] 分別代表陣列中的第一個元素和第二個元素。
這裡的 checked[0] 對應陣列中的第一個元素,即 true,而 checked[1] 對應陣列中的第二個元素,同樣是 true。
*/


  • 💝

    checked:[true, true] 代表初始化了一個包含兩個布林值的陣列,即 [true, true],也為checked:[0, 0]。

  • 在這個陣列中,checked[0] 和 checked[1] 分別代表陣列中的第一個元素和第二個元素。

  • 這裡的 checked[0] 對應陣列中的第一個元素,即 true,

  • 而 checked[1] 對應陣列中的第二個元素,同樣是 true。


💝

  • 如果使用fetch 方法從伺服器端取得數據,且該資料是包含多個使用者物件的JSON 數組,

  • 則需要先將回應用response.json() 解析為JavaScript 物件或數組,

  • 然後遍歷數組以取得每個使用者物件的屬性,例如age 屬性。


  • response.json(), JSON.parse(), JSON.stringify()介紹,延伸閱讀>>

  • 在此例中,response.json() 傳回的是一個包含多個使用者物件的 JSON 陣列。

  • 若要存取其中每個使用者物件的 age 屬性,可以使用.forEach() 方法。

  • .forEach() 方法用於遍歷數組中的每個使用者對象,並輸出每個使用者的 age 屬性值。


let userText = ''; // 用于保存所有用户信息的字符串
await fetch(`/users/${document.getElementById('find_one_id').value}`)
   .then((response) => {
      return response.json();
    })
   .then((response) => {
      console.log('New find_one created:',response);
       // response 是包含多个用户对象的数组,您需要遍历数组来获取每个用户的 属性
       response.forEach((res_user) => {
           userText += `_id: ${res_user._id}; username: ${res_user.username};
           email: ${res_user.email}; age: ${res_user.age} <br>`;
           // 将每个用户对象的属性添加到字符串中,使用换行符或其他分隔符分隔每个用户信息
           document.querySelector("#find_OneOrAll_txt").innerHTML = userText;
           // 将包含所有用户信息的字符串设置为 textContent
       });
})

此圖為回傳2個JSON 數組


app.js

const express = require('express');
const app = express();
const mongoose = require('mongoose');
const User = require('./models/user_shchema'); // 假設已經建立了User模型

// 連接至 MongoDB
mongoose.connect('mongodb://localhost:27017/my_database')
  .then(() => {
    console.log('成功連接至 MongoDB');
  })
  .catch((err) => {
    console.error('連接失敗:', err);
  });

// 設置靜態資源目錄(假設HTML檔案存放在 public 目錄下)
const path = require("path")
const static_path = path.join(__dirname, "public") //變成絕對路徑 D:\qqq\public,nodejs使用絕對路徑較安全可靠,在 Node 中使用相對路徑進行檔案讀取是很危險的, 建議一律都透過絕對路徑的方式來處理
app.use(express.static(static_path))
//app.use(express.static('.'));
app.use(express.urlencoded({ extended: true })); // 處理 URL 編碼的資料
app.use(express.json()); //這個必須存在

//Post新增用戶, POST請求的路由處理
app.post('/users', async (req, res) => {
    var newUser = {
        username: req.body.username,
        email: req.body.email,
        age: req.body.age,}
     
    console.log("newUser",newUser);
    await User.create(newUser)
    .then(createdUser => {
        console.log(createdUser);
        res.status(201).json(createdUser); // 返回從資料庫返回的新建用戶資料
    })
    .catch (error => {
        res.status(500).json({ message: "qqq POST 錯誤" });
    })    
});

//Put更新用戶
app.put('/users/:id', async function (req, res) {
    const updatedFields = {}; // 創建一個空物件來存儲要更新的欄位
   
    // 檢查並將用戶輸入的欄位添加到要更新的物件中
    if (req.body.username2v) {
        updatedFields.username = req.body.username2v;
    }
    if (req.body.age2v) {
        updatedFields.age = req.body.age2v;
    }
    if (req.body.email2v) {
        updatedFields.email = req.body.email2v;
    }
   
    await User.findOneAndUpdate(
        {
            "_id": req.params.id, //req.params.id 的值,因為它是在路由中 :id 的位置提供的值。
        }, // 條件,選擇要更新的文檔
        { $set:
            updatedFields // 使用包含有輸入值的欄位的物件進行更新
        },
        { new: true } // 選項,返回更新後的文檔
    )
    .then(updatedUser => {
        // ...處理更新後的用戶
        console.log('更新後的用戶:', updatedUser);
        console.log('updatedFields', updatedFields);
        res.status(201).json(updatedUser);
    })
    .catch(error => {
        console.error('更新用戶時出錯:', error);
        res.status(500).json({ message: "qqq put更新 ID出錯ㄌ" ,id: req.params.id});
    });
 })

//delete刪除用戶
app.delete('/users/:id', async (req, res) => {
    await User.deleteOne({ _id: req.params.id })
    .then(delete_user => {
        console.log("delete: "+JSON.stringify(delete_user),"delete_ID: "+req.params.id);
        res.status(201).json(delete_user); // 返回從資料庫返回的新建用戶資料
    })
    .catch (error => {
        res.status(500).json({ message: "Delete qqq 沒有這個ID啦" });
    })    
});

//Get個別別用戶
app.get('/users/:id', async function (req, res) {
    await User.find({"_id": req.params.id})
    .then(FindUser => {
        console.log('用戶:', FindUser);
        res.status(201).json(FindUser);
    })
    .catch (error => {
        console.error('更新用戶時出錯:', error);
        res.status(500).json({ message: "qqq Get個別 沒有這個用戶" });
    })
})

 //Get全部用戶
 app.get('/users', async function (req, res) {
    await User.find({})
    .then(FindUser => {
        console.log('用戶列表:', FindUser);
        res.status(201).json(FindUser);
    })
    .catch (error => {
        console.error('更新用戶時出錯:', error);
        res.status(500).json({ message: "qqq Get全部 出錯了" });
    })
})

// 監聽端口
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`Express 伺服器已啟動,監聽在端口 ${PORT}`);
});


user_shchema.js

const mongoose = require('mongoose');

// 定義模型 Schema
const Schema = mongoose.Schema;
const userSchema = new Schema({
  username: String,
  email: String,
  age: Number
}, { collection: 'my_custom_users' }); // 在這裡指定集合名稱為 my_custom_users collection;

// 創建 User 模型
const User = mongoose.model('User', userSchema);
/*創建 User 模型:透過 mongoose.model('User', userSchema) 來創建名為 User 的 Mongoose 模型。
使用 mongoose.model 方法將定義的模式 userSchema 轉換為模型 User */

module.exports = User;
/*匯出模型:module.exports = User;
將創建的 User 模型透過 module.exports 導出,使其可以在其他文件中引入和使用。 */

首頁


新增會員


更新會員資料


尋找各別單一會員


尋找所有會員

刪除會員




沒有留言:

張貼留言

喜歡我的文章嗎? 喜歡的話可以留言回應我喔! ^^