跳到主要內容

如何在 Shopify 中使用 Prisma Postgres

25 分鐘

簡介

Shopify 是一個流行的電子商務商店構建平臺。本指南將向您展示如何將 Shopify 應用程式連線到 Prisma Postgres 資料庫,以便為產品建立內部備註。

先決條件

1. 設定你的專案

注意

如果您尚未安裝 Shopify CLI,可以使用 npm install -g @shopify/cli 進行安裝。

首先,使用 Shopify CLI 初始化一個新的 Shopify 應用程式。

shopify app init

在設定過程中,系統會提示您自定義應用程式。別擔心——只需遵循以下推薦選項,即可快速入門並確保您的應用程式設定成功。

資訊
  • 開始構建您的應用程式: 構建一個 Remix 應用程式 (推薦)
  • 對於您的 Remix 模板,您想要使用哪種語言: JavaScript
  • 應用程式名稱: prisma-store (名稱不能包含 shopify)

導航到 prisma-store 目錄

cd prisma-store

2. 設定 Prisma

Prisma 已預先安裝在您的專案中,但讓我們花點時間將其更新到最新版本。這確保您在構建應用程式時可以訪問最新功能、改進和最佳體驗。

您將切換到 Prisma Postgres 資料庫,因此請刪除 prisma 目錄中的 migrations 資料夾和 dev.sqlite 檔案。

您需要更新 schema.prisma 檔案中的一些內容,使其與 Remix 和 Prisma Postgres 協同工作。

  • 切換到新的 prisma-client 生成器。
  • 將 provider 更新為 postgresql
  • 將 url 更新為新的資料庫 url。
prisma/schema.prisma
generator client {
provider = "prisma-client-js"
provider = "prisma-client"
output = "../app/generated/prisma"
}

datasource db {
provider = "sqlite"
provider = "postgresql"
url = "file:../dev.db"
url = env("DATABASE_URL")
}

model Session {
// ... existing model
}

為了使您的應用程式能夠為每個產品儲存備註,讓我們向 Prisma schema 新增一個新的 ProductNote 模型。

這個模型將允許您透過 productGid 欄位在資料庫中儲存和組織與單個產品關聯的備註。

prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../app/generated/prisma"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Session {
// ... existing model
}

