Skip to content
/ scf Public

CLI to scaffold Shopify apps with Cloudflare Workers (D1, KV, R2)

License

Notifications You must be signed in to change notification settings

ilrein/scf

Repository files navigation

scf

Scaffold Shopify apps that run on Cloudflare Workers.

npx scf init my-app

Why?

The default Shopify CLI generates apps for Node.js. This CLI generates apps optimized for Cloudflare's edge network:

Node.js (default) Cloudflare Workers
Cold starts 200-500ms ~0ms
Global latency Single region Edge everywhere
Database Prisma + SQLite D1 (SQLite at edge)
Sessions Prisma adapter KV (global)
Scaling Manual Automatic
Cost $5+/mo minimum Free tier generous

Quick Start

# Create a new app
npx scf init my-shopify-app
cd my-shopify-app

# Install dependencies
npm install

# Configure credentials
cp .dev.vars.example .dev.vars
# Edit .dev.vars with your Shopify API credentials

# Set up Cloudflare resources (D1 database, KV namespace)
npm run setup

# Start development
npm run dev

What You Get

my-shopify-app/
├── app/
│   ├── db/
│   │   ├── client.ts         # Drizzle + D1
│   │   └── schema.ts         # Your database schema
│   ├── routes/
│   │   ├── _index.tsx        # Main app page
│   │   ├── auth.$.tsx        # OAuth callback
│   │   ├── auth.login/       # Login page
│   │   └── webhooks.*.tsx    # Webhook handlers
│   └── shopify.server.ts     # Shopify configuration
├── workers/
│   └── app.ts                # Cloudflare Worker entry
├── wrangler.json             # Cloudflare configuration
└── shopify.app.toml          # Shopify app configuration

Stack

  • Runtime: Cloudflare Workers
  • Framework: React Router v7
  • Database: Cloudflare D1 + Drizzle ORM
  • Sessions: Cloudflare KV
  • UI: Shopify Polaris

Commands

Development

npm run dev          # Start with Shopify CLI (recommended)
npm run dev:wrangler # Start with Wrangler only

Database

npm run db:generate  # Generate migration from schema changes
npm run db:migrate   # Apply migrations locally
npm run db:studio    # Open Drizzle Studio

Deployment

npm run deploy       # Deploy everything (migrations + Worker + Shopify)

Or step by step:

npm run db:migrate:prod  # Apply migrations to production D1
npm run deploy:cf        # Deploy to Cloudflare Workers
npm run deploy:shopify   # Deploy Shopify app config

Configuration

Environment Variables

Local development (.dev.vars):

SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SHOPIFY_APP_URL=https://your-tunnel.trycloudflare.com
SCOPES=write_products,read_products

Production (Cloudflare secrets):

wrangler secret put SHOPIFY_API_KEY
wrangler secret put SHOPIFY_API_SECRET
wrangler secret put SHOPIFY_APP_URL
wrangler secret put SCOPES

Cloudflare Resources

After running npm run setup, update wrangler.json with your resource IDs:

{
  "d1_databases": [{
    "binding": "DB",
    "database_id": "your-d1-id"
  }],
  "kv_namespaces": [{
    "binding": "SESSION_STORAGE", 
    "id": "your-kv-id"
  }]
}

Adding Database Tables

Edit app/db/schema.ts:

import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";

export const products = sqliteTable("products", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  shopId: text("shop_id").notNull(),
  title: text("title").notNull(),
  syncedAt: integer("synced_at", { mode: "timestamp" }),
});

Then generate and apply the migration:

npm run db:generate
npm run db:migrate

Route Pattern

Every route that needs Shopify authentication follows this pattern:

import type { LoaderFunctionArgs } from "react-router";
import { initializeShopify, authenticate } from "../shopify.server";
import { getDbFromContext, type CloudflareEnv } from "../db/client";

export const loader = async ({ request, context }: LoaderFunctionArgs) => {
  const env = (context as { cloudflare: { env: CloudflareEnv } }).cloudflare.env;
  
  // Initialize Shopify (required before authenticate)
  initializeShopify(env);
  
  // Authenticate
  const { session, admin } = await authenticate.admin(request);
  
  // Get database
  const db = getDbFromContext(context as { cloudflare: { env: CloudflareEnv } });
  
  // Query with shop isolation
  const data = await db.query.products.findMany({
    where: (products, { eq }) => eq(products.shopId, session.shop),
  });

  return { data };
};

License

MIT

About

CLI to scaffold Shopify apps with Cloudflare Workers (D1, KV, R2)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published