如何将 Prisma Postgres 与 Shopify 结合使用
介绍
¥Introduction
Shopify 是一个用于构建电商商店的热门平台。本指南将向你展示如何将 Shopify 应用连接到 Prisma Postgres 数据库,以便为产品创建内部注释。
¥Shopify is a popular platform for building e-commerce stores. This guide will show you how to connect a Shopify app to a Prisma Postgres database in order to create internal notes for products.
先决条件
¥Prerequisites
1. 设置你的项目
¥ Set up your project
如果你未安装 Shopify CLI,可以使用 npm install -g @shopify/cli
进行安装。
¥If you do not have the Shopify CLI installed, you can install it with npm install -g @shopify/cli
.
首先,使用 Shopify CLI 初始化一个新的 Shopify 应用:
¥To start, initialize a new Shopify app using the Shopify CLI:
shopify app init
在设置过程中,系统会提示你自定义应用。别担心 - 只需按照以下推荐选项即可快速入门并确保你的应用设置成功:
¥During setup, you'll be prompted to customize your app. Don't worry—just follow these recommended options to get started quickly and ensure your app is set up for success:
-
开始构建你的应用:
Build a Remix app (recommended)
¥Get started building your app:
Build a Remix app (recommended)
-
对于你的 Remix 模板,你需要哪种语言:
JavaScript
¥For your Remix template, which language do you want:
JavaScript
-
应用名称:
prisma-store
(名称不能包含shopify
)¥App Name:
prisma-store
(name cannot containshopify
)
进入 prisma-store
目录:
¥Navigate to the prisma-store
directory:
cd prisma-store
2. 设置 Prisma
¥ Set up Prisma
Prisma 已预装在你的项目中,但请花点时间将其更新到最新版本。这可确保你在构建应用时能够访问最新的功能、改进和最佳体验。
¥Prisma comes pre-installed in your project, but let's take a moment to update it to the latest version. This ensures you have access to the newest features, improvements, and the best possible experience as you build your app.
你将切换到 Prisma Postgres 数据库,因此请删除 prisma
目录中的 migrations
文件夹和 dev.sqlite
文件。
¥You will be swapping to a Prisma Postgres database, so delete the migrations
folder along with the dev.sqlite
file, inside of the prisma
directory.
你需要更新 schema.prisma
文件中的几项内容,以使其能够与 Remix 和 Prisma Postgres 兼容。
¥You need to update a few things in the schema.prisma
file to get it working with Remix and Prisma Postgres.
-
切换到新的
prisma-client
生成器。¥Swap to the new
prisma-client
generator. -
将提供程序更新为
postgresql
。¥Update the provider to
postgresql
. -
将 URL 更新为新的数据库 URL。
¥Update the url to the new database 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 模式中添加一个新的 ProductNote
模型。
¥To enable your app to store notes for each product, let's add a new ProductNote
model to your Prisma schema.
此模型允许你通过 productGid
字段保存和组织链接到数据库中各个产品的注意。
¥This model will allow you to save and organize notes linked to individual products in your database through the productGid
field.
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 更新到最新版本。运行:
¥Next, Prisma will need to be updated to the latest version. Run:
npm install prisma --save-dev && npm install @prisma/client
Prisma Postgres 允许你动态创建新数据库,你可以在初始化项目的同时通过添加 --db
标志来创建新数据库:
¥Prisma Postgres allows you to create a new database on the fly, you can create a new database at the same time you initialize your project by adding the --db
flag:
npx prisma init --db
完成提示后,就可以访问你的新数据库了:
¥Once you've completed the prompts, it's time to access your new database:
-
打开 :
¥Open the :
-
登录并找到你新创建的数据库项目。
¥Log in and find your newly created database project.
-
-
设置你的数据库凭据:
¥Set up your database credentials:
-
在侧边栏中,点击“数据库”,然后选择“设置”。
¥In the sidebar, click Database, then select Setup.
-
选择“现有项目”,然后按“生成数据库凭据”。
¥Choose Existing project and press Generate database credentials.
-
-
配置你的环境:
¥Configure your environment:
-
在项目根目录中创建一个新的
.env
文件。¥Create a new
.env
file in the root of your project. -
将你刚刚生成的
DATABASE_URL
文件复制并粘贴到此文件中。它应该类似于以下内容:¥Copy and paste the
DATABASE_URL
you just generated into this file. It should look similar to this:
DATABASE_URL="prisma+postgres://accelerate.prisma-data.net/?api_key=..."
-
-
应用你的数据库模式:
¥Apply your database schema:
-
运行以下命令创建表并准备好数据库:
¥Run the following command to create your tables and get your database ready:
npx prisma migrate dev --name init
-
现在,在继续之前,让我们更新你的 db.server.ts
文件以使用新生成的 Prisma 客户端。
¥Now, before moving on, let's update your db.server.ts
file to use the newly generated Prisma client.
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
文件中。
¥It is recommended to add app/generated/prisma
to your .gitignore
file.
3. 创建你的 Remix 模型
¥ Create your Remix model
为了使你的项目井井有条,让我们创建一个新的 models/
文件夹。在此文件夹中,添加一个名为 notes.server.js
的文件。这将成为你所有与注意相关的逻辑的归宿,并使你的代码库随着应用的增长而更易于管理。
¥To keep your project organized, let's create a new models/
folder. Inside this folder, add a file named notes.server.js
. This will be the home for all your note-related logic and make your codebase easier to manage as your app grows.
notes.server.js
文件包含两个函数:
¥The notes.server.js
file will contain two functions:
-
getNotes
- 这将获取给定产品的所有注释。¥
getNotes
- This will get all the notes for a given product. -
createNote
- 这将为给定产品创建新的注意。¥
createNote
- This will create a new note for a given product.
首先从 db.server.ts
导入 Prisma 客户端,并创建 getNotes
函数:
¥Start by importing the Prisma client from db.server.ts
and creating the getNotes
function:
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
的函数:
¥To enable users to add new notes to your database, let's create a function in notes.server.js
that uses 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. 创建你的布局路由
¥ Create your layout route
在这些函数能够被调用之前,我们的路由需要一个布局。此布局路由将包含一个用于选择产品的按钮,并将作为 ProductNotes
路由的父路由,从而使你的应用保持井然有序且用户友好。
¥Before those functions are able to be called, our route needs a layout to sit in. This layout route will feature a button for selecting a product, and will act as the parent for your ProductNotes
route, keeping your app organized and user-friendly.
4.1.创建 ProductNotesLayout 组件
¥4.1. Create the ProductNotesLayout component
首先创建文件夹 routes/app.product-notes.jsx
,并在其中添加 ProductNotesLayout
组件:
¥Start by creating the the folder routes/app.product-notes.jsx
and adding the ProductNotesLayout
component inside of it:
import { Page, Layout } from "@shopify/polaris";
export default function ProductNotesLayout() {
return (
<Page title="Product Notes">
<Layout>
<Layout.Section></Layout.Section>
</Layout>
</Page>
);
}
接下来,创建 selectProduct
函数和 Button
函数,以便用户选择产品:
¥Next, create the selectProduct
function and a Button
to let the user pick a product:
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 渲染提供了渲染嵌套路由的功能。在将渲染 ProductNotes
路由的 routes/app.product-notes.jsx
文件中添加 <Outlet />
函数:
¥Remix renders provides the ability to render a nested route. Add an <Outlet />
to the routes/app.product-notes.jsx
file where the ProductNotes
route will be rendered:
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 添加到侧边栏
¥4.2. Add the ProductNotesLayout to the sidebar
如果你运行 npm run dev
,你将无法看到 Product Notes
路由。要解决此问题,你需要将 ProductNotesLayout
添加到 app.jsx
文件中,以便它显示在侧边栏中:
¥If you run npm run dev
, you won't be able to see the Product Notes
route. To fix this, you need to add the ProductNotesLayout
to the app.jsx
file so it shows up in the sidebar:
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. 创建你的产品备注路由
¥ Create your product notes route
目前,如果你运行 npm run dev
并导航到 Product Notes
路径,选择产品后将看不到任何内容。
¥Currently, if you run npm run dev
and navigate to the Product Notes
route, you will see nothing once selecting a product.
按照以下步骤创建产品备注路由:
¥Follow these steps to create the product notes route:
创建一个新的 routes/app/app.notes.$productGid.jsx
文件,该文件将接收 productGid 作为参数,并返回与产品关联的产品备注以及用于创建新备注的表单:
¥Create a new routes/app/app.notes.$productGid.jsx
file which will take in the productGid as a parameter, and return the product notes associated with the product as well as a form to create a new note:
export default function ProductNotes() {
return (
<></>
);
}
5.1.渲染注释
¥5.1. Render the notes
加载时,路由需要获取产品的备注并显示它们。
¥On load, the route will need to fetch the notes for the product and display them.
在路由中添加 loader
函数:
¥Add a loader
function to the route:
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
组件中映射注释:
¥Map out the notes in the ProductNotes
component, using Polaris components:
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>
</>
);
}
你应该会看到 "暂无备注。"。如果是这样,则说明你走对了路。
¥You should be seeing "No notes yet.". If so, you're on the right track.
5.2.添加表单
¥5.2. Add the form
为了创建新的注释,需要在路由中添加以下几项:
¥A few things need to be added to the route in order to create a new note:
-
在路由中添加
action
函数。¥Add an
action
function to the route. -
创建注意时显示
Toast
通知。¥Display a
Toast
notification when a note is created. -
从
models/note.server.js
导入createNote
函数。¥Import the
createNote
function frommodels/note.server.js
. -
导入
useActionData
和useAppBridge
¥Import the
useActionData
anduseAppBridge
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
函数的表单:
¥Now, you can build out the form that will call the action
function:
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>
</>
);
}
现在你应该能够为产品添加注释并查看其显示内容。
¥You should now be able to add a note to a product and see it displayed.
6. 测试你的路由
¥ Test your route
运行 npm run dev
并导航到 Product Notes
路由。
¥Run npm run dev
and navigate to the Product Notes
route.
-
导航到侧边栏上的“产品说明”
¥Navigate to Product Notes on the sidebar
-
选择产品
¥Select a product
-
添加注释
¥Add a note
-
已验证注释是否正确显示和保存。
¥Verify that notes are displayed and saved correctly.
后续步骤
¥Next Steps
现在你有一个连接到 Prisma Postgres 数据库的 Shopify 应用,你可以:
¥Now that you have a working Shopify app connected to a Prisma Postgres database, you can:
-
使用更多模型和关系扩展你的 Prisma 模式
¥Extend your Prisma schema with more models and relationships
-
添加创建/更新/删除路由和表单
¥Add create/update/delete routes and forms
-
使用 Prisma Postgres 启用查询缓存以获得更好的性能
¥Enable query caching with Prisma Postgres for better performance
更多信息
¥More Info
Stay connected with Prisma
Continue your Prisma journey by connecting with our active community. Stay informed, get involved, and collaborate with other developers:
- Follow us on X for announcements, live events and useful tips.
- Join our Discord to ask questions, talk to the community, and get active support through conversations.
- Subscribe on YouTube for tutorials, demos, and streams.
- Engage on GitHub by starring the repository, reporting issues, or contributing to an issue.