# Caching & ETags

Quickback APIs include automatic [ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) support on all read endpoints. This lets clients and edge caches avoid re-transferring unchanged data, reducing latency and bandwidth.

## How it works

1. Every GET, LIST, and VIEW response includes a weak ETag header computed from the JSON body:
   ```
   ETag: W/"a1b2c3d4e5f60718"
   ```

2. On subsequent requests, the client sends the ETag back:
   ```
   If-None-Match: W/"a1b2c3d4e5f60718"
   ```

3. If the data hasn't changed, the server returns `304 Not Modified` with no body — saving bandwidth and parse time.

4. If the data has changed, the server returns the full response with a new ETag.

## Which endpoints support ETags

| Endpoint | ETag | Notes |
|----------|------|-------|
| `GET /` (list) | Yes | |
| `GET /:id` (get) | Yes | |
| `GET /views/:name` (view) | Yes | |
| `POST /` (create) | No | Mutations don't cache |
| `PUT /:id` (update) | No | |
| `DELETE /:id` (delete) | No | |
| Actions | No | Side-effectful |
| Batch operations | No | Mutations |

## Security

ETags are computed **after** all security pillars are applied (Firewall, Access, Guards, Masking). This means:

- Different users with different masking rules get different ETags
- Firewall-scoped data produces ETags specific to that scope
- There is no risk of leaking data across users via cached ETags

## Response headers

When ETags are enabled, GET responses include:

```
Content-Type: application/json
ETag: W/"a1b2c3d4e5f60718"
Cache-Control: no-cache
```

`Cache-Control: no-cache` means the browser (or edge cache) **must revalidate** on every request, but can use the cached response if the ETag matches. This is the correct behavior for authenticated APIs — data is always fresh, but unchanged responses avoid re-transfer.

## Client usage

### fetch API

```typescript
// First request
const res = await fetch("/api/v1/jobs");
const etag = res.headers.get("ETag");
const data = await res.json();

// Subsequent request with ETag
const res2 = await fetch("/api/v1/jobs", {
  headers: { "If-None-Match": etag },
});

if (res2.status === 304) {
  // Data hasn't changed, use cached version
} else {
  const freshData = await res2.json();
}
```

### Axios / ky / other clients

Most HTTP clients handle `304` responses automatically when configured with an interceptor or cache adapter.

## Configuration

ETags are enabled by default. To disable them, set `etag.enabled` to `false` in `quickback.config.ts`:

```typescript
export default defineConfig({
  name: "my-app",
  // ...
  etag: {
    enabled: false,
  },
});
```

When disabled, GET responses use standard `c.json()` with no ETag headers or `304` handling.

| Option | Default | Description |
|--------|---------|-------------|
| `enabled` | `true` | Include ETag headers on GET/LIST/VIEW responses |

## Technical details

- **Weak ETags** (`W/"..."`) — semantically equivalent responses, not byte-identical
- **SHA-256 truncated to 16 hex chars** — sufficient for collision avoidance, keeps the header small
- **Web Crypto API** — uses `crypto.subtle.digest()`, available in Cloudflare Workers and Node 18+
- **No dependencies** — zero additional packages required
