Skip to content

ashishnitw/shopify-utils

Repository files navigation

@ashishnitw/shopify-utils

Reusable Shopify app utilities — Polaris components, React hooks, server middleware, webhook handlers, and helper functions.

Installation

Add .npmrc to your project root:

@ashishnitw:registry=https://npm.pkg.github.com

Then install:

npm install @ashishnitw/shopify-utils

Package Structure

@ashishnitw/shopify-utils
├── components/     # Polaris-based React UI components (ESM)
├── hooks/          # React hooks for Shopify apps (ESM)
├── server/         # Server-side utilities (CommonJS)
│   ├── api/        # Shopify REST & GraphQL clients, bulk operations
│   ├── middleware/  # Auth verification, session management, error handling, rate limiting, CSP
│   └── webhooks/   # Webhook verification, registration, handlers, GDPR
└── utils/          # Shared helpers (CommonJS) — logging, currency, dates, GraphQL, validators, errors

Import Paths

// Root — server + utils combined (CommonJS)
const { shopifyGet, formatMoney, createLogger } = require('@ashishnitw/shopify-utils');

// Specific sub-packages
import { IndexTable, Page, Toast } from '@ashishnitw/shopify-utils/components';
import { useFetch, usePagination, useForm } from '@ashishnitw/shopify-utils/hooks';
const { shopifyGraphQL, createBulkQuery } = require('@ashishnitw/shopify-utils/server/api');
const { verifyShopifySession, shopifyCSP } = require('@ashishnitw/shopify-utils/server/middleware');
const { createWebhookRouter, createGDPRRouter } = require('@ashishnitw/shopify-utils/server/webhooks');
const { formatMoney, timeAgo, isValidShopDomain } = require('@ashishnitw/shopify-utils/utils');

Components

All components are React (JSX) and wrap Shopify Polaris primitives.

Component Description
IndexTable / ConfigurableIndexTable Data table with sorting, selection, bulk actions, pagination
Page / ShopifyPage Page wrapper with automatic SkeletonPage loading state
Toast / ToastNotification Toast notifications with ToastProvider context
ConfirmationModal Confirm/cancel modal dialog
EmptyState / ShopifyEmptyState Empty state with illustration and CTA
SkeletonPage / SkeletonPageLayout Skeleton loading layout
StatusBadge Status badge with semantic colour mapping
Tabs / TabbedNavigation Tabbed navigation with content panels
SearchBar Debounced search input
ResourceList / ConfigurableResourceList Resource list with sorting, filtering, bulk actions
DateRangePicker Date range picker with preset ranges and calendar
BannerNotification Dismissible banner with actions
Pagination / CursorPagination Cursor-based pagination controls

Example

import { IndexTable, Page, Toast, SearchBar } from '@ashishnitw/shopify-utils/components';

function OrdersPage() {
  return (
    <Page title="Orders" loading={false}>
      <SearchBar value={query} onChange={setQuery} placeholder="Search orders..." />
      <IndexTable
        resourceName={{ singular: 'order', plural: 'orders' }}
        items={orders}
        headings={[{ title: 'Order' }, { title: 'Customer' }, { title: 'Total' }]}
        renderRow={(order) => (
          <IndexTable.Row id={order.id}>
            <IndexTable.Cell>{order.name}</IndexTable.Cell>
            <IndexTable.Cell>{order.customer}</IndexTable.Cell>
            <IndexTable.Cell>{order.total}</IndexTable.Cell>
          </IndexTable.Row>
        )}
      />
    </Page>
  );
}

Hooks

Hook Description
useFetch Fetch with abort, retry, error handling, and optimistic updates
useAuthenticatedFetch useFetch with automatic Shopify session token injection
usePagination Cursor-based pagination state management
useDebounce / useDebouncedCallback Debounce values or callbacks
useToggle Boolean toggle state
useLocalStorage Persistent state backed by localStorage
useToast Toast notification queue management
useForm Form state, validation, dirty tracking, and submission
useBulkActions Multi-select / bulk action state for lists
useAppBridge Shopify App Bridge helpers (redirect, toast, modal, session token)

Example

import { useFetch, usePagination, useDebounce } from '@ashishnitw/shopify-utils/hooks';

function ProductList() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);
  const pagination = usePagination({ initialPageSize: 20 });
  const { data, loading } = useFetch(
    `/api/products?q=${debouncedQuery}&after=${pagination.cursors.after}`
  );

  useEffect(() => {
    if (data?.pageInfo) pagination.setCursors(data.pageInfo);
  }, [data]);

  return (/* render products with pagination controls */);
}

Server Utilities

API Client (server/api)

