筆記非教程。
建立 Express 專案
先安裝 express 和 dotenv
npm install express dotenv
專案結構
- src
- connections
- index.js
- models
- routes
- index.js
- connections
- .env
- package.json
index.js
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
module.exports = app
package.json 中的 scripts 加上 start, server
"scripts": {
"start": "node ./src/index.js",
"server": "nodemon ./src/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
連接 MongoDB Atlas
創建 Cluster
註冊MongoDB Atlas帳號,新建 Project > Cluster (可以選M0 / Google Cloud / Taiwan)
建好後點 Connect
Driver 選 Mongoose,然後複製第三步的 connection string
Express 連接資料庫
安裝 mongoose
npm install mongoose
新增 .env
MONGODB_URI="mongodb+srv://root:<db_password>@<cluster_name>.homtu.mongodb.net/<db_name>?retryWrites=true&w=majority&appName=<cluster_name>"
PORT="3000"
connections/index.js
const mongoose = require('mongoose')
const uri = process.env.MONGODB_URI
const clientOptions = {
serverApi: { version: '1', strict: true, deprecationErrors: true },
}
const run = async () => {
try {
await mongoose.connect(uri, clientOptions)
console.log("成功連接到 MongoDB!")
} catch (error) {
console.error("連接 MongoDB 時出錯:", error)
process.exit(1)
}
}
const disconnect = async () => {
try {
await mongoose.disconnect()
console.log("MongoDB 連線已關閉")
} catch (error) {
console.error("關閉 MongoDB 連線時出錯:", error)
}
}
module.exports = { run, disconnect }
index.js
require('dotenv').config()
const express = require('express')
const app = express()
const cors = require('cors')
const db = require('./connections')
db.run().catch(console.dir)
app.use(cors())
app.use(express.json())
app.get('/', (req, res) => {
res.send('Hello World!')
})
const startServer = async () => {
try {
await db.run()
console.log('資料庫連接成功')
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`伺服器正在監聽 http://localhost:${port}`)
})
} catch (error) {
console.error('啟動伺服器時出錯:', error)
process.exit(1)
}
}
startServer()
process.on('SIGINT', async () => {
await db.disconnect()
console.log('伺服器關閉,MongoDB 連線已斷開')
process.exit(0)
})
module.exports = app
資料的CRUD
在 src 底下新增 routes, models 資料夾
routes
定義 API endpoints,用於處理 client 端的請求並提供響應models
定義資料結構和資料庫的交互邏輯
models
以 User 為例
// models/User.js
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
}, { timestamps: true })
const User = mongoose.model('User', userSchema)
module.exports = User
註:Mongoose 會根據 model 名稱自動推測 collection 名稱,並將其轉換為小寫複數形式,因此 collection 名稱為
users
routes
還可以把處理邏輯再拆分到 services 中,這邊就不展開了
// routes/user.js
const express = require('express')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
const User = require('../models/User')
const router = express.Router()
// 註冊新用戶
router.post('/register', async (req, res) => {
const { name, email, password } = req.body
try {
// 檢查是否已有此電子郵件的用戶
const existingUser = await User.findOne({ email })
if (existingUser) {
return res.status(400).json({ error: 'Email already exists' })
}
// 密碼加密
const hashedPassword = await bcrypt.hash(password, 10)
// 創建新用戶
const newUser = new User({
name,
email,
password: hashedPassword,
})
await newUser.save()
res.status(201).json({ message: 'User created successfully' })
} catch (error) {
res.status(500).json({ error: 'Server error' })
}
})
// 用戶登入 (生成 JWT)
router.post('/login', async (req, res) => {
const { email, password } = req.body
try {
const user = await User.findOne({ email })
if (!user) {
return res.status(400).json({ error: 'Invalid credentials' })
}
// 密碼比對
const match = await bcrypt.compare(password, user.password)
if (!match) {
return res.status(400).json({ error: 'Invalid credentials' })
}
// 生成 JWT
const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' })
res.json({ token })
} catch (error) {
res.status(500).json({ error: 'Server error' })
}
})
// 獲取用戶資料
router.get('/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json(user)
} catch (error) {
res.status(500).json({ error: 'Server error' })
}
})
// 更新用戶資料
router.put('/:id', async (req, res) => {
const { name, email, password } = req.body
try {
const user = await User.findById(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
// 更新字段
if (name) user.name = name
if (email) user.email = email
if (password) user.password = await bcrypt.hash(password, 10)
await user.save()
res.json({ message: 'User updated successfully' })
} catch (error) {
res.status(500).json({ error: 'Server error' })
}
})
// 刪除用戶
router.delete('/:id', async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json({ message: 'User deleted successfully' })
} catch (error) {
res.status(500).json({ error: 'Server error' })
}
})
module.exports = router
在 index.js 中使用路由
// index.js
app.use(express.json())
const userRoutes = require('./routes/user')
app.use('/api/users', userRoutes)
請求時進行權限驗證
新增 middlewares/auth.js
需自行新增
JWT_SECRET
到 .env
// middlewares/auth.js
const jwt = require('jsonwebtoken')
const authenticate = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '')
if (!token) {
return res.status(401).json({ error: 'Authentication required' })
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = decoded
next()
} catch (error) {
res.status(401).json({ error: 'Invalid token' })
}
}
module.exports = authenticate
使用 middlewares
// index.js
const userRoutes = require('./routes/user')
const authenticate = require('./middlewares/auth')
app.use('/api/users', authenticate, userRoutes)
app.use('/api/users/register', userRoutes.register)
app.use('/api/users/login', userRoutes.login)
部署 API 到 Vercel
1.新增 vercel.json
到根目錄
{
"version": 2,
"builds": [
{
"src": "/src/index.js",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "/src/index.js"
}
]
}
2.將專案推上 Github
3.登入 Vercel 並且創建新的 Project
4.創建專案時選擇 Github repo 並且設置 env
5.部署完成後就能獲取 API 地址 https://<project_name>.vercel.app/
錯誤:連接 MongoDB 時出錯
如果出現下方錯誤代表需要將 IP 加到 atlas cluster 的 IP 白名單中
連接 MongoDB 時出錯: MongooseServerSelectionError: Could not connect to any servers in your MongoDB Atlas cluster. One common reason is that you're trying to access the database from an IP that isn't whitelisted. Make sure your current IP address is on your Atlas cluster's IP whitelist: https://www.mongodb.com/docs/atlas/security-whitelist/
在 Project > Data Services > SECURITY – Network Access > IP Access List 添加 IP
由於 Vercel 部署使用動態 IP 無法獲取一個固定 IP,所以需要新增 0.0.0.0/0
(允許所有 IP)。