# Error handling best practices

Robust error handling keeps your application stable and gives users a good experience even when things go wrong. This page covers the principles and the patterns that belong in your app: retries, fallbacks and empty states. How the SDK reports errors (error groups and types, loader failures, quality errors) is described in the SDK guide [Error handling](/sdk/guides/error-handling).

## Error handling principles

- **Anticipate failures** - Assume network requests and data operations can fail
- **Check results** - The SDK reports many problems through return values and quality errors, not exceptions
- **Fail gracefully** - Provide fallbacks and meaningful error messages
- **Log for debugging** - Capture enough context (operation, model, key) to diagnose issues
- **Recover when possible** - Retry transient failures automatically

## Know what the SDK does instead of throwing

Most dcupl calls don't throw when something is wrong. Check for each case:

- **`init()` or `dcupl.update()` fails:** it logs the error and resolves `false`. Check the boolean it resolves to.
- **Invalid model definition:** dcupl records an `InvalidModelDefinition` quality error, and `init()` still resolves `true`.
- **Invalid or missing values:** dcupl records quality errors such as `WrongDataType` or `UndefinedValue` (attributes are required by default).
- **Unknown key in `data.update()` or `remove()`:** ignored. Check that the item exists first, or use `data.upsert()`.
- **Unknown key in `query.one()`:** returns `null`. Handle `null` in the UI.
- **Unknown attribute or operator in a condition:** an empty result, with no error or log. Check the names against the model.
- **Malformed condition** (`in` with a value that isn't an array, `find` with an array, no `operator`): throws `invalid query - …` and isn't stored, so the list's query stays as it was.
- **Unknown model key:** `lists.create()` and `query.execute()` throw "Model with key X not found", and `query.one()` throws an Error with an empty message. Create lists after `await dcupl.init()`, and check the key.
- **`data.set()` for a model that isn't registered:** the rows don't load into any model. `init()` only logs `Model not found during data processing` and still resolves `true`. See [Data for an Unknown Model](/sdk/guides/common-mistakes#data-for-an-unknown-model).

Quality errors don't stop anything, so read them after loading, at least during development:

```typescript
const qualityErrors = dcupl.quality.errors.summary();
if (qualityErrors.length > 0) console.warn('Data quality issues:', qualityErrors);
```

For an empty result you didn't expect, see [Query Returns No Results](/sdk/guides/common-mistakes#query-returns-no-results). For checking data quality after loading, see [Quality Validation](/sdk/advanced/quality-validation).

## App-level patterns

The snippets use placeholder helpers from your app: `showErrorMessage()`, `showMessage()` and `renderProducts()`.

### Retry failed data loading

Network requests can fail. Retry with exponential backoff before you give up:

```typescript filename="retry-loading.ts"
async function loadDataWithRetry(
  dcupl: Dcupl,
  fetchFn: () => Promise<any[]>,
  model: string,
  maxRetries = 3,
): Promise<boolean> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const data = await fetchFn();
      dcupl.data.set(data, { model }); // staged until init() or update()
      return true;
    } catch (error) {
      console.error(`Load attempt ${attempt} failed for ${model}:`, error);

      if (attempt === maxRetries) {
        return false;
      }

      // Exponential backoff: 1s, 2s, 4s
      const delay = 1000 * Math.pow(2, attempt - 1);
      await new Promise((resolve) => setTimeout(resolve, delay));
    }
  }
  return false;
}

// Usage
const loaded = await loadDataWithRetry(dcupl, () => fetch('/api/products').then((r) => r.json()), 'Product');

if (!loaded) {
  showErrorMessage('Unable to load products. Please check your connection.');
}
```

When you load through `DcuplAppLoader`, the loader reports failed resources instead; see [Resource Retry Pattern](/sdk/guides/error-handling#resource-retry-pattern).

### Fall back to cached data

If fresh data fails to load, use the last copy you cached:

```typescript filename="cache-fallback.ts"
async function loadProductsWithFallback(dcupl: Dcupl): Promise<boolean> {
  try {
    // Try to load fresh data
    const products = await fetch('/api/products').then((r) => r.json());
    dcupl.data.set(products, { model: 'Product' });

    // Cache for future fallback
    localStorage.setItem('products_cache', JSON.stringify(products));
    localStorage.setItem('products_cache_time', Date.now().toString());

    return true;
  } catch (error) {
    console.error('Failed to load fresh data:', error);

    // Try cached data
    const cached = localStorage.getItem('products_cache');
    if (cached) {
      const cacheTime = localStorage.getItem('products_cache_time');
      const cacheAge = Date.now() - Number(cacheTime);
      const maxAge = 24 * 60 * 60 * 1000; // 24 hours

      if (cacheAge < maxAge) {
        console.log('Using cached data');
        dcupl.data.set(JSON.parse(cached), { model: 'Product' });
        return true;
      } else {
        console.warn('Cache too old, not using');
      }
    }

    return false;
  }
}
```

### Validate user input before querying

Check filter values from the UI (a negative price, a minimum above the maximum) before they reach the query, and show a message instead of an empty result.

### Handle empty results

Empty results are not errors, but the UI should say why it is empty:

```typescript filename="empty-results.ts"
function searchProducts(list: DcuplList, searchTerm: string) {
  list.catalog.query.clear();

  if (searchTerm.trim()) {
    // Wrap the term in slashes for substring search; a bare string must match the whole value.
    // Escape regex characters in user input first.
    const escaped = searchTerm.trim().replace(/[.*+?^${}()|[\]\\/]/g, '\\$&');
    list.catalog.query.addCondition({
      attribute: 'name',
      operator: 'find',
      value: `/${escaped}/`,
      options: { regexFlags: 'i' },
    });
  }

  const results = list.catalog.query.items();
  const count = list.catalog.query.count();

  return {
    items: results,
    count,
    isEmpty: count === 0,
    hasSearchTerm: searchTerm.trim().length > 0,
  };
}

// Usage in UI
const search = searchProducts(productList, userInput);

if (search.isEmpty && search.hasSearchTerm) {
  showMessage(`No products found matching "${userInput}"`);
} else if (search.isEmpty) {
  showMessage('No products available');
} else {
  renderProducts(search.items);
}
```

### Degrade gracefully

If `init()` resolves `false`, keep the rest of the page usable: show the error and fallback content instead of the data-driven parts. The SDK guide shows [Graceful Degradation](/sdk/guides/error-handling#graceful-degradation) with a local fallback configuration and [Partial Data Loading](/sdk/guides/error-handling#partial-data-loading) with critical and optional resources.

### Log with context

Log the operation, the model and the key with every error, and send errors to your monitoring service in production. See [Integration with Error Tracking Services](/sdk/guides/error-handling#integration-with-error-tracking-services).

## Error handling checklist

- [ ] Check the boolean `init()` and `dcupl.update()` resolve to (they don't throw)
- [ ] Check `dcupl.quality.errors.summary()` after loading, at least during development
- [ ] Retry transient load failures, and fall back to cached data
- [ ] Check that an item exists before you update it
- [ ] Handle `null` from `query.one()` and empty results in the UI
- [ ] Validate user-provided filter values before querying
- [ ] Log failed operations with context

## Next steps

- [Error handling](/sdk/guides/error-handling) — How the SDK reports errors
- [Common mistakes](/sdk/guides/common-mistakes) — Avoid typical pitfalls
- [Quality Validation](/sdk/advanced/quality-validation) — Validate data quality
- [Data modeling](/learn/best-practices/data-modeling) — Structure models effectively
