Vue Storefront is now Alokai! Learn More
When to Use try/catch

When to Use try/catch

This is the most important section for writing clean middleware code. Understanding when to use (and skip) try/catch will save you from writing unnecessary code.


The Golden Rule

Only wrap external API calls in try/catch.

If you're calling methods from your integration's api object, they already normalize errors. No try/catch needed.


Scenario 1: Integration Methods (Skip try/catch)

When this applies

You're calling methods like:

  • api.getCart()
  • api.addCartEntry()
  • api.getProduct()
  • Any method from context.getApiClient()

Why skip try/catch?

Integration methods already throw normalized HttpError instances with proper status codes. Wrapping them in try/catch is redundant.

Example

export const updateCart = async (context, args) => {
  const { api } = await context.getApiClient();
  
  // ✅ No try/catch needed
  const { data: cart } = await api.getCart({ cartId: args.cartId });
  await api.addCartEntry({ cartId: args.cartId, sku: args.sku });
  
  return normalizeCart(cart);
};

Think of integration methods as "already safe" — they handle error normalization for you.


Scenario 2: External APIs (Usually Skip try/catch)

When this applies

You're calling:

  • Third-party APIs with HTTP clients
  • External services (address validation, payment gateways, etc.)
  • Any API not part of your integration's api object

Why usually skip try/catch?

If your HTTP client is configured with an error adapter (in index.server.ts or client factory), errors are automatically normalized. No try/catch needed.

Example

// In index.server.ts - configure once with error adapter
const validatorClient = adapter.withErrorNormalizer(
  httpClient,
  { extractMessage: (payload) => payload?.error },
);

// In your endpoint - no try/catch needed
export const validateAddress = async (context, args) => {
  // Errors automatically normalized by adapter
  const { data } = await validatorClient.post('/check', {
    address: args.address,
  });
  return { isValid: data.valid };
};

When to add try/catch

Only if you need custom error transformation:

export const validateAddress = async (context, args) => {
  try {
    const { data } = await validatorClient.post('/check', {
      address: args.address,
    });
    return { isValid: data.valid };
  } catch (error) {
    // Transform adapter-normalized error for specific UX
    const normalized = HttpError.isHttpError(error) ? error : new HttpError(500, 'Unknown error');
    
    if (normalized.statusCode === 422) {
      throw context.createHttpError({
        statusCode: 400,
        message: 'Please provide a complete address',
        data: { field: 'address' },
        cause: error,
      });
    }
    
    throw normalized;
  }
};

Best practice: Configure adapters once in setup files. Your endpoint methods stay clean without try/catch blocks.


Scenario 3: Custom Error Mapping (Use try/catch)

When this applies

You need to:

  • Transform technical errors into user-friendly messages
  • Map vendor error codes to custom messages
  • Add frontend-specific error data

Why use try/catch?

You're catching the error, inspecting it, and throwing a new, more specific error.

Example: Password change

Technical error: 401 Unauthorized
User-friendly error: "Current password is incorrect"

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) {
    const normalized = normalizeAxiosError(error);
    
    // Map 401 to user-friendly message
    if (normalized.statusCode === 401) {
      throw context.createHttpError({
        statusCode: 400,
        message: 'Current password is incorrect',
        data: { field: 'currentPassword' },
        cause: error,
      });
    }
    
    // Re-throw other errors as-is
    throw normalized;
  }
};

The key: You're adding value by making the error more specific for your frontend.


Scenario 4: Vendor Payload Errors (No try/catch)

When this applies

Vendor returns HTTP 200 with error indicators in the response body (like SAP's statusCode: 'noStock').

Why skip try/catch?

There's no exception thrown — the API call succeeded. You check the response and throw manually.

Example

export const addToCart = async (context, args) => {
  const { api } = await context.getApiClient();
  
  const { data } = await api.addCartEntry(args);
  
  // Check response payload (no try/catch needed)
  if (data?.modifications?.find(m => m.statusCode === 'noStock')) {
    throw context.createHttpError({
      statusCode: 409,
      message: 'Product is out of stock',
      data: { errors: [{ type: 'InsufficientStockError' }] },
    });
  }
  
  return normalizeCart(data);
};

Decision Flowchart

Use this to decide whether you need try/catch:

Are you calling an integration method?
│
├─ YES → ❌ Skip try/catch
│         (Already normalized)
│
└─ NO → Are you calling an external API?
        │
        ├─ YES → ✅ Use try/catch
        │         (Need to normalize)
        │
        └─ NO → Do you need custom error mapping?
                │
                ├─ YES → ✅ Use try/catch
                │         (Transform the error)
                │
                └─ NO → ❌ Skip try/catch
                          (Let errors propagate)

Common Mistakes

❌ Wrapping integration methods

// DON'T
try {
  const cart = await api.getCart({ cartId });
} catch (error) {
  throw normalizeAxiosError(error); // Redundant!
}

Problem: You're normalizing an already-normalized error.

Fix: Remove the try/catch. Let api.getCart() throw directly.


❌ Forgetting external APIs

// DON'T
const { data } = await axios.get(externalUrl); // Missing try/catch!

Problem: If Axios throws, the error isn't normalized. Wrong status code in logs, circuit breaker confusion.

Fix: Wrap in try/catch and use normalizeAxiosError().


Quick Summary

Scenariotry/catch?Tool to Use
Integration method (api.getCart())❌ NoLet it throw
External API (Axios/Fetch)✅ YesnormalizeAxiosError()
Custom error mapping✅ Yescontext.createHttpError()
Vendor payload errors (HTTP 200)❌ NoCheck response, throw manually

Next Steps