Collector marketplace on Node.js and React
Pokémon Card Trading Platform with Shopify Integration
A trading and catalogue platform for card collectors, integrated with Shopify for commerce and built to handle inconsistent third-party card data.
- Commerce
- Shopify Admin API
- Catalogue
- Normalised card data
- Sync
- Idempotent webhooks
- Status
- Live demo available
Role
Full-stack development and integration
Timeline
2026
Stack
Node.js, React, MongoDB, Shopify API
Overview
A platform for trading-card collectors: browse a normalised card catalogue, track a personal collection, and buy through a Shopify-backed storefront. React on the front end, Node.js and MongoDB behind it, with Shopify handling checkout, payment and fulfilment.
The problem
Two things make this harder than a standard catalogue app. First, trading-card data from third-party sources is genuinely messy — the same card appears across sets, printings, languages and condition grades, with inconsistent naming between providers. Second, commerce state lives in Shopify while catalogue and collection state lives in the application database, and those two have to agree.
Architecture
Shopify is the source of truth for anything involving money: products, prices, inventory, orders. The application database owns catalogue metadata and user collections. A single reference field links a catalogue entry to its Shopify product.
That split matters. Reimplementing checkout, tax or payment handling would have been substantial work with real compliance surface, and Shopify does it properly. The integration boundary is deliberately narrow: the app never writes prices or inventory, only reads them.
Technical challenges
Normalising inconsistent card data
Card identity is not a single field. The same card differs by set, collector number, printing variant, language and grade, and source data spells all of those inconsistently.
The solution was a deterministic composite key derived from the stable identifying attributes, with a normalisation step applied at ingest — casing, punctuation and known naming variants folded before hashing. Re-ingesting the same source data produces the same keys, so imports are repeatable rather than duplicating the catalogue.
Keeping Shopify and the catalogue in sync
Polling the Shopify API for changes is slow and burns rate limit. Webhooks are the right mechanism, but they arrive at-least-once and occasionally out of order, so a naive handler can apply a stale update over a fresh one.
Handlers are idempotent and version-aware: each applies only if the incoming payload is newer than what is stored, and delivery IDs are recorded so replays are ignored. Webhook signatures are verified before the body is parsed.
// Verify before trusting anything in the payload.
function verifyShopifyWebhook(raw: Buffer, header: string) {
const digest = crypto
.createHmac("sha256", process.env.SHOPIFY_WEBHOOK_SECRET!)
.update(raw)
.digest("base64");
return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(header));
}Using a timing-safe comparison rather than `===` matters here — a plain string comparison leaks signature information through response timing.
Search across a large catalogue
Collectors search by partial name, set, and attributes together, and expect it to feel instant. Regex scans over the collection did not hold up as the catalogue grew.
Moving to a compound text index with the common filter fields included let MongoDB serve the frequent query shapes from the index, and paginating by indexed cursor rather than by skip kept later pages as fast as the first.
Outcome
The platform runs as a live demo with a working catalogue, collection tracking and Shopify-backed purchasing. The architectural decision worth highlighting is the narrow integration boundary — by refusing to duplicate commerce state locally, an entire class of reconciliation bug never existed.
Screens




Published by Cenedy Udoy Palma.