Lab19 logo

Lab19

Shopify price automation platform

Built a custom price automation system for an Italian Shopify store. The system eliminates manual price review workflows, by syncing all products prices with real market data. Prices are synced through scheduled pipelines, ensuring daily up-to-date prices.
Note: This is a custom solution built for a private client.

700+ Products
9000+ Variants
Daily Price refresh
2026 In production since
3 Monorepo services
4 Cron schedules
3-tier Variant mapping

Highlights

Three-tier variant mapping

Shopify variants are matched to provider data through a fallback chain.

Lookup cascade

Barcode / GTIN lookup
exact match against provider catalog
SKU + SIZE match
structured size normalization
Product name fallback
fuzzy fallback
manual_review
surfaced in dashboard for manual intervention

Priority-based markup system

Pricing is resolved through a priority system, from product-level overrides down to global defaults.

Priority cascade — highest wins

Product-level override
highest priority · set per product
MarkupRule
brand / category / gender · highest priority rule wins
Global default
fallback for all products

Scheduled pricing workflows

Scheduled workflows keep product data and pricing in sync.

1. Daily catalog sync
2. Weekly provider pricing update
3. Hottest products price refresh
4. Trending products refresh

Price history & audit trail

Every price change is tracked with full context.

Price history record

run_id groups changes from the same job run
source pricing source (manual, bulk update, scheduled jobs)
variant_id Shopify variant reference
old/new_price before and after values
created_at timestamp

Architecture

Three independent services in a monorepo, each with a distinct responsibility and sharing the same PostgreSQL database via Prisma.

Next.js Frontend App Router · Tailwind CSS
Discord OAuth2 · Admin access
Shopify App Embedded · Webhook proxy
Express Backend Cron Jobs · Shopify GraphQL API
PostgreSQL Supabase · Prisma · Pooled connections
External Market Data Fetch real time prices
Shopify Store Webhook source · Source of truth

Key Challenges

Cross-platform size normalization

Shopify stores sizes as free-text while external providers use structured size arrays.

Approach

Sizes are normalized into a shared format (region labels stripped, formats standardized) before comparison, ensuring consistent matching across sources.

Edge cases
  • ·Collectibles have no size, solved by matching by product identity.
  • ·US 8.5 and EU 42 are distinct types in provider data, the correct type is resolved before comparison.
  • ·Normalization runs on both sides of the comparison, to ensure both sides are normalized.

Mapping drift

Handling inconsistent product data without breaking the matching pipeline.

Approach

Each mapping is persisted with a status (mapped, not_found, manual_review). Failed items are skipped on subsequent runs, avoiding redundant API calls.

Edge cases
  • ·A product deleted from provider transitions to status not_found and excluded from future runs.
  • ·Re-mapping can be triggered manually from the dashboard after fixing the Shopify product metadata.

Windows server deployment

Running multiple Node.js services on a Windows VPS with Cloudflare, Caddy and PM2.

Approach

Caddy handles HTTPS via Cloudflare origin certificates, while PM2 runs and monitors all services. A Docker setup is available as a fallback for portability.

Edge cases
  • ·Log rotation prevents disk exhaustion on the host.
  • ·All traffic is proxied through Cloudflare, blocking direct origin access.

Complete Stack

Frontend
Next.jsTypeScriptTailwind CSSShadcn UI
Next.jsTypeScriptTailwind CSS
Backend
ExpressPostgreSQLPrismaCron JobsGraphQL
PostgreSQLPrismaGraphQL
Infrastructure
SupabaseCaddyDockerPM2Cloudflare
SupabaseDockerCloudflare

What I'd Do Differently

Solve price atomicity upfront

The backend writes to Shopify GraphQL first, then commits to PostgreSQL. If the DB transaction fails after the Shopify mutation succeeds, prices silently drift. I'd implement an outbox pattern or idempotency keys from day one to guarantee consistency across both systems.

Remove the hardcoded batch limit

weeklyPriceUpdate.js has a take: 10 limit that was hardcoded during development and never replaced. At scale it silently under-processes the catalog. I'd replace it with a paginated processor and a configurable chunk size from the start.

Shared Prisma workspace package

Backend and Shopify App each maintain separate Prisma instances pointing to the same database. Schema changes require migrating and generating in both services independently. I'd extract the shared schema into a dedicated monorepo package to avoid the duplication.

Need something like this?

I build custom internal tools and automation systems tailored to specific business workflows from pricing pipelines to inventory dashboards.

Get in touch