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
| Technology | Version | Purpose |
|---|---|---|
| Express.js | 4.21.2 | HTTP server framework |
| http-proxy-middleware | 3.0.5 | Reverse proxy to theme servers |
| PostgreSQL (pg) | 8.16.3 | Domain resolution and store lookup |
| Redis | 5.10.0 | Theme resolution caching (60s TTL) |
| Winston | 3.18.3 | Structured application logging |
| Morgan | 1.10.0 | HTTP access logging |
| EJS | 3.1.9 | Error page template rendering |
| JSON Web Token | 9.0.2 | Vendor authentication for API routes |
| js-yaml | 4.1.1 | Traefik dynamic config generation |
| cookie-parser | 1.4.7 | Cookie parsing for auth forwarding |
| cors | 2.8.5 | Cross-origin request handling |
| dotenv | 17.2.3 | Environment 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.jsonHow 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_domainstable by exact domain match - Verify
verified = trueanddisabled = false - Extract
store_idandvendor_idfrom the matched row
2. Platform Slug Flow (e.g., codingninja.site.siyahfy.com)
- Extract slug from subdomain
- Query
storestable bystore_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 tohttp://{slug}.localhost:3002
Database Tables
The proxy relies on four key database tables:
stores
| Column | Type | Description |
|---|---|---|
store_id | integer | Primary key |
vendor_id | integer | Foreign key to vendor |
store_slug | varchar | Unique slug for *.site.siyahfy.com |
store_name | varchar | Display name |
api_key | jsonb | Array of API key objects |
domain_status | varchar | Domain verification status |
store_domains
| Column | Type | Description |
|---|---|---|
domain | varchar | Custom domain name (unique) |
store_id | integer | FK to stores |
vendor_id | integer | FK to vendor |
verified | boolean | Domain ownership verified |
disabled | boolean | Domain disabled by admin |
is_main | boolean | Main domain for store |
is_primary | boolean | Primary custom domain (redirect target) |
txt_token | varchar | TXT record for verification |
vendor_subscriptions
| Column | Type | Description |
|---|---|---|
vendor_id | integer | FK to vendor |
store_name | varchar | Store slug |
plan_name | varchar | Plan level (basic/pro/growth) |
vendor_theme_purchases
| Column | Type | Description |
|---|---|---|
vendor_id | integer | FK to vendor |
store_name | varchar | Store slug |
theme_key | varchar | Theme identifier |
status | varchar | Purchase 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
wwwsubdomain - 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):
| Template | HTTP Status | When Shown |
|---|---|---|
404.ejs | 404 | Store slug not found in database |
domain-not-found.ejs | 404 | Custom domain not configured in store_domains |
domain-pending.ejs | 200 | Domain exists but verified = false |
theme-starting.ejs | 502 | Theme 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
lehttpcert resolver; Cloudflare DNS-01 viacfresolver 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: messageformat console.log,console.info,console.warn, andconsole.errorare 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.comURL, they are 302-redirected to the custom domain x-siyahfy-hostheader: The proxy stamps the original visitor hostname so theme servers know which store data to load (Traefik overwritesx-forwarded-host)x-forwarded-cookieheader: 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