Express + MongoDB Atlas + Vercel 部署後端 API

Express + MongoDB Atlas + Vercel 部署後端 API

筆記非教程。

建立 Express 專案

先安裝 express 和 dotenv

npm install express dotenv

專案結構

  • src
    • connections
      • index.js
    • models
    • routes
    • index.js
  • .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)

    image 5

    建好後點 Connect

    image 6

    Driver 選 Mongoose,然後複製第三步的 connection string

    image 8

    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/

    guest

    0 評論
    最舊
    最新 最多投票
    內聯回饋
    查看全部評論