Developing Product Recommendations System for E-commerce
Recommendations — algorithmic engine increasing average check and catalog depth. "Amazon recommends" — not marketing slogan but complex infrastructure with collaborative filtering, behavioral models, ML pipelines. Takes 10–20+ business days depending on approach.
Complexity Levels
Recommendation algorithms by complexity:
| Level | Approach | When To Use |
|---|---|---|
| Basic | Rule-based (popular, new, discounted) | Store launch |
| Medium | Item-based collaborative filtering | 10k+ orders in DB |
| Advanced | Matrix factorization (ALS, SVD) | 100k+ events |
| Enterprise | Deep learning (two-tower, BERT4Rec) | ML infrastructure |
For most stores optimal: medium level — collaborative filtering on orders + content signals.
Rule-Based Recommendations
Quick to start, needs no accumulated data:
class PopularityRecommender {
public function getPopular(int $categoryId, int $limit = 8): Collection {
return Product::where('category_id', $categoryId)
->where('is_active', true)
->withCount(['orderItems as sales_count' => fn($q) =>
$q->whereHas('order', fn($o) =>
$o->where('status', 'completed')
->where('created_at', '>=', now()->subDays(30))
)
])
->orderByDesc('sales_count')
->limit($limit)
->get();
}
}
New users — popular products. Users with history — personalized.
Collaborative Filtering by Orders
"Users who bought this also bought..." — classic item-to-item CF:
CREATE MATERIALIZED VIEW product_cooccurrences AS
SELECT
oi1.product_id AS product_a,
oi2.product_id AS product_b,
COUNT(DISTINCT oi1.order_id) AS cooccurrence_count
FROM order_items oi1
JOIN order_items oi2
ON oi1.order_id = oi2.order_id
AND oi1.product_id != oi2.product_id
JOIN orders o ON oi1.order_id = o.id
WHERE o.status = 'completed'
GROUP BY oi1.product_id, oi2.product_id
HAVING COUNT(DISTINCT oi1.order_id) >= 3;
CREATE INDEX idx_cooc_product_a ON product_cooccurrences(product_a, cooccurrence_count DESC);
Update nightly: $schedule->command('db:refresh-cooccurrences')->dailyAt('03:00');
Get recommendations by viewed product:
class CollaborativeRecommender {
public function getSimilar(int $productId, int $limit = 8): Collection {
$recommendedIds = DB::table('product_cooccurrences')
->where('product_a', $productId)
->orderByDesc('cooccurrence_count')
->limit($limit)
->pluck('product_b');
return Product::whereIn('id', $recommendedIds)
->where('is_active', true)
->get();
}
}
ML Recommendations via Python Service
For advanced level: Python microservice on FastAPI with ALS model:
import implicit
from fastapi import FastAPI
app = FastAPI()
model = implicit.als.AlternatingLeastSquares(factors=64, iterations=20)
@app.get("/recommendations/user/{user_id}")
async def user_recommendations(user_id: int, n: int = 12):
user_idx = user_id_to_idx.get(user_id)
if user_idx is None:
return {"items": get_popular_fallback(n)}
ids, scores = model.recommend(user_idx, user_item_matrix[user_idx], N=n)
product_ids = [idx_to_product_id[i] for i in ids]
return {"items": product_ids, "scores": scores.tolist()}
PHP backend calls service, caches result:
$recommendations = Cache::remember(
"recs:user:{$userId}",
1800,
fn() => Http::timeout(2)->get("{$this->mlServiceUrl}/recommendations/user/{$userId}", ['n' => 12])
->throw()
->json('items')
);
Fallback on ML unavailable — rule-based.
Event Tracking for Training
Recommendation quality depends on data. Track:
api.post('/events', {
type: 'product_view',
product_id: product.id,
session_id: getSessionId(),
timestamp: new Date().toISOString(),
});
api.post('/events', { type: 'add_to_cart', product_id, quantity });
// Purchase tracked server-side after order creation
CREATE TABLE recommendation_events (
id BIGSERIAL PRIMARY KEY,
event_type VARCHAR(30) NOT NULL,
user_id BIGINT,
session_id VARCHAR(64),
product_id BIGINT,
metadata JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
A/B Testing Algorithms
Show different algorithms to different segments:
$algorithm = $user->id % 2 === 0 ? 'collaborative' : 'ml';
$recommendations = $this->recommenderFactory->make($algorithm)->get($user, $product);
event(new RecommendationShown($user, $recommendations, $algorithm, $context));
After 2–4 weeks compare CTR and conversion, pick winner.
Placement
- Product page: "Bought with this", "Similar products"
- Cart: "You forgot to add", "Complete your set"
- Homepage: "Just for you", "Popular"
- Post-purchase email: "You might like"
- Empty search: "Try these products"







