Skip to Content
Apps & ServicesStorefront Proxy

Storefront Proxy

Overview

The Storefront Proxy (backend-store.siyahfy.com) is the intelligent reverse proxy and domain routing layer that sits between incoming HTTP requests and the Next.js theme servers powering vendor storefronts. Every request to a vendor’s online store — whether through a platform subdomain (*.site.siyahfy.com), a custom domain (example.com), or a development domain (*.localhost) — passes through this service first.

It resolves the correct theme server for each store by querying PostgreSQL, caches the result in Redis (60-second TTL), and proxies the request to the appropriate Next.js theme instance. It also handles domain verification, SSL certificate provisioning via Traefik, and renders branded error pages when something goes wrong.

Runtime: Node.js 20 with Express.js Port: 5014 Deployment: Docker container behind Traefik edge router

Tech Stack

TechnologyVersionPurpose
Express.js4.21.2HTTP server framework
http-proxy-middleware3.0.5Reverse proxy to theme servers
PostgreSQL (pg)8.16.3Domain resolution and store lookup
Redis5.10.0Theme resolution caching (60s TTL)
Winston3.18.3Structured application logging
Morgan1.10.0HTTP access logging
EJS3.1.9Error page template rendering
JSON Web Token9.0.2Vendor authentication for API routes
js-yaml4.1.1Traefik dynamic config generation
cookie-parser1.4.7Cookie parsing for auth forwarding
cors2.8.5Cross-origin request handling
dotenv17.2.3Environment variable management

Folder Structure

backend-store.siyahfy.com/ ├── server.js # Main Express server with proxy middleware ├── theme-resolver.js # Core theme URL resolution logic (DB queries) ├── app-logger.js # Winston logger with console.log/error override ├── logger.js # Morgan HTTP access logger configuration ├── config/ │ ├── db.js # PostgreSQL connection pool │ └── redis.js # Redis client setup (production: redis://redis:6379) ├── routes/ │ ├── index.js # Public API endpoints (/api/*) │ └── vendor-store.js # Authenticated vendor endpoints (/api/vendor/*) ├── utils/ │ └── traefik-domain.js # Dynamic Traefik config YAML generation ├── views/ # EJS error page templates │ ├── 404.ejs # Store not found │ ├── domain-not-found.ejs # Domain not configured │ ├── domain-pending.ejs # Domain pending verification │ └── theme-starting.ejs # Theme server booting (auto-refresh 3s) ├── logs/ │ ├── access.log # Morgan combined format HTTP logs │ └── server.log # Winston JSON application logs ├── stores.json # Static store configuration reference └── package.json

How It Works

Request Flow

Domain Resolution Logic (theme-resolver.js)

The resolver handles three domain types with distinct lookup strategies:

1. Custom Domain Flow (e.g., pwscoding.co.in)

  • Query store_domains table by exact domain match
  • Verify verified = true and disabled = false
  • Extract store_id and vendor_id from the matched row

2. Platform Slug Flow (e.g., codingninja.site.siyahfy.com)

  • Extract slug from subdomain
  • Query stores table by store_slug
  • Check for a primary custom domain (is_main = true AND is_primary = true AND verified = true)
  • If a primary custom domain exists, the proxy will 302-redirect there

3. Development Flow (e.g., myshop.localhost)

  • Extract slug by stripping .localhost
  • Same as platform slug flow

After resolving the store, the resolver fetches:

  • Subscription plan from vendor_subscriptions (basic/pro/growth)
  • Active theme from vendor_theme_purchases (theme_key where status = ‘active’)
  • Theme URL via getThemeUrl() — in production this resolves to the internal theme server; in development to http://{slug}.localhost:3002

Database Tables

The proxy relies on four key database tables:

stores

ColumnTypeDescription
store_idintegerPrimary key
vendor_idintegerForeign key to vendor
store_slugvarcharUnique slug for *.site.siyahfy.com
store_namevarcharDisplay name
api_keyjsonbArray of API key objects
domain_statusvarcharDomain verification status

store_domains

ColumnTypeDescription
domainvarcharCustom domain name (unique)
store_idintegerFK to stores
vendor_idintegerFK to vendor
verifiedbooleanDomain ownership verified
disabledbooleanDomain disabled by admin
is_mainbooleanMain domain for store
is_primarybooleanPrimary custom domain (redirect target)
txt_tokenvarcharTXT record for verification

vendor_subscriptions

ColumnTypeDescription
vendor_idintegerFK to vendor
store_namevarcharStore slug
plan_namevarcharPlan level (basic/pro/growth)

vendor_theme_purchases

ColumnTypeDescription
vendor_idintegerFK to vendor
store_namevarcharStore slug
theme_keyvarcharTheme identifier
statusvarcharPurchase status (active/pending/free)

API Endpoints

