使用 Next.js 串接 MongoDB

MongoDB 詳細操作: https://www.notion.so/Use-MERN-stack-to-dev-Full-Stack-web-2d59def5443d80a194a5e8d6bf6d740a

我們建立好叢集和集合後,就可以去Next.js 寫上環境設定和連接了!

如果在本地測試都OK,部屬到Vercel就不行了,可能要在Vercel中加上環境變數!


環境設定

可以在專案中的 DataBase & NetWork Access 查看使用者名稱

MONGODB_URI=mongodb+srv://使用者名稱:密碼@cluster0.tend0gf.mongodb.net/資料庫名稱?appName=Cluster0Code language: JavaScript (javascript)

切記!!!!! 在專案中的 NetWork Access 設定 允許 0.0.0.0/0

接著要下載官方套件

npm install @auth/mongodb-adapter mongodb@^6.13.0Code language: CSS (css)

lib / db.ts

寫一個連線池(Connection Pool),避免因為無限制地重複連線而導致資料庫崩潰。

import { MongoClient, ServerApiVersion } from 'mongodb';

const uri = process.env.MONGODB_URI;

function createClient(): MongoClient {
  if (!uri) {
    throw new Error(
      '請新增MONGODB_URI環境變數',
    );
  }

  return new MongoClient(uri, {
    serverApi: {
      version: ServerApiVersion.v1,
      strict: true,
      deprecationErrors: true,
    },
  });
}

declare global {
  // eslint-disable-next-line no-var
  var _mongoClientPromise: Promise<MongoClient> | undefined;
}

let clientPromise: Promise<MongoClient>;

if (process.env.NODE_ENV === 'development') {
  if (!global._mongoClientPromise) {
    const client = createClient();
    global._mongoClientPromise = client.connect();
  }
  clientPromise = global._mongoClientPromise;
} else {
  const client = createClient();
  clientPromise = client.connect();
}

export default clientPromise;

export async function pingMongo(): Promise<boolean> {
  const client = await clientPromise;
  await client.db().command({ ping: 1 });
  return true;
}Code language: TypeScript (typescript)

auth.tsx

上一章有講到如何使用google sso來實現登入,當使用者登入後我們當然要存下他的資料,因此我們更改我們的auth.tsx:

  • 首次登入:如果是第一次登入的,Auth.js 會自動透過 clientPromise 連線到 MongoDB,並在指定的資料庫中自動建立三個Collections:usersaccountssessions。接著觸發 events.createUserevents.linkAccount,可以在 Vercel 的 Logs 中看到 [auth] MongoDB user created:... 的訊息。
  • Session 維持:每次使用者重新整理網頁或切換路由時,Auth.js 透過瀏覽器 Cookie 中的 sessionToken 去 MongoDB 的 sessions 集合查詢該 Session 是否過期。
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import { MongoDBAdapter } from '@auth/mongodb-adapter';
import clientPromise from '@/lib/db';

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: MongoDBAdapter(clientPromise),
  providers: [
    Google({
      clientId: process.env.AUTH_GOOGLE_ID,
      clientSecret: process.env.AUTH_GOOGLE_SECRET,
    }),
  ],
  secret: process.env.AUTH_SECRET,
  trustHost: true,
  pages: {
    signIn: '/login',
  },
  session: {
    strategy: 'database',
  },
  callbacks: {
    async session({ session, user }) {
      if (session.user && user.id) {
        session.user.id = user.id;
      }
      return session;
    },
  },
  events: {
    async createUser({ user }) {
      console.info('[auth] MongoDB user created:', user.id, user.email);
    },
    async linkAccount({ user }) {
      console.info('[auth] Account linked for user:', user.id);
    },
  },
});Code language: JavaScript (javascript)

update 資料到MongoDB

使用Server action。我們可以在Server action中(也就是後端)把資料塞進資料庫。

import { auth } from '@/auth';
import { clientPromise } from '@/lib/db';

/**
 * 萬用模板:新增或更新資料到指定的 Collection
 * @param data 欲存入的資料內容(可根據需求定義型別)
 */
export async function saveSomethingToDb(data: any): Promise<{ success: boolean; message?: string }> {
  // 1. 安全防禦:檢查使用者是否登入
  const session = await auth();
  if (!session?.user?.id) {
    return { success: false, message: '儲存失敗:使用者尚未登入' };
  }

  try {
    // 2. 獲取資料庫連線與實例
    const client = await clientPromise;
    const db = client.db();

    // 3. 執行資料庫寫入動作 (使用 updateOne + upsert)
    const COLLECTION_NAME = 'YOUR_COLLECTION_NAME'; // 改成你資料表的名稱 (例如: user_profiles, bookmarks)
    
    await db.collection(COLLECTION_NAME).updateOne(
      { userId: session.user.id }, // 查詢條件:確保這筆資料牢牢綁定在目前登入的使用者身上
      {
        $set: {
          // 改成想塞入的欄位與資料格式
          yourDataKey: data, 
          updatedAt: new Date(), // 順便記錄最後修改時間
        },
        // 如果是全新檔案,想在建立時加入「建立時間」,可以使用 $setOnInsert
        $setOnInsert: {
          createdAt: new Date(),
        }
      },
      { upsert: true } // 如果找不到該 userId 的文件就「直接新增」;如果找到了就「局部更新」欄位
    );

    return { success: true };
  } catch (error) {
    // 4. 錯誤捕捉與日誌紀錄
    console.error(`[saveSomethingToDb][${new Date().toISOString()}]`, error);
    return { success: false, message: '伺服器內部錯誤,請稍後再試' };
  }
}Code language: JavaScript (javascript)