Vue Storefront is now Alokai! Learn More
Real-World Examples

Real-World Examples

This section shows patterns from production integrations you can copy and adapt. Each example explains the problem, the pattern, and why it works.


Pattern 1: SAP Out of Stock Detection

The Problem

SAP Commerce Cloud returns HTTP 200 success even when adding an out-of-stock product. The error is hidden in the response payload under cartModifications[].statusCode.

Without checking the payload, your middleware returns success. Frontend shows "Item added to cart" but the cart is empty. User clicks multiple times, gets confused.

The Pattern

Check vendor response payloads for error indicators, even on success:

export const addCartLineItem = async (context, args) => {
  const { api } = await context.getApiClient();
  
  const { data } = await api.doAddOrgCartEntries({
    cartId: args.cartId,
    orderEntryList: {
      orderEntries: [{ product: { code: args.sku }, quantity: args.quantity }],
    },
  });
  
  // Check payload for error indicator
  if (data?.cartModifications?.find(m => m.statusCode === 'noStock')) {
    throw context.createHttpError({
      statusCode: 409, // Conflict
      message: 'Product is out of stock',
      data: { errors: [{ type: 'InsufficientStockError' }] },
    });
  }
  
  // Success case
  const { data: cart } = await api.getCart({ cartId: args.cartId });
  return normalizeCart(cart);
};

Why It Works

  • 409 Conflict clearly indicates business constraint (not server error)
  • Structured error data (InsufficientStockError) lets frontend show specific message
  • No try/catch needed — checking response, not catching exception

Apply this pattern whenever your vendor returns success codes with error indicators. Common in: SAP, some Commerce Tools scenarios, custom internal APIs.


Pattern 2: Password Change Error Mapping

The Problem

External API returns 401 Unauthorized when the current password is wrong. That's technically correct, but confusing for users. Frontend should show "Current password is incorrect" (field-level error), not "Unauthorized".

The Pattern

Catch the error, inspect it, transform into user-friendly format:

export const changePassword = async (context, args) => {
  const { api } = await context.getApiClient();
  
  try {
    await api.updatePassword({
      userId: args.userId,
      oldPassword: args.currentPassword,
      newPassword: args.newPassword,
    });
    return { success: true };
  } catch (error) {
    // Error already normalized by adapter
    const normalized = HttpError.isHttpError(error) ? error : new HttpError(500, 'Unknown error');
    
    // Transform technical error into field-specific error
    if (normalized.statusCode === 401) {
      throw context.createHttpError({
        statusCode: 400, // Bad Request (client error)
        message: 'Current password is incorrect',
        data: { field: 'currentPassword' }, // Frontend can highlight this field
        cause: error, // Preserve for debugging
      });
    }
    
    // Other errors pass through
    throw normalized;
  }
};