Public API (/api/*)

GET /api/get-theme-env?subdomain={slug|domain}

Fetch theme server environment for a given store. Used by theme servers to get their API token and store metadata.

Query Parameters:

  • subdomain — Store slug (e.g., myshop) or custom domain (e.g., example.com)

Response:

{ "domain": "myshop.site.siyahfy.com", "domain_type": "platform", "status": "verified_or_platform", "api_key": "jwt_token_here", "phone": "9876543210", "store_name": "My Shop", "email": "[email protected]" }

The endpoint joins store_domains, stores, and vendors_registration to build the response. For platform domains, verification is skipped; for custom domains, verified = true is required.

GET /api/logs?lines={N}

Retrieve HTTP access logs (Morgan combined format). Returns up to 5000 lines from logs/access.log.

GET /api/server-logs?lines={N}

Retrieve server application logs (Winston format). Returns up to 5000 lines from logs/server.log.

Authenticated Vendor API (/api/vendor/*)

All vendor endpoints require JWT authentication via tokenVendorsSagartech cookie or Authorization: Bearer header.

GET /api/vendor/check-domain?domain={domain}&store_id={id}&store_slug={slug}

Check domain availability, generate DNS records, and create Traefik config for automatic SSL.

Response includes:

  • A record pointing to server IP (95.216.248.241)
  • CNAME record for www subdomain
  • TXT verification token (siyahfy-verification=...)
  • RDAP registrar lookup result

POST /api/vendor/verify-domain

Verify domain ownership and mark as verified in store_domains.

DELETE /api/vendor/remove-domain

Remove custom domain from database and delete associated Traefik config.

GET /api/vendor/list-domains

List all custom domains for the authenticated vendor across all stores.

POST /api/vendor/switchTheme

Switch the active theme for a store. Sets all themes to pending status, then activates the selected one. Refreshes the Redis cache immediately.

Caching Strategy

Redis Theme Cache

  • Cache key: theme:{lookupKey} (slug or custom domain)
  • TTL: 60 seconds
  • Cached payload:
{ "target": "https://preview-01.site.siyahfy.com", "api_key": null, "domain_status": "verified", "main_domain": "example.com" }
  • Cache invalidation: Automatic after TTL expiry; manual by restarting the container or calling switchTheme (which refreshes the cache)

Proxy Instance Cache

HTTP proxy instances are cached in memory per target theme server URL using a Map. This prevents creating new proxy connections for every request and reuses persistent connections with WebSocket support for Next.js SSR streaming.

Error Pages

All error pages are styled EJS templates with the Siyahfy brand gradient (#667eea to #764ba2):

TemplateHTTP StatusWhen Shown
404.ejs404Store slug not found in database
domain-not-found.ejs404Custom domain not configured in store_domains
domain-pending.ejs200Domain exists but verified = false
theme-starting.ejs502Theme server not responding (proxy error) — auto-refreshes every 3 seconds

Traefik Integration

The proxy generates dynamic Traefik YAML config files for custom domains via utils/traefik-domain.js:

  • Config directory: /traefik-dynamic/ (mounted from host)
  • File naming: custom-{domain-with-dashes}.yml
  • Router priorities:
    • Priority 1000: Let’s Encrypt ACME challenge routes
    • Priority 100: Explicit custom domain routes
    • Priority 10: Platform subdomain wildcard routes
    • Priority -10: Catchall for unregistered custom domains
  • SSL: Automatic Let’s Encrypt HTTP-01 challenge via lehttp cert resolver; Cloudflare DNS-01 via cf resolver for Cloudflare-managed domains

Logging

HTTP Access Logs (logs/access.log)

  • Format: Morgan combined
  • Includes: timestamp, method, URL, status, response time, user agent

Server Application Logs (logs/server.log)

  • Format: Winston with custom YYYY-MM-DD HH:mm:ss LEVEL: message format
  • console.log, console.info, console.warn, and console.error are all overridden to route through Winston while preserving original console output

Environment Variables

# PostgreSQL connection DB_USER=<username> DB_HOST=postgres DB_DATABASE=<database> DB_PASSWORD=<password> DB_PORT=5432 # Server configuration PORT=5014 NODE_ENV=production # Authentication API_KEY_SECRET=<jwt-secret>

Key Operational Notes

  • First HTTPS request to a new custom domain triggers Let’s Encrypt certificate issuance (10-30 second delay)
  • Platform subdomain to custom domain redirect: If a store has a verified primary custom domain and a user visits the *.site.siyahfy.com URL, they are 302-redirected to the custom domain
  • x-siyahfy-host header: The proxy stamps the original visitor hostname so theme servers know which store data to load (Traefik overwrites x-forwarded-host)
  • x-forwarded-cookie header: All cookies are forwarded to theme servers via this custom header
  • DIRECT_THEMES bypass: Subdomains listed in DIRECT_THEMES (e.g., preview-01) skip domain resolution and go directly to the proxy