筆記非教程。
建立 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}`)
})
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/?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)
await mongoose.connection.db.admin().command({ ping: 1 })
console.log("Pinged your deployment. You successfully connected to MongoDB!")
} finally {
await mongoose.disconnect()
}
}
module.exports = { run }
index.js
require('dotenv').config()
const express = require('express')
const app = express()
const db = require('./connections')
const port = process.env.PORT || 3000
db.run().catch(console.dir)
app.use(express.json())
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
資料的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)
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Server is running on port ${port}`)
})
請求時進行權限驗證
新增 middlewares/auth.js
// 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)
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Server is running on port ${port}`)
})
部署 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/