Skip to main content

如何将 Prisma Postgres 与 Shopify 结合使用

25 min

介绍

¥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 contain shopify)

进入 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.

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 模式中添加一个新的 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.

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 更新到最新版本。运行:

¥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:

  1. 打开

    ¥Open the :

    • 登录并找到你新创建的数据库项目。

      ¥Log in and find your newly created database project.

  2. 设置你的数据库凭据:

    ¥Set up your database credentials:

    • 在侧边栏中,点击“数据库”,然后选择“设置”。

      ¥In the sidebar, click Database, then select Setup.

    • 选择“现有项目”,然后按“生成数据库凭据”。

      ¥Choose Existing project and press Generate database credentials.

  3. 配置你的环境:

    ¥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=..."
  4. 应用你的数据库模式:

    ¥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.

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 文件中。

¥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:

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 的函数:

¥To enable users to add new notes to your database, let's create a function in notes.server.js that uses 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. 创建你的布局路由

¥ 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:

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 函数,以便用户选择产品:

¥Next, create the selectProduct function and a Button to let the user pick a product:

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 渲染提供了渲染嵌套路由的功能。在将渲染 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:

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 添加到侧边栏

¥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:

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. 创建你的产品备注路由

¥ 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:

app/routes/app/app.notes.$productGid.jsx
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:

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 组件中映射注释:

¥Map out the notes in the ProductNotes component, using Polaris components:

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>
</>
);
}

你应该会看到 "暂无备注。"。如果是这样,则说明你走对了路。

¥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 from models/note.server.js.

  • 导入 useActionDatauseAppBridge

    ¥Import the useActionData and useAppBridge

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 函数的表单:

¥Now, you can build out the form that will call the action function:

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>
</>
);
}

现在你应该能够为产品添加注释并查看其显示内容。

¥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:

We genuinely value your involvement and look forward to having you as part of our community!