model ProductNote {
id String @id @default(uuid())
productGid String
body String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

接下來,Prisma 需要更新到最新版本。執行

npm install prisma --save-dev && npm install @prisma/client

Prisma Postgres 允許您即時建立新資料庫,您可以透過新增 --db 標誌在初始化專案時同時建立一個新資料庫

npx prisma init --db

完成提示後,是時候訪問您的新資料庫了

  1. 開啟:
    • 登入並找到您新建立的資料庫專案。
  2. 設定您的資料庫憑據
    • 在側邊欄中,點選 Database,然後選擇 Setup
    • 選擇 Existing project 並按下 Generate database credentials
  3. 配置您的環境
    • 在專案的根目錄中建立一個新的 .env 檔案。
    • 將您剛剛生成的 DATABASE_URL 複製並貼上到此檔案中。它應該看起來類似於:
    DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=..."
  4. 應用您的資料庫 schema
    • 執行以下命令來建立表並準備好資料庫
    npx prisma migrate dev --name init

現在,在繼續之前,讓我們更新 db.server.ts 檔案以使用新生成的 Prisma 客戶端。

app/db.server.ts
import { PrismaClient } from "@prisma/client";
import { PrismaClient } from "./generated/prisma/client.js";

if (process.env.NODE_ENV !== "production") {
if (!global.prismaGlobal) {
global.prismaGlobal = new PrismaClient();
}
}

const prisma = global.prismaGlobal ?? new PrismaClient();

export default prisma;
警告

建議將 app/generated/prisma 新增到您的 .gitignore 檔案中。

3. 建立你的 Remix 模型

為了保持專案組織良好,讓我們建立一個新的 models/ 資料夾。在這個資料夾中,新增一個名為 notes.server.js 的檔案。這將是所有與備註相關的邏輯的存放處,並使您的程式碼庫隨著應用程式的增長更易於管理。

notes.server.js 檔案將包含兩個函式

  • getNotes - 這將獲取給定產品的所有備註。
  • createNote - 這將為給定產品建立新的備註。

首先從 db.server.ts 匯入 Prisma 客戶端並建立 getNotes 函式

models/notes.server.js
import prisma from "../db.server";

export const getNotes = async (productGid) => {
const notes = await prisma.productNote.findMany({
where: { productGid: productGid.toString() },
orderBy: { createdAt: "desc" },
});
return notes;
};

為了讓使用者能夠向您的資料庫新增新備註,讓我們在 notes.server.js 中建立一個使用 prisma.productNote.create 的函式

models/notes.server.js
import prisma from "../db.server";

export const getNotes = async (productGid) => {
const notes = await prisma.productNote.findMany({
where: { productGid: productGid.toString() },
orderBy: { createdAt: "desc" },
});
return notes;
};

export const createNote = async (note) => {
const newNote = await prisma.productNote.create({
data: {
body: note.body,
productGid: note.productGid,
},
});
return newNote;
};

4. 建立您的佈局路由

在這些函式能夠被呼叫之前,我們的路由需要一個佈局來承載。這個佈局路由將具有一個用於選擇產品的按鈕,並將作為您的 ProductNotes 路由的父級,從而使您的應用程式組織有序且使用者友好。

4.1. 建立 ProductNotesLayout 元件

首先建立資料夾 routes/app.product-notes.jsx 並在其中新增 ProductNotesLayout 元件

app/routes/app.product-notes.jsx
import { Page, Layout } from "@shopify/polaris";

export default function ProductNotesLayout() {
return (
<Page title="Product Notes">
<Layout>
<Layout.Section></Layout.Section>
</Layout>
</Page>
);
}

接下來,建立 selectProduct 函式和一個 Button,讓使用者選擇一個產品

app/routes/app.product-notes.jsx
import { useNavigate } from "@remix-run/react";
import { Page, Layout } from "@shopify/polaris";
import { Button, Page, Layout } from "@shopify/polaris";

export default function ProductNotesLayout() {
const navigate = useNavigate();

async function selectProduct() {
const products = await window.shopify.resourcePicker({
type: "product",
action: "select",
});
const selectedGid = products[0].id;
navigate(`/app/product-notes/${encodeURIComponent(selectedGid)}`);
}

return (
<Page title="Product Notes">
<Layout>
<Layout.Section>
<Button onClick={selectProduct} fullWidth size="large">
Select Product
</Button>
</Layout.Section>
</Layout>
</Page>
);
}

Remix 提供了渲染巢狀路由的能力。在 routes/app.product-notes.jsx 檔案中新增一個 <Outlet />,以便渲染 ProductNotes 路由

app/routes/app.product-notes.jsx
import { useNavigate } from "@remix-run/react";
import { Outlet, useNavigate } from "@remix-run/react";
import { Page, Button, Layout } from "@shopify/polaris";

export default function ProductNotesLayout() {
const navigate = useNavigate();

async function selectProduct() {
const products = await window.shopify.resourcePicker({
type: "product",
action: "select",
});
const selectedGid = products[0].id;
navigate(`/app/product-notes/${encodeURIComponent(selectedGid)}`);
}

return (
<Page title="Product Notes">
<Layout>
<Layout.Section>
<Button onClick={selectProduct} fullWidth size="large">
Select Product
</Button>
</Layout.Section>
<Outlet />
</Layout>
</Page>
);
}

4.2. 將 ProductNotesLayout 新增到側邊欄

如果您執行 npm run dev,您將無法看到 Product Notes 路由。要解決這個問題,您需要將 ProductNotesLayout 新增到 app.jsx 檔案中,以便它顯示在側邊欄中

app/routes/app.jsx
import { Link, Outlet, useLoaderData, useRouteError } from "@remix-run/react";
import { boundary } from "@shopify/shopify-app-remix/server";
import { AppProvider } from "@shopify/shopify-app-remix/react";
import { NavMenu } from "@shopify/app-bridge-react";
import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
import { authenticate } from "../shopify.server";

export const links = () => [{ rel: "stylesheet", href: polarisStyles }];

export const loader = async ({ request }) => {
await authenticate.admin(request);

return { apiKey: process.env.SHOPIFY_API_KEY || "" };
};

export default function App() {
const { apiKey } = useLoaderData();

return (
<AppProvider isEmbeddedApp apiKey={apiKey}>
<NavMenu>
<Link to="/app" rel="home">
Home
</Link>
<Link to="/app/product-notes">Product Notes</Link>
</NavMenu>
<Outlet />
</AppProvider>
);
}

// Shopify needs Remix to catch some thrown responses, so that their headers are included in the response.
export function ErrorBoundary() {
return boundary.error(useRouteError());
}

export const headers = (headersArgs) => {
return boundary.headers(headersArgs);
};

5. 建立你的產品備註路由

目前,如果你執行 npm run dev 並導航到 Product Notes 路由,在選擇產品後,你將什麼也看不到。

按照以下步驟建立產品備註路由

建立一個新的 routes/app/app.notes.$productGid.jsx 檔案,該檔案將接收 productGid 作為引數,並返回與產品關聯的產品備註以及建立新備註的表單

app/routes/app/app.notes.$productGid.jsx
export default function ProductNotes() {
return (
<></>
);
}

5.1. 渲染備註

載入時,路由需要獲取產品的備註並顯示它們。

為路由新增 loader 函式

app/routes/app/app.notes.$productGid.jsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { getNotes } from "../models/note.server";

export const loader = async ({ params }) => {
const { productGid } = params;
const notes = await getNotes(productGid);
return json({ notes, productGid });
};

export default function ProductNotes() {
const { notes, productGid } = useLoaderData();

return (
<></>
);
}

使用 Polaris 元件在 ProductNotes 元件中映射出備註

app/routes/app/app.notes.$productGid.jsx
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { getNotes } from "../models/note.server";
import { Card, Layout, Text, BlockStack } from "@shopify/polaris";

export const loader = async ({ params }) => {
const { productGid } = params;
const notes = await getNotes(productGid);
return json({ notes, productGid });
};

export default function ProductNotes() {
const { notes, productGid } = useLoaderData();
return (
<>
<Layout.Section>
<BlockStack gap="200">
{notes.length === 0 ? (
<Text as="p" variant="bodyMd" color="subdued">
No notes yet.
</Text>
) : (
notes.map((note) => (
<Card key={note.id} sectioned>
<BlockStack gap="100">
{note.body && (
<Text as="p" variant="bodyMd">
{note.body}
</Text>
)}
<Text as="p" variant="bodySm" color="subdued">
Added: {new Date(note.createdAt).toLocaleString()}
</Text>
</BlockStack>
</Card>
))
)}
</BlockStack>
</Layout.Section>
</>
);
}

您應該會看到“暫無備註”。如果是這樣,您就走對了。

5.2. 新增表單

需要向路由新增一些內容才能建立新備註

  • 為路由新增 action 函式。
  • 建立備註時顯示 Toast 通知。
  • models/note.server.js 匯入 createNote 函式。
  • 匯入 useActionDatauseAppBridge
app/routes/app/app.notes.$productGid.jsx
import { json, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { useLoaderData, useActionData } from "@remix-run/react";
import { getNotes } from "../models/note.server";
import { getNotes, createNote } from "../models/note.server";
import { Card, Layout, Text, BlockStack } from "@shopify/polaris";
import { useAppBridge } from "@shopify/app-bridge-react";

export const loader = async ({ params }) => {
const { productGid } = params;
const notes = await getNotes(productGid);
return json({ notes, productGid });
};

export const action = async ({ request, params }) => {
const formData = await request.formData();
const body = formData.get("body")?.toString() || null;
const { productGid } = params;

await createNote({ productGid, body });
return redirect(`/app/product-notes/${encodeURIComponent(productGid)}`);
};

export default function ProductNotes() {
const { notes, productGid } = useLoaderData();
const actionData = useActionData();
const app = useAppBridge();

useEffect(() => {
if (actionData?.ok) {
app.toast.show("Note saved", { duration: 3000 });
setBody("");
}
}, [actionData, app]);

return (
<>
<Layout.Section>
<BlockStack gap="200">
{notes.length === 0 ? (
<Text as="p" variant="bodyMd" color="subdued">
No notes yet.
</Text>
) : (
notes.map((note) => (
<Card key={note.id} sectioned>
<BlockStack gap="100">
{note.body && (
<Text as="p" variant="bodyMd">
{note.body}
</Text>
)}
<Text as="p" variant="bodySm" color="subdued">
Added: {new Date(note.createdAt).toLocaleString()}
</Text>
</BlockStack>
</Card>
))
)}
</BlockStack>
</Layout.Section>
</>
);
}

現在,您可以構建將呼叫 action 函式的表單

app/routes/app/app.notes.$productGid.jsx
import { json, redirect } from "@remix-run/node";
import { useLoaderData, useActionData } from "@remix-run/react";
import { getNotes, createNote } from "../models/note.server";
import { Card, Layout, Text, BlockStack } from "@shopify/polaris";
import { Card, Layout, Text, BlockStack, Form, FormLayout, TextField, Button } from "@shopify/polaris";
import { useAppBridge } from "@shopify/app-bridge-react";

export const loader = async ({ params }) => {
const { productGid } = params;
const notes = await getNotes(productGid);
return json({ notes, productGid });
};

export const action = async ({ request, params }) => {
const formData = await request.formData();
const body = formData.get("body")?.toString() || null;
const { productGid } = params;

await createNote({ productGid, body });
return redirect(`/app/product-notes/${encodeURIComponent(productGid)}`);
};

export default function ProductNotes() {
const { notes, productGid } = useLoaderData();
const actionData = useActionData();
const app = useAppBridge();

useEffect(() => {
if (actionData?.ok) {
app.toast.show("Note saved", { duration: 3000 });
setBody("");
}
}, [actionData, app]);

return (
<>
<Layout.Section>
<Card sectioned>
<Form method="post">
<FormLayout>
<BlockStack gap="200">
<input type="hidden" name="productGid" value={productGid} />
<TextField
label="Note"
value={body}
onChange={setBody}
name="body"
autoComplete="off"
multiline={4}
/>
<Button submit primary>
Add Note
</Button>
</BlockStack>
</FormLayout>
</Form>
</Card>
</Layout.Section>
<Layout.Section>
<BlockStack gap="200">
{notes.length === 0 ? (
<Text as="p" variant="bodyMd" color="subdued">
No notes yet.
</Text>
) : (
notes.map((note) => (
<Card key={note.id} sectioned>
<BlockStack gap="100">
{note.body && (
<Text as="p" variant="bodyMd">
{note.body}
</Text>
)}
<Text as="p" variant="bodySm" color="subdued">
Added: {new Date(note.createdAt).toLocaleString()}
</Text>
</BlockStack>
</Card>
))
)}
</BlockStack>
</Layout.Section>
</>
);
}

您現在應該能夠向產品新增備註並看到其顯示。

6. 測試你的路由

執行 npm run dev 並導航到 Product Notes 路由。

  • 導航到側邊欄上的“產品備註”
  • 選擇一個產品
  • 新增備註
  • 驗證備註是否正確顯示和儲存。

下一步

現在您已經擁有一個連線到 Prisma Postgres 資料庫的 Shopify 應用程式,您可以:

  • 使用更多模型和關係擴充套件您的 Prisma schema
  • 新增建立/更新/刪除路由和表單
  • 透過 Prisma Postgres 啟用查詢快取以獲得更好的效能

更多資訊


保持與 Prisma 的聯絡

透過以下方式繼續您的 Prisma 之旅: 我們的活躍社群。保持資訊靈通,參與其中,並與其他開發者協作

我們真誠地重視您的參與,並期待您成為我們社群的一部分!

© . This site is unofficial and not affiliated with Prisma Data, Inc.