メインコンテンツにスキップ

ShopifyでPrisma Postgresを使用する方法

25分

はじめに

Shopifyは、eコマースストア構築のための人気プラットフォームです。このガイドでは、ShopifyアプリをPrisma Postgresデータベースに接続し、商品の内部メモを作成する方法を説明します。

前提条件

1. プロジェクトをセットアップする

注意

Shopify CLIがインストールされていない場合は、npm install -g @shopify/cliでインストールできます。

まず、Shopify CLIを使用して新しいShopifyアプリを初期化します。

shopify app init

セットアップ中に、アプリのカスタマイズを求めるプロンプトが表示されます。心配いりません。これらの推奨オプションに従うだけで、すぐに始められ、アプリが成功するようにセットアップされます。

情報
  • アプリの構築を開始する: Build a Remix app (recommended)
  • Remixテンプレートの言語は何にしますか: JavaScript
  • アプリ名: prisma-store (名前にshopifyは含められません)

prisma-storeディレクトリに移動します。

cd prisma-store

2. Prismaをセットアップする

Prismaはプロジェクトにプリインストールされていますが、最新バージョンに更新しておきましょう。これにより、最新の機能、改善点、そしてアプリを構築する上での最高の体験にアクセスできます。

Prisma Postgresデータベースに切り替えるため、prismaディレクトリ内のmigrationsフォルダーとdev.sqliteファイルを削除してください。

RemixとPrisma Postgresで動作させるには、schema.prismaファイルのいくつか更新する必要があります。

  • 新しいprisma-clientジェネレーターに切り替えます。
  • プロバイダーを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
}

アプリが各製品のメモを保存できるように、新しいProductNoteモデルをPrismaスキーマに追加しましょう。

このモデルを使用すると、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. データベーススキーマを適用する
    • 以下のコマンドを実行してテーブルを作成し、データベースの準備をします。
    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ファイルには2つの関数が含まれます。

  • 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.jsprisma.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ルートは表示されません。これを修正するには、ProductNotesLayoutapp.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通知を表示します。
  • createNote関数をmodels/note.server.jsからインポートします。
  • 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ルートに移動します。

  • サイドバーのProduct Notesに移動します。
  • 製品を選択します。
  • メモを追加します。
  • メモが正しく表示され、保存されることを確認します。

次のステップ

ShopifyアプリをPrisma Postgresデータベースに接続して動作するようになったので、以下を行うことができます。

  • Prismaスキーマをさらに多くのモデルとリレーションで拡張する
  • 作成/更新/削除ルートとフォームを追加する
  • パフォーマンス向上のためにPrisma Postgresでクエリキャッシュを有効にする

詳細情報


Prismaとつながる

Prismaの旅を続けるために、以下とつながりましょう。 私たちの活発なコミュニティ。情報収集、参加、他の開発者との協力ができます。

  • Xでフォローする アナウンス、ライブイベント、役立つヒントをお届けします。
  • Discordに参加する 質問したり、コミュニティと話したり、会話を通して活発なサポートを受けたりできます。
  • YouTubeを購読する チュートリアル、デモ、ストリームを視聴できます。
  • GitHubで関わる リポジトリにスターを付けたり、問題を報告したり、問題に貢献したりできます。
皆様のご参加を心より歓迎し、コミュニティの一員となることを楽しみにしています!

© . All rights reserved.