첫 번째 Shopify 앱
이 수업에서는 개발 스토어에 설치된 실행 중인 Shopify 앱을 처음부터 만들어 봅니다. Shopify CLI를 사용하여 프로젝트를 스캐폴드하고, 파일 구조를 이해하며, 개발 서버를 시작하고, Claude Code를 사용하여 첫 번째 기능을 구축합니다.
shopify app init으로 스캐폴딩
Shopify CLI는 인증, 데이터베이스, UI가 이미 구성된 완전한 앱 프로젝트를 생성합니다. 다음을 실행하세요:
# 새 Shopify 앱 생성
cd ~/shopify-projects
shopify app init
# CLI가 프롬프트를 표시합니다:
# ? Your app project name: masterclass-app
# ? Get started building your app:
# > Start with Remix (recommended)
# Start with a blank extension-only app
# Start with a template
Start with Remix를 선택하세요 -- 이것이 Shopify에서 권장하는 프레임워크이며 코스 전체에서 사용할 것입니다.
# 스캐폴딩 완료 후
cd masterclass-app
Shopify는 서버 사이드 렌더링 모델, 중첩 라우팅, 우수한 데이터 로딩 패턴 때문에 Remix를 권장 앱 프레임워크로 선택했습니다. Remix의 action과 loader는 Shopify의 OAuth 흐름과 API 호출에 자연스럽게 매핑됩니다. Shopify Remix 템플릿에는 인증, 세션 관리, App Bridge 통합이 기본으로 포함되어 있습니다.
프로젝트 구조 이해
스캐폴드된 프로젝트는 이해해야 할 특정 구조를 가지고 있습니다. 모든 디렉토리는 명확한 목적을 가지고 있습니다:
masterclass-app/
├── app/ # Remix 애플리케이션
│ ├── routes/ # 페이지 라우트 (파일 기반 라우팅)
│ │ ├── app._index.jsx # 메인 앱 페이지 (인증 후)
│ │ ├── app.jsx # 네비게이션이 포함된 앱 레이아웃
│ │ ├── auth.$.jsx # OAuth 콜백 핸들러
│ │ ├── auth.login/ # 로그인 페이지
│ │ │ ├── route.jsx
│ │ │ └── login.css
│ │ └── webhooks.jsx # Webhook 핸들러
│ ├── shopify.server.js # Shopify API 클라이언트 설정
│ ├── db.server.js # 데이터베이스 연결 (SQLite)
│ └── entry.server.jsx # Remix 서버 진입점
├── extensions/ # Shopify 확장 (UI, Functions 등)
├── prisma/
│ └── schema.prisma # 데이터베이스 스키마
├── public/ # 정적 자산
├── shopify.app.toml # 앱 구성
├── shopify.web.toml # 웹 서버 구성
├── package.json
├── remix.config.js
└── .env # 환경 변수 (자동 생성)
주요 파일 설명
shopify.app.toml -- 앱 매니페스트입니다. 앱의 이름, 스코프(권한), 확장 구성을 정의합니다:
name = "masterclass-app"
client_id = "your-client-id"
application_url = "https://your-tunnel.trycloudflare.com"
embedded = true
[access_scopes]
scopes = "write_products,read_orders"
[webhooks]
api_version = "2026-04"
app/shopify.server.js -- Shopify API 클라이언트입니다. 인증을 설정하고 API 호출을 위한 메서드를 제공합니다:
import "@shopify/shopify-app-remix/adapters/node";
import {
ApiVersion,
AppDistribution,
shopifyApp,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import prisma from "./db.server";
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
apiVersion: ApiVersion.April26,
scopes: process.env.SCOPES?.split(","),
appUrl: process.env.SHOPIFY_APP_URL || "",
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
future: {
unstable_newEmbeddedAuthStrategy: true,
},
});
export default shopify;
export const apiVersion = ApiVersion.April26;
export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
export const authenticate = shopify.authenticate;
export const unauthenticated = shopify.unauthenticated;
export const login = shopify.login;
export const registerWebhooks = shopify.registerWebhooks;
export const sessionStorage = shopify.sessionStorage;
app/routes/app._index.jsx -- Shopify Admin 내에 임베디드 앱으로 렌더링되는 앱의 메인 페이지입니다.
개발 서버 실행
Shopify CLI dev 명령으로 앱을 시작하세요:
shopify app dev
이 단일 명령은 놀라운 양의 작업을 수행합니다:
CLI는 다음을 수행합니다:
localhost:3000에서 Remix 개발 서버 시작- HTTPS 접근을 위한 Cloudflare 터널 생성
- Partner Dashboard에서 앱 URL 업데이트
- 개발 스토어에 앱을 설치하기 위해 브라우저 열기
처음 실행 시 CLI가 데이터베이스 테이블을 생성하고, OAuth 자격증명을 생성하며, 터널을 설정합니다. 이후 실행은 훨씬 빠릅니다.
개발 스토어에 설치
CLI가 브라우저를 열면 Shopify 인증 화면이 표시됩니다. 앱 설치를 클릭하여 요청된 권한을 부여하세요. 설치 후 앱의 메인 페이지가 Shopify Admin 내에 임베디드로 나타납니다.
샘플 콘텐츠와 함께 "Nice work on building a Shopify app"이라는 문구가 포함된 Shopify Polaris UI 페이지가 보여야 합니다.
Claude Code로 첫 번째 기능 구축
이제 흥미로운 부분입니다 -- Claude Code를 사용하여 실제 기능을 구축합니다. 스토어의 총 상품 수와 상태별 분류를 보여주는 상품 수 대시보드를 추가합니다.
프로젝트 디렉토리에서 Claude Code를 열세요:
cd ~/shopify-projects/masterclass-app
claude
Claude Code에 이 프롬프트를 제공하세요:
Update the main app page (app/routes/app._index.jsx) to show a product
dashboard. Use the Shopify Admin GraphQL API to fetch the total number
of products and break them down by status (active, draft, archived).
Display the data using Polaris Cards and Layout components. Include a
loading state.
Claude Code는 다음을 수행합니다:
- 기존
app._index.jsx파일을 읽어 현재 구조를 이해 - Shopify Admin API를 호출하는 Remix
loader함수 작성 - 상태별 상품 수를 가져오는 GraphQL 쿼리 사용
- Polaris
Card,Layout,Text컴포넌트를 렌더링하도록 JSX 업데이트 - 로딩 및 오류 상태 추가
결과 코드는 다음과 같습니다:
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import {
Page,
Layout,
Card,
BlockStack,
Text,
InlineGrid,
} from "@shopify/polaris";
import { authenticate } from "../shopify.server";
export const loader = async ({ request }) => {
const { admin } = await authenticate.admin(request);
const response = await admin.graphql(`
{
activeCount: productsCount(query: "status:active") {
count
}
draftCount: productsCount(query: "status:draft") {
count
}
archivedCount: productsCount(query: "status:archived") {
count
}
totalCount: productsCount {
count
}
}
`);
const data = await response.json();
return json({
products: {
total: data.data.totalCount.count,
active: data.data.activeCount.count,
draft: data.data.draftCount.count,
archived: data.data.archivedCount.count,
},
});
};
export default function Index() {
const { products } = useLoaderData();
return (
<Page title="Product Dashboard">
<Layout>
<Layout.Section>
<InlineGrid columns={4} gap="400">
<Card>
<BlockStack gap="200">
<Text variant="headingSm">Total Products</Text>
<Text variant="heading2xl">{products.total}</Text>
</BlockStack>
</Card>
<Card>
<BlockStack gap="200">
<Text variant="headingSm">Active</Text>
<Text variant="heading2xl" tone="success">
{products.active}
</Text>
</BlockStack>
</Card>
<Card>
<BlockStack gap="200">
<Text variant="headingSm">Draft</Text>
<Text variant="heading2xl" tone="caution">
{products.draft}
</Text>
</BlockStack>
</Card>
<Card>
<BlockStack gap="200">
<Text variant="headingSm">Archived</Text>
<Text variant="heading2xl" tone="subdued">
{products.archived}
</Text>
</BlockStack>
</Card>
</InlineGrid>
</Layout.Section>
</Layout>
</Page>
);
}
파일을 저장하고 브라우저를 확인하세요 -- Remix가 자동으로 핫 리로드되므로 상품 대시보드가 나타납니다.
방금 일어난 일을 주목하세요. 기능을 평문 영어로 설명했고, Claude Code가 GraphQL 쿼리, Remix loader, Polaris UI를 작성했습니다. 기존 shopify.server.js 파일을 읽었기 때문에 authenticate.admin(request)를 사용해야 한다는 것을 알았습니다. Shopify Admin API를 이해하기 때문에 productsCount를 사용해야 한다는 것을 알았습니다. 이것이 에이전틱 코딩의 실제 모습입니다.
체크포인트
다음으로 넘어가기 전에 확인하세요:
-
shopify app dev가 오류 없이 시작됩니다 - 개발 스토어에 앱이 설치되어 있습니다
- 상품 대시보드가 스토어의 실제 데이터를 표시합니다
- Claude Code를 사용하여 앱을 성공적으로 수정했습니다
"App not loaded" 오류: localhost가 아닌 Cloudflare 터널 URL을 사용하고 있는지 확인하세요. 터널은 shopify app dev에 의해 자동으로 생성됩니다.
"Access denied" 오류: shopify.app.toml의 앱 스코프에 read_products가 포함되어 있는지 확인하세요. shopify app dev --reset을 실행하여 재인증하세요.
비어 있는 상품 수: 개발 스토어에 상품이 없는 경우 Shopify Admin으로 이동하여 "상품 추가"를 클릭하여 몇 가지 테스트 상품을 만들거나 대량 가져오기 기능을 사용하세요.
배운 내용
이 수업에서:
- Shopify CLI를 사용하여 Shopify Remix 앱을 스캐폴드했습니다
- 프로젝트 구조와 주요 구성 파일을 이해했습니다
- 터널링과 핫 리로드로 개발 서버를 실행했습니다
- 개발 스토어에 앱을 설치했습니다
- Claude Code를 사용하여 상품 대시보드 기능을 구축했습니다
다음 수업에서는 Shopify Partner 생태계와 Shopify 개발의 비즈니스 측면을 탐구합니다.