Developing E-commerce Store on Spree Commerce
Spree is open-source Rails engine existing since 2008. Unlike Vendure and commercetools, Spree is monolithic-first: embeds in Rails app as Engine and works with same database. Since version 4.3 added headless mode via REST API v2, allowing Spree as backend for React/Vue frontend.
Two Usage Modes
Monolith (classic): Spree works as Rails Engine inside your Rails app. Storefront rendered by server (ERB + Turbo), Admin UI built-in. Fits Rails-expert teams and medium traffic.
Headless:
Spree provides REST API v2 (/api/v2/storefront) and standard React storefront (spree/storefront). Storefront deploys separately. Becomes main mode with Spree 5.x.
Architecture: Rails Engine
# Gemfile
gem 'spree', '~> 4.10'
gem 'spree_auth_devise', '~> 4.6'
gem 'spree_gateway', '~> 3.10'
gem 'spree_backend', '~> 4.10' # Admin UI
gem 'spree_sample', '~> 4.10' # Test data
# For headless:
gem 'spree_api', '~> 4.10'
bundle install
bin/rails g spree:install
bin/rails g spree:auth:install
bin/rails db:migrate
bin/rails db:seed
After install available:
-
/admin— management panel -
/api/v2/storefront— REST API for frontend -
/— classic storefront (ifspree_frontendinstalled)
Data Model
Spree has established schema:
Spree::Store # Multi-store
├── Spree::Taxon # Categories (nested via ancestry)
├── Spree::Product
│ ├── Spree::Variant # Variants = SKU
│ ├── Spree::Price # Price per variant per currency
│ └── Spree::Image
├── Spree::Order
│ ├── Spree::LineItem
│ ├── Spree::Payment
│ └── Spree::Shipment
└── Spree::User # via spree_auth_devise
REST API v2: Headless Mode
// lib/spreeClient.ts
import { makeClient } from "@spree/storefront-api-v2-sdk";
export const client = makeClient({
host: process.env.NEXT_PUBLIC_SPREE_URL!,
});
// Get product list
const products = await client.products.list(
{},
{
include: "default_variant,images,taxons",
filter: { taxons: taxonId },
sort: "name",
page: 1,
per_page: 24,
}
);
// Create cart
const cart = await client.cart.create();
const orderToken = cart.success().data.attributes.token;
// Add item
await client.cart.addItem(
{ orderToken },
{
variant_id: variantId,
quantity: 1,
}
);
// Checkout: address
await client.checkout.orderUpdate(
{ orderToken },
{
order: {
ship_address_attributes: {
firstname: "Ivan",
lastname: "Petrov",
address1: "ul. Lenina 1",
city: "Moscow",
country_iso: "RU",
zipcode: "101000",
phone: "+79001234567",
},
},
}
);
Business Logic Customization
Spree uses Decorator pattern for extending models without fork:
# app/models/spree/product_decorator.rb
module Spree
module ProductDecorator
def bundle?
bundle_parts.any?
end
def effective_price_for(quantity)
if quantity >= 10
price * 0.9
elsif quantity >= 5
price * 0.95
else
price
end
end
end
end
Spree::Product.prepend(Spree::ProductDecorator)
Promotions — separate system (Spree::Promotion):
promotion = Spree::Promotion.create!(
name: "Summer discount 15%",
code: "SUMMER15",
starts_at: Date.today,
expires_at: 3.months.from_now,
usage_limit: 1000
)
promotion.actions.create!(
type: "Spree::Promotion::Actions::CreateAdjustment",
calculator: Spree::Calculator::FlatPercentItemTotal.create!(
preferred_flat_percent: 15.0
)
)
Development Stages
| Stage | Description | Timeline |
|---|---|---|
| Setup + config | Rails app, Spree install, DB | 2–3 days |
| Catalog + import | Rake tasks, CSV/API import | 4–8 days |
| Custom business logic | Decorators, promotions, shipping | 5–10 days |
| Frontend (Headless) | Next.js + Spree SDK | 10–20 days |
| Payment integrations | 2–3 providers | 4–6 days |
| Admin UI customization | Additional sections | 3–5 days |
| Total | 28–52 days |
Tech Stack
- Backend: Ruby 3.2 + Rails 7.1 + Spree 4.10
- DB: PostgreSQL 15 (complex promotions, ancestry trees)
- Cache: Redis (Solid Cache or Redis-store)
- Fonts: Sidekiq for background tasks (email, sync)
-
Search: Elasticsearch via
searchkickor pg_search -
Frontend: Next.js 14 +
@spree/storefront-api-v2-sdk







