The SDK works excellently in Node.js applications for backend services, APIs, and server-side e-commerce operations.

Installation

Install the SDK in your Node.js project:

npm install @commercengine/storefront-sdk
# or
yarn add @commercengine/storefront-sdk

Basic Setup

Configure the SDK for server-side usage:

// src/lib/storefront.ts
import { StorefrontSDK, Environment, MemoryTokenStorage } from '@commercengine/storefront-sdk';

const storefront = new StorefrontSDK({
  storeId: process.env.STORE_ID!,
  environment: process.env.NODE_ENV === 'production' ? Environment.Production : Environment.Staging,
  apiKey: process.env.CE_API_KEY!,
  tokenStorage: new MemoryTokenStorage(), // For server-side token management
  timeout: 30000 // 30 second timeout
});

export default storefront;

Environment Variables

Set up your environment configuration:

# .env
STORE_ID=your_store_id
CE_API_KEY=your_api_key
NODE_ENV=development

Token Management Strategies

Memory Storage (Default)

For simple server applications or stateless operations:

import { MemoryTokenStorage } from '@commercengine/storefront-sdk';

const client = new StorefrontSDK({
  storeId: process.env.STORE_ID!,
  apiKey: process.env.CE_API_KEY!,
  tokenStorage: new MemoryTokenStorage()
});

Database Integration

For persistent token storage across server restarts:

import { TokenStorage } from '@commercengine/storefront-sdk';

class DatabaseTokenStorage implements TokenStorage {
  private userId: string;

  constructor(userId: string) {
    this.userId = userId;
  }

  async getAccessToken(): Promise<string | null> {
    const user = await User.findById(this.userId);
    return user?.accessToken || null;
  }

  async setAccessToken(token: string): Promise<void> {
    await User.findByIdAndUpdate(this.userId, { accessToken: token });
  }

  async getRefreshToken(): Promise<string | null> {
    const user = await User.findById(this.userId);
    return user?.refreshToken || null;
  }

  async setRefreshToken(token: string): Promise<void> {
    await User.findByIdAndUpdate(this.userId, { refreshToken: token });
  }

  async clearTokens(): Promise<void> {
    await User.findByIdAndUpdate(this.userId, { 
      accessToken: null, 
      refreshToken: null 
    });
  }
}

// Usage with user-specific tokens
const userClient = new StorefrontSDK({
  storeId: process.env.STORE_ID!,
  apiKey: process.env.CE_API_KEY!,
  tokenStorage: new DatabaseTokenStorage('user-123')
});

Redis Integration

For distributed systems with shared token storage:

import Redis from 'ioredis';
import { TokenStorage } from '@commercengine/storefront-sdk';

class RedisTokenStorage implements TokenStorage {
  private redis: Redis;
  private keyPrefix: string;

  constructor(redis: Redis, userId: string) {
    this.redis = redis;
    this.keyPrefix = `storefront:${userId}`;
  }

  async getAccessToken(): Promise<string | null> {
    return await this.redis.get(`${this.keyPrefix}:access_token`);
  }

  async setAccessToken(token: string): Promise<void> {
    await this.redis.setex(`${this.keyPrefix}:access_token`, 3600, token); // 1 hour expiry
  }

  async getRefreshToken(): Promise<string | null> {
    return await this.redis.get(`${this.keyPrefix}:refresh_token`);
  }

  async setRefreshToken(token: string): Promise<void> {
    await this.redis.setex(`${this.keyPrefix}:refresh_token`, 86400, token); // 24 hour expiry
  }

  async clearTokens(): Promise<void> {
    await this.redis.del(
      `${this.keyPrefix}:access_token`,
      `${this.keyPrefix}:refresh_token`
    );
  }
}

Express.js Integration

Basic Express Setup

import express from 'express';
import storefront from './lib/storefront';

const app = express();
app.use(express.json());

