dist/codex/bigcommerce-commerce/skills/node-backend/SKILL.md
Build Node.js backends for BigCommerce apps — Express/Fastify servers, OAuth handling, JWT verification, API proxy, webhook processing, session management, and deployment. Use when building the server-side component of BigCommerce apps.
npx skillsauth add orcaqubits/agentic-commerce-claude-plugins node-backendInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fetch live docs:
site:developer.bigcommerce.com apps guide for app development patternshttps://expressjs.com/ or https://fastify.dev/ for framework docsbigcommerce node sample app github for official sample appsBigCommerce Admin (iframe)
↓ OAuth flow / Load callback
Your Node.js Server (Express/Fastify)
↓ API calls
BigCommerce REST/GraphQL APIs
↓ Webhooks
Your Webhook Handler
bc-app/
├── src/
│ ├── index.ts # Server entry point
│ ├── routes/
│ │ ├── auth.ts # OAuth callbacks (install, load, uninstall)
│ │ ├── api.ts # App API routes
│ │ └── webhooks.ts # Webhook handlers
│ ├── services/
│ │ ├── bigcommerce.ts # BigCommerce API client
│ │ └── store.ts # Store/token management
│ ├── middleware/
│ │ ├── auth.ts # Authentication middleware
│ │ └── verify.ts # JWT/webhook verification
│ └── lib/
│ ├── jwt.ts # JWT utilities
│ └── db.ts # Database connection
├── .env # Environment variables
├── package.json
└── tsconfig.json
// routes/auth.ts
app.get('/auth/install', async (req, res) => {
const { code, scope, context } = req.query;
// Exchange code for permanent token
const tokenResponse = await fetch('https://login.bigcommerce.com/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: process.env.BC_CLIENT_ID,
client_secret: process.env.BC_CLIENT_SECRET,
code,
scope,
grant_type: 'authorization_code',
redirect_uri: process.env.BC_AUTH_CALLBACK,
context,
}),
});
const { access_token, user, context: storeContext } = await tokenResponse.json();
const storeHash = storeContext.split('/')[1];
// Store token securely
await saveStoreToken(storeHash, access_token, user);
// Return app UI
res.send('<html>App installed successfully!</html>');
});
app.get('/auth/load', async (req, res) => {
const signedPayload = req.query.signed_payload_jwt as string;
// Verify JWT
const decoded = verifyJwt(signedPayload, process.env.BC_CLIENT_SECRET!);
const storeHash = decoded.sub.split('/')[1];
const userId = decoded.user.id;
// Load store token
const token = await getStoreToken(storeHash);
// Render app UI
res.send(renderApp(storeHash, userId));
});
app.get('/auth/uninstall', async (req, res) => {
const signedPayload = req.query.signed_payload_jwt as string;
const decoded = verifyJwt(signedPayload, process.env.BC_CLIENT_SECRET!);
const storeHash = decoded.sub.split('/')[1];
// Clean up stored data
await deleteStoreData(storeHash);
res.status(200).send('OK');
});
import jwt from 'jsonwebtoken';
function verifyJwt(token: string, secret: string) {
return jwt.verify(token, secret, {
algorithms: ['HS256'],
audience: process.env.BC_CLIENT_ID,
});
}
class BigCommerceClient {
constructor(
private storeHash: string,
private accessToken: string,
) {}
private async request<T>(path: string, options?: RequestInit): Promise<T> {
const url = `https://api.bigcommerce.com/stores/${this.storeHash}${path}`;
const response = await fetch(url, {
...options,
headers: {
'X-Auth-Token': this.accessToken,
'Content-Type': 'application/json',
Accept: 'application/json',
...options?.headers,
},
});
if (response.status === 429) {
const retryAfter = response.headers.get('X-Rate-Limit-Time-Reset-Ms');
// Implement backoff
}
if (!response.ok) {
throw new ApiError(response.status, await response.text());
}
return response.json();
}
async getProducts(params?: Record<string, string>) {
const query = new URLSearchParams(params).toString();
return this.request(`/v3/catalog/products?${query}`);
}
async getOrder(orderId: number) {
return this.request(`/v2/orders/${orderId}`);
}
}
app.post('/webhooks/orders', async (req, res) => {
// Verify webhook (check custom header)
const secret = req.headers['x-webhook-secret'];
if (secret !== process.env.WEBHOOK_SECRET) {
return res.status(401).send('Unauthorized');
}
// Acknowledge immediately
res.status(200).send('OK');
// Process asynchronously
const { scope, data, store_id } = req.body;
await processOrderEvent(store_id, data.id, scope);
});
Store sessions with store hash context:
storeHash and userIdapp.use(session({
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JS access
sameSite: 'none', // Required for iframe embedding
maxAge: 24 * 60 * 60 * 1000,
},
}));
Note: sameSite: 'none' is required because BigCommerce apps load in an iframe.
// Global error handler
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error('Unhandled error:', err);
res.status(500).json({ error: 'Internal server error' });
});
// API error class
class ApiError extends Error {
constructor(public statusCode: number, message: string) {
super(message);
}
}
| Platform | Pros | Setup |
|----------|------|-------|
| Vercel | Easy, auto-scaling, edge functions | vercel deploy |
| Railway | Simple, DB support | railway deploy |
| Render | Free tier, managed services | Git push |
| AWS Lambda | Serverless, pay-per-use | SAM/CDK |
| Heroku | Classic PaaS | git push heroku |
Always set via platform's secret management — never in code:
BC_CLIENT_ID=xxx
BC_CLIENT_SECRET=xxx
BC_AUTH_CALLBACK=https://your-app.com/auth/install
BC_LOAD_CALLBACK=https://your-app.com/auth/load
BC_UNINSTALL_CALLBACK=https://your-app.com/auth/uninstall
SESSION_SECRET=xxx
WEBHOOK_SECRET=xxx
DATABASE_URL=xxx
sameSite: 'none' + secure: true cookies for iframe embeddingFetch the BigCommerce app development guide and Node.js framework docs for exact callback parameters, JWT structure, and deployment patterns before implementing.
development
Build with Spree's headless Next.js storefront — the official `spree/storefront` repo (Next.js 16 App Router with Server Actions and Turbopack, React 19 Server Components, Tailwind CSS 4, TypeScript 5, `@spree/sdk`, Sentry), server-only auth (httpOnly JWT cookies + publishable key), MeiliSearch faceted catalog, one-page checkout with Apple/Google Pay/Klarna/Affirm/SEPA, multi-region market routing, GA4 + JSON-LD SEO, and Vercel/Docker deployment. Use when forking or customizing the storefront, or evaluating headless adoption.
tools
Build Spree extensions as Rails engines — gem scaffolding, `bin/rails g spree:extension`, mounting routes/migrations/assets, the modern `prepend` decorator pattern (`*_decorator.rb` with `self.prepended(base)`), generators (`spree:model_decorator`, `spree:controller_decorator`), the four customization surfaces in preference order (Events > Webhooks > Dependencies > Decorators), Spree::Dependencies for swapping service objects, gem release/versioning, and the deprecated Deface engine. Use when building a reusable Spree extension or adding non-trivial customization to an app.
development
Build with Spree's event bus and Webhooks 2.0 — `Spree::Events` publication, `Spree::Subscriber` DSL with `subscribes_to` and `on`, wildcard matching, lifecycle events (`{model}.created/.updated/.deleted` via `publishes_lifecycle_events`), the canonical event catalog (order.*, payment.*, shipment.*, product.*), Webhooks 2.0 endpoints, HMAC-SHA256 signing (`X-Spree-Webhook-Signature`), exponential-backoff retries, and Sidekiq job orchestration. Use when wiring event-driven business logic, building webhook consumers, or replacing ActiveSupport callback chains.
tools
Cross-cutting Spree development patterns — the customization preference hierarchy (Events > Webhooks > Dependencies > Decorators), `Spree::Dependencies` service-object swapping, the `_decorator.rb` + `prepend` + `self.prepended` idiom, idempotent subscribers and webhook receivers, multi-store scoping discipline, prefixed IDs, calculator polymorphism (shipping/promotion/tax share the base), service-object composition with `dry-monads` or simple results, why to avoid `class_eval` reopening and Deface, and Spree-on-Rails idioms (Hotwire/Turbo Stimulus, ActiveStorage, Action Cable, Sidekiq). Use when designing the architecture of a Spree extension or solving cross-cutting concerns.