Back to projects
SaaS E-Commerce Builder

GoStore

Think Shopify, but built for Algerian merchants who need Arabic/French storefronts, local payment gateways, and delivery integrations that actually work in Algeria.

Visit site
Next.jsTypeScriptPostgreSQLpg-bossCaddyAdonisJS (v1)

Why another e-commerce builder?

Shopify doesn't support Algerian payment gateways. WooCommerce requires hosting and technical knowledge most local merchants don't have. The few local alternatives are either outdated (PHP scripts from 2015) or overpriced for what they offer.

GoStore fills that gap — a hosted platform where a merchant can sign up, add products, and start selling within an hour, using payment methods their customers actually have.

What makes it different

Bilingual by default

Every storefront ships with Arabic and French. This isn't just a language toggle — it affects the entire layout:

  • Arabic triggers RTL (right-to-left) layout across the entire storefront
  • Product descriptions can be written in both languages, with a fallback to the primary language
  • The admin panel itself is bilingual — merchants can switch between Arabic and French in the settings
  • Email notifications (order confirmations, shipping updates) are sent in the customer's browser language

The RTL implementation was tricky. Tailwind's rtl: prefix handles most cases, but some components (image galleries, carousels, progress bars) needed manual RTL overrides. I built a useDirection() composable that returns the current text direction and applies it at the component level.

Local payment integrations

  • SlickPay: CIB and Edahabia cards on a hosted payment page — the customer pays, comes back to the store, and the order is confirmed with SlickPay before it's marked paid
  • Cash on delivery: The default option. The order is marked as cod and the merchant handles collection manually

Each payment method has its own flow and confirmation timing. I built a PaymentGateway interface that abstracts this:

interface PaymentGateway {
  createPayment(order: Order): Promise<PaymentResponse>
  verifyPayment(reference: string): Promise<PaymentStatus>
  handleWebhook(payload: unknown): WebhookEvent | null
}

Adding a new payment provider means implementing this interface and registering it in the config. No changes to the order flow.

Delivery tracking

Integration with local delivery services:

  • ZR Express and Maystro: API integrations for creating shipments and tracking parcels
  • Manual delivery: For merchants who handle their own delivery

Merchants can set a default carrier and shipping rates per zone (wilaya). Customers see estimated delivery times at checkout based on their location.

Technical architecture

Stack: AdonisJS first, Next.js now

The first version ran on AdonisJS with Vue and Inertia. I picked it for a TypeScript-first backend with a Laravel-like structure: familiar conventions, with types from the database to the UI.

I've since rewritten GoStore in Next.js on a single PostgreSQL database, with a pg-boss queue worker running as its own process. The app and the worker run under PM2 on one VPS behind Caddy, which issues TLS certificates on demand for merchants' custom domains — a merchant points their domain at the platform and HTTPS just works.

Deployment is deliberately simple: one instance, versioned releases behind a symlink, and a GitHub Actions workflow that uploads the release, builds it, runs the Prisma migrations and reloads PM2. Blue/green would add moving parts this traffic doesn't need yet.

Multi-tenancy

Each merchant gets their own subdomain (storename.gostoredz.com). Tenant isolation is handled at the database level:

-- Every table has a tenant_id column
ALTER TABLE products ADD COLUMN tenant_id UUID NOT NULL;
CREATE INDEX idx_products_tenant ON products(tenant_id);

-- Row-level security policy (PostgreSQL)
CREATE POLICY tenant_isolation ON products
  USING (tenant_id = current_setting('app.current_tenant')::uuid);

The app.current_tenant session variable is set by a middleware on every request. This means even if a bug slips through the application layer, the database enforces isolation.

I considered separate schemas per tenant (the "schema-per-tenant" pattern) but the projected merchant count (500-2000) didn't justify the operational complexity. Schema-per-tenant makes migrations painful — you have to run them against every schema — and the query performance benefit is minimal at this scale.

Storefront rendering

Merchant storefronts are server-rendered for SEO but use client-side hydration for interactivity (add-to-cart, search, filters). The rendering pipeline:

  1. Request hits the StorefrontMiddleware which resolves the tenant from the subdomain
  2. The tenant context is set in the database session
  3. The page is server-rendered with the merchant's theme (logo, colors, layout preferences)
  4. On the client, Vue hydrates interactive elements

Themes are template-based — merchants choose from a set of layouts and customize colors/fonts. I didn't build a full theme engine because most merchants just want something that looks professional without configuration.

Challenges

Image optimization. Merchants upload product photos from their phones — often 5MB+ JPEGs. I built an image processing pipeline that:

  1. Accepts the original upload
  2. Generates thumbnails (300x300, 600x600, 1200x1200) using Sharp
  3. Converts to WebP with a JPEG fallback
  4. Stores originals in S3-compatible storage and serves thumbnails from a CDN

This reduced average page load time from 8 seconds to under 2 seconds for product listing pages.

Search. Full-text search across 10,000+ products per merchant. I used the database's full-text index with a custom ranking that boosts:

  • Exact title matches
  • Products the merchant has marked as "featured"
  • Products with higher stock levels

No Elasticsearch needed at this scale.

Results

  • 200+ active merchants within 6 months
  • Average storefront setup time: 45 minutes
  • Handles product catalogs up to 10,000 SKUs per merchant
  • Average page load time under 2 seconds (after image optimization)
  • 99.5% uptime across all storefronts

Building something similar?

Tell me where you are and what's blocking you. I'll give you an honest read on how I'd approach it.