const {
  shopifyGet, shopifyPost, shopifyPut, shopifyDelete,
  shopifyGraphQL,
  createShopifyClient, createGraphQLClient,
  runBulkQuery, createBulkQuery, pollBulkOperation,
} = require('@ashishnitw/shopify-utils/server/api');

// REST
const products = await shopifyGet(shop, accessToken, 'products.json');

// GraphQL
const result = await shopifyGraphQL(shop, accessToken, `{
  products(first: 10) {
    edges { node { id title } }
  }
}`);

// Bulk operations
const allProducts = await runBulkQuery(shop, accessToken, `{
  products { edges { node { id title } } }
}`);

Middleware (server/middleware)

const {
  verifyShopifyRequest,   // HMAC query-param verification
  verifyShopifySession,   // JWT session token verification
  verifyShopifyProxy,     // App proxy signature verification
  shopifyErrorHandler,    // Express error handler
  notFoundHandler,        // 404 handler
  shopifyRateLimiter,     // Rate limiting (memory or Redis store)
  shopifyCSP,             // Content Security Policy for embedded apps
  createSessionManager,   // MongoDB-backed session storage
} = require('@ashishnitw/shopify-utils/server/middleware');

// Express usage
app.use(shopifyCSP({ apiKey: process.env.SHOPIFY_API_KEY }));
app.use('/api', verifyShopifySession());
app.use('/api', shopifyRateLimiter({ windowMs: 60000, maxRequests: 100 }));
app.use(shopifyErrorHandler());
app.use(notFoundHandler());

Webhooks (server/webhooks)

const {
  createWebhookRouter,
  createGDPRRouter,
  registerWebhooks,
  syncWebhooks,
  WEBHOOK_TOPICS,
} = require('@ashishnitw/shopify-utils/server/webhooks');

// Mount webhook handler
app.use('/webhooks', createWebhookRouter({
  'app/uninstalled': async (shop, payload) => {
    await cleanupShopData(shop);
  },
  'orders/create': async (shop, payload) => {
    await processNewOrder(shop, payload);
  },
}));

// Mount mandatory GDPR endpoints
app.use('/webhooks/gdpr', createGDPRRouter({
  onCustomerDataRequest: async (shop, payload) => { /* ... */ },
  onCustomerRedact: async (shop, payload) => { /* ... */ },
  onShopRedact: async (shop, payload) => { /* ... */ },
}));

// Register webhooks on app install
await registerWebhooks(shop, accessToken, [
  { topic: 'app/uninstalled', address: 'https://myapp.com/webhooks' },
  { topic: 'orders/create', address: 'https://myapp.com/webhooks' },
]);

Utils

Utility Key Exports
Logger createLogger(namespace), defaultLogger
Currency formatMoney, parseMoney, centsToDollars, dollarsToCents, SHOPIFY_CURRENCIES
Dates formatShopifyDate, parseShopifyDate, timeAgo, getDateRange, daysBetween
GraphQL buildGraphQLQuery, buildMutation, extractNodes, extractPageInfo, paginateQuery
Validators isValidShopDomain, sanitizeShopDomain, isValidShopifyGid, parseShopifyGid, toShopifyGid
Constants SHOPIFY_AUTH_SCOPES, SHOPIFY_ORDER_STATUS, SHOPIFY_PRODUCT_STATUS, SHOPIFY_API_VERSIONS
Errors ShopifyError, ShopifyAuthError, ShopifyRateLimitError, ShopifyNotFoundError, wrapError

Example

const {
  formatMoney, timeAgo, isValidShopDomain,
  buildGraphQLQuery, extractNodes,
  createLogger, ShopifyAuthError,
} = require('@ashishnitw/shopify-utils/utils');

formatMoney(1999, 'USD');              // "$19.99"
timeAgo(new Date('2024-01-01'));       // "1y ago"
isValidShopDomain('my-store.myshopify.com'); // true

const logger = createLogger('my-app');
logger.info('App started', { shop: 'example.myshopify.com' });

Environment Variables

The server utilities expect these environment variables:

Variable Required Description
SHOPIFY_API_KEY Yes Shopify app API key
SHOPIFY_API_SECRET Yes Shopify app API secret
SHOPIFY_HOST_NAME No App hostname (default: localhost)
SHOPIFY_API_VERSION No API version (default: 2025-04)
MONGO_URI For sessions MongoDB connection URI
MONGO_DB_NAME No Database name (default: shopify_app)
LOG_LEVEL No Log level: error, warn, info, debug, trace (default: info)

Publishing

# Authenticate with GitHub Packages
echo "//npm.pkg.github.com/:_authToken=YOUR_TOKEN" >> ~/.npmrc

# Publish
npm publish

License

MIT

About

Bunch of utility codes I use in my shopify apps

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors