如何在 Shopify 中使用 Prisma Postgres
簡介
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。
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 欄位在資料庫中儲存和組織與單個產品關聯的備註。
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
完成提示後,是時候訪問您的新資料庫了
- 開啟:
- 登入並找到您新建立的資料庫專案。
- 設定您的資料庫憑據
- 在側邊欄中,點選 Database,然後選擇 Setup。
- 選擇 Existing project 並按下 Generate database credentials。
- 配置您的環境
- 在專案的根目錄中建立一個新的
.env檔案。 - 將您剛剛生成的
DATABASE_URL複製並貼上到此檔案中。它應該看起來類似於:
DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=..." - 在專案的根目錄中建立一個新的
- 應用您的資料庫 schema
- 執行以下命令來建立表並準備好資料庫
npx prisma migrate dev --name init
現在,在繼續之前,讓我們更新 db.server.ts 檔案以使用新生成的 Prisma 客戶端。
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 函式
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 的函式
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 元件
import { Page, Layout } from "@shopify/polaris";
export default function ProductNotesLayout() {
return (
<Page title="Product Notes">
<Layout>
<Layout.Section></Layout.Section>
</Layout>
</Page>
);
}
接下來,建立 selectProduct 函式和一個 Button,讓使用者選擇一個產品
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 路由
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 檔案中,以便它顯示在側邊欄中
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 作為引數,並返回與產品關聯的產品備註以及建立新備註的表單
export default function ProductNotes() {
return (
<></>
);
}
5.1. 渲染備註
載入時,路由需要獲取產品的備註並顯示它們。
為路由新增 loader 函式
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 元件中映射出備註
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函式。 - 匯入
useActionData和useAppBridge
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 函式的表單
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 之旅: 我們的活躍社群。保持資訊靈通,參與其中,並與其他開發者協作
- 在 X 上關注我們 獲取公告、直播活動和實用技巧。
- 加入我們的 Discord 提問、與社群交流,並透過對話獲得積極支援。
- 訂閱 YouTube 獲取教程、演示和直播。
- 在 GitHub 上參與 透過給儲存庫加星、報告問題或為問題貢獻程式碼。