跳到主要内容

如何在 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 之旅: 我们的活跃社区。保持信息灵通,参与其中,并与其他开发者协作

我们真诚地重视您的参与,并期待您成为我们社区的一部分!

© . All rights reserved.