如何在 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 上参与 通过给存储库加星、报告问题或为问题贡献代码。