casper's Profile Image

Full Stack Web/Mobile Developer

Nov, 1, 2024

如何使用 Next Js 15 Api 路由與 Prisma Mongodb 製作全堆疊 Web 應用程式

您只需使用 Next Js api 路由,即可製作全堆疊的 Web 應用程式,並與資料庫互動。

如何使用 Next Js 15 Api 路由與 Prisma Mongodb 製作全堆疊 Web 應用程式 Image

我們可以從建立全新的 next js 應用程式開始工作。

我不會使用 Typescript,因為我想讓它對初學者友善。

npx create-next-app@latest
您的專案名稱是什麼:fullstack-nextjs-app

您是否願意使用 Typescript:否

您是否願意使用 ESLint:否

您是否願意使用 Tailwind CSS:是

您希望您的程式碼位於「src/」目錄內嗎?不需要

您是否願意使用 App Router?是

您是否願意在下次開發時使用 Turbopack?是的

您想要自訂匯入別名 (@/* 預設值)嗎?否

安裝完成後,您可以在 Visual Studio Code 或任何您喜歡的 ide 編輯器中開啟專案資料夾。

yarn dev

使用下列程式碼變更 "globals.css"。

@tailwind base;
@tailwind components;
@tailwind utilities;

使用以下程式碼變更 "page.js"。

import CreatePost from "./components/CreatePost"
匯出預設函式 Home() {
  返回 (
    <div className="max-w-3xl container mx-auto flex items-center justify-center min-h-screen">
      <div className="flex flex-col space-y-2">
        <h1 className="text-3xl font-bold">Full Stack Next Js App</h1>

        <CreatePost />
      </div>
    </div
  );
}


在 app 資料夾內建立「元件」資料夾,並建立名為「CreatePost」的元件:

"use client"
import { useState } from "react"
const CreatePost = () => {
  const [title, setTitle] = useState("")  const [description, setDescription] = useState("")
  const handleSubmit = () => {
    console.log("clicked!")    // 我們會在這裡處理建立後的進度。
  };

  返回 (
    <div
      <div className="grid gap-6 mb-6 md:grid-cols-2">
        <div
          <label
            htmlFor="title"
            className="block mb-2 text-sm font-medium text-gray-900"
          >
            標題
          </label>
          <input
            type="text"
            id="title"
            value={title}
            onChange={(event) => setTitle(event.target.value)}
            className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
            placeholder="Title"
            需要
          />
        </div>

        <div
          <標籤
            htmlFor="description"
            className="block mb-2 text-sm font-medium text-gray-900"
          >
            說明
          </label>
          <input
            type="text"
            id="description"
            value={description}
            onChange={(event) => setDescription(event.target.value)}
            className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
            placeholder="Description"
            需要
          />
        </div> </div
      </div

      < 按鈕
        onClick={handleSubmit} 按鈕
        className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center"
      >
        建立
      </按鈕
    </div
  );
};

export default CreatePost;

我們的 Create Post 表單準備好了,現在我們可以為表單建立 api 路由。

在應用程式資料夾「api/posts/create」中建立資料夾,並建立「route.js」。

import { NextResponse } from "next/server"
匯出 async 函式 POST(request) {
  const body = await request.json()
  if (!body.title || !body.description) {
    return NextResponse.json(
      { error:"Title and description are required" }      { status: 200 }
    );
  }

  try {
    const post = { ...body, id:Date.now().toString() }
    console.log("New post created:", post)
    return NextResponse.json(post, { status: 201 })  } catch (error) {
    return NextResponse.json(
      { error:"Failed to create post" }      { status:500 }
    );
  }
}

現在我們有了 api 路由,可以處理從表單傳送的資料,並處理錯誤。

是時候編輯我們的 "CreatePost" 元件來測試 api 路由了。

js "use client";

import { useState } from "react";

const CreatePost = () => { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); const [errorMsg, setErrorMsg] = useState("");

const handleSubmit = async () => { try { const response = await fetch("/api/posts/create", { method:"POST"、 headers:{ "Content-Type":"application/json"、 }, body:JSON.stringify({ title、 description、 }), });

  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }

  const data = await response.json();

  if (data.error) {
    setErrorMsg(data.error);
  } else {
    console.log("Post created successfully:", data);
  }

  return data;
} catch (error) {
  console.error("There was a problem with the fetch operation:", error);
}

};

return ( <div {errorMsg ?





<strong ) : undefined}
<div <input type="text" id="title" value={title} onChange={(event) => setTitle(event.target.value)} className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5" placeholder="Title" 需要 />

    <div
      <標籤
        htmlFor="description"
        className="block mb-2 text-sm font-medium text-gray-900"
      >
        說明
      </label>
      <input
        type="text"
        id="description"
        value={description}
        onChange={(event) => setDescription(event.target.value)}
        className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
        placeholder="Description"
        需要
      />
    </div> </div
  </div

  < 按鈕
    onClick={handleSubmit} 按鈕
    className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center"
  >
    建立
  </按鈕
</div

); };

export default CreatePost;


您可以使用表單與 api 路由互動。如果您傳送空表單,您會在表單頂端看到一個錯誤訊息;如果您填寫表單欄位,您會在終端控制台和瀏覽器控制台看到建立的虛擬資料。

現在是時候初始化 Prisma 並連接到 MongoDB 資料庫。

全局安裝 Prisma CLI(如果尚未安裝):

npm install -g prisma


在專案中初始化 Prisma:

npx prisma init


此指令會建立一個有 schema.prisma 檔案的 prisma 目錄,您可以在其中定義資料庫模式。

在 schema.prisma 檔案結尾加入「Post」模型。

別忘了將資料庫提供者從 "postgresql 「改成 」mongodb"。

模型 Post { id String @id @default(auto())@map("_id") @db.ObjectId title String description String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }


編輯根目錄中 .env 檔案的 DATABASE_URL 變數:

DATABASE_URL="your-mongodb-connection-string"


推送您的 Prisma 結構圖到資料庫:

npx prisma db push


此指令會自動產生所需的檔案,如果沒有手動產生的話:

npx prisma generate


安裝相依性:

npm install @prisma/client


編輯 "/app/api/posts/create/route.js" 檔案:

```js
import { NextResponse } from "next/server";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

匯出 async 函式 POST(request) {
  const body = await request.json();

  if (!body.title || !body.description) {
    return NextResponse.json(
      { error:"Title and description are required" }、
      { status: 200 }
    );
  }

  try {
    const post = await prisma.post.create({
      data:{
        title: body.title、
        description: body.description、
      },
    });

    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error:"Failed to create post" }、
      { status:500 }
    );
  } finally {
    await prisma.$disconnect();
  }
}


現在您可以使用我們的表單建立文章。

您可以使用 Prisma Studio 檢查您的資料庫:

npx prisma studio

讓我們列出建立文章表單下的文章。

在 "app/components「 資料夾內建立 」Posts" 元件:

"use client"
import { useEffect, useState } from "react"
const Posts = () => {
  const [posts, setPosts] = useState([])
  const getPosts = async () => {
    try {
      const response = await fetch("/api/posts/get")
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)      }

      const data = await response.json()
      setPosts(data.posts)
      return data;
    } catch (error) {
      console.error("There was a problem with the fetch operation:", error)    }
  };

  useEffect(() => {
    getPosts()  }, []);

  返回 (
    <div className="space-y-4 mt-8">
      <h2 className="text-2xl font-semibold">Posts ({posts.length})</h2>

      {posts.map((post) => (
        <div key={post.id} className="bg-blue-50 p-4 rounded-md">
          <p>{post.title}</p>
          <p>{post.description}</p>
        </div
      ))}
    </div>
  );
};

輸出 default Posts;

新增 "Posts「 元件至 」/app/page.js":

import CreatePost from "./components/CreatePost"import Posts from "./components/Posts"
匯出預設函式 Home() {
  返回 (
    <div className="max-w-3xl container mx-auto flex items-center justify-center min-h-screen">
      <div className="flex flex-col space-y-2">
        <h1 className="text-3xl font-bold">Full Stack Next Js App</h1>

        <CreatePost />

        <Posts />
      </div>
    </div
  );
}


建立新的 api 路由 "/app/api/posts/get/route.js":

import { NextResponse } from "next/server"import { PrismaClient } from "@prisma/client"
const prisma = new PrismaClient()
匯出 async 函式 GET() {
  try {
    const posts = await prisma.post.findMany()
    return NextResponse.json({ posts })  } catch (error) {
    return NextResponse.json({ error: "Failed to get posts" }, { status: 500 })  } finally {
    await prisma.$disconnect()  }
}

現在您可以在首頁看到已建立的文章。

讓我們加入刪除文章的功能。

建立「/app/api/posts/delete/[id]/route.js」:

import { PrismaClient } from "@prisma/client"import { NextResponse } from "next/server"
const prisma = new PrismaClient()
匯出 async 函式 DELETE(_request, context) {
  try {
    const params = await context.params;
    const id = params.id;

    // 驗證 id
    if (!id) {
      return NextResponse.json({ error: "ID not found!" }, { status: 400 })    }

    // 刪除文章
    const deletedPost = await prisma.post.delete({
      where:{
        id、
      },
    });

    if (!deletedPost) {
      return NextResponse.json({ error: "Post not found" }, { status: 404 })    }

    return NextResponse.json(
      { message:"Post deleted successfully" }      { status: 200 }
    );
  } catch (error) {
    console.error("Error deleting post:", error)    return NextResponse.json(
      { error:"Failed to delete post" }      { status:500 }
    );
  } finally {
    await prisma.$disconnect()  }
}

編輯「Posts」元件:

"use client"
import { useEffect, useState } from "react"
const Posts = () => {
  const [posts, setPosts] = useState([])
  const deletePost = async (id) => {
    try {
      const response = await fetch(`/api/posts/delete/${id}`, {
        method:"DELETE"      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)      }

      window.location.reload()      return data;
    } catch (error) {
      console.error("There was a problem with the delete operation:", error)    }
  };

  const getPosts = async () => {
    try {
      const response = await fetch("/api/posts/get")
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)      }

      const data = await response.json()
      setPosts(data.posts)
      return data;
    } catch (error) {
      console.error("There was a problem with the fetch operation:", error)    }
  };

  useEffect(() => {
    getPosts()  }, []);

  返回 (
    <div className="space-y-4 mt-8">
      <h2 className="text-2xl font-semibold">Posts ({posts.length})</h2>

      {posts.map((post) => (
        <div key={post.id} className="bg-blue-50 p-4 rounded-md">
          <p>{post.title}</p>
          <p>{post.description}</p>

          <div className="flex">
            <p
              onClick={() => deletePost(post.id)}
              className="mt-2 bg-red-500 px-2 py-1 rounded-md text-xs text-gray-50 font-semibold cursor-pointer"
            >
              刪除
            </p>
          </div
        </div
      ))}
    </div>
  );
};

輸出 default Posts;

現在您可以列出文章並逐一刪除。

您也可以建立編輯功能和分頁功能。請隨意編輯我們所寫的程式碼。

0
0

Comments (0)