app.get('/api/products', async (req, res) => {
  try {
    const result = await storefront.catalog.listProducts({
      limit: parseInt(req.query.limit as string) || 20
    });

    if (result.success) {
      res.json(result.data);
    } else {
      res.status(400).json({ error: result.error.message });
    }
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Middleware for User Context

import { Request, Response, NextFunction } from 'express';

interface AuthenticatedRequest extends Request {
  userClient?: StorefrontSDK;
  userId?: string;
}

const authenticateUser = async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
  const authHeader = req.headers.authorization;
  if (!authHeader) {
    return res.status(401).json({ error: 'Authorization header required' });
  }

  const token = authHeader.replace('Bearer ', '');
  
  // Create user-specific SDK instance
  req.userClient = new StorefrontSDK({
    storeId: process.env.STORE_ID!,
    apiKey: process.env.CE_API_KEY!,
    accessToken: token // Manual token management
  });

  // Verify token by getting user info
  const userInfo = await req.userClient.getUserInfo();
  if (!userInfo) {
    return res.status(401).json({ error: 'Invalid token' });
  }

  req.userId = userInfo.sub;
  next();
};

// Protected routes
app.get('/api/cart', authenticateUser, async (req: AuthenticatedRequest, res) => {
  const result = await req.userClient!.cart.retrieveCartUsingUserId(req.userId!);
  
  if (result.success) {
    res.json(result.data);
  } else {
    res.status(400).json({ error: result.error.message });
  }
});

Background Jobs

Order Processing

import { Queue, Worker } from 'bullmq';
import storefront from './lib/storefront';

interface OrderProcessingJob {
  orderId: string;
  userId: string;
}

const orderQueue = new Queue<OrderProcessingJob>('order-processing');

const orderWorker = new Worker<OrderProcessingJob>('order-processing', async (job) => {
  const { orderId, userId } = job.data;
  
  try {
    // Get order details
    const orderResult = await storefront.order.retrieveOrderDetail(orderId);
    if (!orderResult.success) {
      throw new Error(`Failed to retrieve order: ${orderResult.error.message}`);
    }

    // Process order (send emails, update inventory, etc.)
    await processOrderFulfillment(orderResult.data);
    
    console.log(`Order ${orderId} processed successfully`);
  } catch (error) {
    console.error(`Failed to process order ${orderId}:`, error);
    throw error; // Re-throw to mark job as failed
  }
});

// Add job to queue
export async function queueOrderProcessing(orderId: string, userId: string) {
  await orderQueue.add('process-order', { orderId, userId });
}

Webhook Handlers

app.post('/webhooks/order-created', async (req, res) => {
  const { order_id, user_id } = req.body;
  
  try {
    // Verify webhook signature here
    
    // Queue background processing
    await queueOrderProcessing(order_id, user_id);
    
    res.status(200).json({ message: 'Webhook processed' });
  } catch (error) {
    console.error('Webhook processing failed:', error);
    res.status(500).json({ error: 'Webhook processing failed' });
  }
});

Microservices Architecture

Service-to-Service Communication

// order-service.ts
export class OrderService {
  private storefront: StorefrontSDK;

  constructor() {
    this.storefront = new StorefrontSDK({
      storeId: process.env.STORE_ID!,
      apiKey: process.env.CE_API_KEY!,
      tokenStorage: new MemoryTokenStorage()
    });
  }

  async createOrder(userId: string, cartData: any) {
    const result = await this.storefront.order.createOrder({
      user_id: userId,
      ...cartData
    });

    if (result.success) {
      // Emit event for other services
      await this.eventBus.emit('order.created', {
        orderId: result.data.id,
        userId,
        amount: result.data.grand_total
      });
    }

    return result;
  }
}

Health Checks

app.get('/health', async (req, res) => {
  try {
    // Test SDK connectivity
    const result = await storefront.helpers.retrieveAllCountries();
    
    if (result.success) {
      res.status(200).json({ 
        status: 'healthy',
        timestamp: new Date().toISOString(),
        sdk: 'connected'
      });
    } else {
      res.status(503).json({ 
        status: 'unhealthy',
        error: 'SDK connection failed'
      });
    }
  } catch (error) {
    res.status(503).json({ 
      status: 'unhealthy',
      error: error.message
    });
  }
});

Error Handling

Global Error Handler

import { ResponseUtils } from '@commercengine/storefront-sdk';

app.use((error: any, req: Request, res: Response, next: NextFunction) => {
  console.error('Unhandled error:', error);

  // Handle SDK-specific errors
  if (error.response && ResponseUtils.isSuccess) {
    const metadata = ResponseUtils.getMetadata(error.response);
    return res.status(metadata.status).json({
      error: 'API request failed',
      details: metadata
    });
  }

  // Generic error response
  res.status(500).json({
    error: 'Internal server error',
    requestId: req.headers['x-request-id']
  });
});

Graceful Shutdown

process.on('SIGTERM', async () => {
  console.log('SIGTERM received, shutting down gracefully');
  
  // Close database connections, stop workers, etc.
  await orderWorker.close();
  await redis.disconnect();
  
  process.exit(0);
});

Performance Considerations

Request Timeouts

const client = new StorefrontSDK({
  storeId: process.env.STORE_ID!,
  apiKey: process.env.CE_API_KEY!,
  timeout: 10000, // 10 second timeout for backend operations
  debug: process.env.NODE_ENV === 'development'
});

Connection Monitoring

// Monitor SDK performance
const trackAPIPerformance = async (operation: string, fn: () => Promise<any>) => {
  const start = process.hrtime.bigint();
  
  try {
    const result = await fn();
    const duration = Number(process.hrtime.bigint() - start) / 1000000; // Convert to ms
    
    console.log(`SDK operation ${operation} completed in ${duration.toFixed(2)}ms`);
    return result;
  } catch (error) {
    const duration = Number(process.hrtime.bigint() - start) / 1000000;
    console.error(`SDK operation ${operation} failed after ${duration.toFixed(2)}ms:`, error);
    throw error;
  }
};

// Usage
const products = await trackAPIPerformance('listProducts', () =>
  storefront.catalog.listProducts({ limit: 20 })
);

Best Practices

Environment Configuration

Use environment variables for all configuration and never hardcode sensitive values

Error Handling

Implement comprehensive error handling for all SDK operations with appropriate logging

Token Storage

Choose appropriate token storage based on your architecture (memory, database, Redis)

Health Monitoring

Implement health checks and monitoring to ensure SDK connectivity and performance

Cross-References

The SDK is optimized for server-side usage with proper timeout handling and token management. Choose your token storage strategy based on your application architecture and scaling requirements.