Why It Works

  • User-friendly message instead of "Unauthorized"
  • Field-level error data lets frontend highlight the currentPassword field
  • Original error preserved in cause for debugging
  • Other errors pass through (don't hide unexpected errors)

Apply this pattern for form submissions where you need field-level error feedback: login, registration, password changes, profile updates.


Pattern 3: External API Normalization

The Problem

You're calling an external address validation service (not part of your commerce platform). If it throws, the error isn't normalized. Circuit breaker gets confused, status codes are wrong.

The Pattern

Configure HTTP client with error adapter once:

// In index.server.ts - one-time setup
import { axiosAdapter } from "@alokai/middleware-axios-error-adapter";

const validatorClient = axiosAdapter.withErrorNormalizer(
  axios.create({ baseURL: 'https://validator.example.com' }),
  { extractMessage: (payload) => payload?.error },
);

// In endpoint - clean code
export const validateAddress = async (context, args) => {
  // No try/catch needed - adapter handles normalization
  const { data } = await validatorClient.post('/check', {
    address: args.address,
  });
  
  return { isValid: data.valid };
};

Why It Works

  • Status codes extracted properly (404, 503, etc.)
  • One-time configuration in setup file
  • No repetitive try/catch blocks in endpoints
  • Circuit breaker treats 4xx as business errors (doesn't open on user typos)

Apply this pattern for any external service: payment gateways, shipping calculators, fraud detection, recommendation engines, etc.


Pattern 4: GraphQL Error Handling

The Problem

GraphQL APIs return HTTP 200 even with errors. Errors are in the response body under errors array. Without normalization, you get inconsistent status codes.

The Pattern

Configure Apollo Client with error adapter:

// In apolloClient.ts - one-time setup
import { apolloAdapter } from "@alokai/middleware-apollo-error-adapter";

export const apolloClientFactory = (options) => {
  const client = new ApolloClient({
    cache: new InMemoryCache(),
    ...options,
  });

  return apolloAdapter.withErrorNormalizer(client, {
    extractMessage: (errors) => errors?.[0]?.message,
  });
};

// In endpoint - clean code
export const getProducts = async (context, args) => {
  // No try/catch needed - adapter handles normalization
  return await context.client.query({
    query: GET_PRODUCTS_QUERY,
    variables: { categoryId: args.categoryId },
  });
};

Why It Works

  • Maps GraphQL error codes to HTTP status: UNAUTHENTICATED → 401, FORBIDDEN → 403, etc.
  • Distinguishes business errors (422) from infrastructure errors (502)
  • One-time configuration in client factory
  • No repetitive error handling in endpoints

Apply this pattern for all GraphQL-based integrations. The normalizer handles error extraction from different GraphQL clients automatically.


Pattern 5: Error Categorization Helper

The Problem

Frontend needs to show different UI for different SAP error types:

  • InsufficientStockError → Show "Out of stock" badge
  • UnknownIdentifierError → Show "Product not found" message
  • Other errors → Show generic "Error occurred"

The error might come from error.data (manually thrown) or error.cause.response.data (from Axios).

The Pattern

Create a helper that checks both locations:

import { HttpError } from '@alokai/connect/middleware';

// Check both possible locations for error type
const isSpecificSAPError = (error: HttpError, errorType: string): boolean => {
  return (
    // Manually thrown errors
    error.data?.errors?.[0]?.type === errorType ||
    // Axios errors (preserved in cause)
    (error.cause as any)?.response?.data?.errors?.[0]?.type === errorType
  );
};

// Categorize for frontend
export const categorizeQuickOrderError = (error: unknown): ErrorCategory => {
  if (!HttpError.isHttpError(error)) {
    return 'unknownError';
  }
  
  if (isSpecificSAPError(error, 'InsufficientStockError')) {
    return 'noQuantity'; // Frontend knows how to handle this
  }
  
  if (isSpecificSAPError(error, 'UnknownIdentifierError')) {
    return 'notFound'; // Frontend knows how to handle this
  }
  
  return 'unknownError'; // Fallback
};

Why It Works

  • Single helper handles both error locations
  • Type-safe with HttpError.isHttpError() check
  • Fallback to unknownError prevents crashes
  • Frontend gets consistent categories regardless of error origin

Apply this pattern when you have multiple error types from the same vendor and need to categorize them for frontend consumption.


Pattern 6: Validation with Schema Library

The Problem

You need to validate user input (e.g., form data) before processing. Manual validation is tedious and error-prone. You want structured error messages with field paths for frontend to display.

The Pattern

Use a schema validation library like zod or valibot:

import { z } from 'zod';

export const createUser = async (context, args) => {
  const schema = z.object({
    email: z.email(),
    username: z.string().min(3).max(20),
    age: z.number().min(18),
  });

  // Validation errors are auto-normalized to ValidationError with 400 status
  const validated = schema.parse(args);

  // Use validated data
  const { api } = await context.getApiClient();
  return api.createUser(validated);
};

Response on Validation Error

{
  "name": "ValidationError",
  "message": "Validation failed",
  "data": {
    "issues": [
      { "message": "Invalid email", "path": ["email"] },
      { "message": "String must contain at least 3 character(s)", "path": ["username"] }
    ]
  }
}

Apply this pattern for any user input validation: form submissions, query parameters, request bodies. Works with zod, valibot, arktype, and 20+ other Standard Schema libraries.


Common Patterns Summary

PatternWhen to UseKey Tool
Payload checkVendor returns 200 with error indicatorCheck response, context.createHttpError()
Error mappingTransform technical errors into user-friendlytry/catch + context.createHttpError()
External APICalling third-party servicesConfigure error adapter in client setup
GraphQL APIGraphQL-based integrationsConfigure error adapter in client factory
Error categorizationMultiple vendor error typesHelper function + HttpError.isHttpError()
Input validationValidating user/request dataSchema library + auto-normalization

Next Steps

Now that you've seen the patterns, you're ready to implement error handling in your integration.

Start small: Pick one endpoint, apply the appropriate pattern, test it. Then expand to other endpoints.

Need help deciding which pattern to use? Go back to When to Use try/catch for a decision guide.