@ashishnitw/shopify-utils
v0.0.9
Published
Reusable Shopify app utilities
Readme
@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.comThen install:
npm install @ashishnitw/shopify-utilsPackage 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, errorsImport Paths
// Root — server + utils combined (CommonJS)
const { shopifyGet, formatMoney, createLogger } = require('@ashishnitw/shopify-utils');
// Specific sub-packages
import { IndexTable, Page } from '@ashishnitw/shopify-utils/components';
import { useFetch, usePagination, useForm } from '@ashishnitw/shopify-utils/hooks';
const { shopifyGraphQL, createBulkQuery } = require('@ashishnitw/shopify-utils/server/api');
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 |
| ConfirmationModal | Confirm/cancel modal dialog |
| EmptyState | Empty state with illustration and CTA |
| SkeletonPage / SkeletonPageLayout | Skeleton loading layout |
| SearchBar | Debounced search input |
| DateRangePicker | Date range picker with preset ranges and calendar |
| ResourcePicker | App Bridge resource picker for products / collections / variants |
| Pagination / CursorPagination | Cursor-based pagination controls |
Example
import { IndexTable, Page, 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 |
| useForm | Form state, validation, dirty tracking, and submission |
| useBulkActions | Multi-select / bulk action state for lists |
| useAppBridge | Shopify App Bridge helpers (redirect, 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 } } }
}`);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 publishLicense
MIT
