> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Using in APIs

> Applying Cache-Control to @api decorators

You can set **Cache-Control headers on API responses** by adding the `cacheControl` option to Sonamu's `@api` decorator.

## Basic Usage

### Adding to @api Decorator

```typescript theme={null}
import { BaseModelClass, api, CachePresets } from "sonamu";

class ProductModelClass extends BaseModelClass {
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.shortLived,  // 1 minute cache
  })
  async findAll() {
    return this.findMany({});
  }
}
```

**Result**:

```http theme={null}
HTTP/1.1 200 OK
Cache-Control: public, max-age=60
Content-Type: application/json

[{"id": 1, "name": "Product A"}, ...]
```

## Configuration Methods

<Tabs>
  <Tab title="Using Presets" icon="list">
    **The simplest approach**

    ```typescript theme={null}
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.mediumLived,  // 5 minute cache
    })
    async getCategories() {
      return this.categories;
    }
    ```

    **Available presets**:

    * `noStore`: No caching
    * `noCache`: Revalidate every time
    * `shortLived`: 1 minute
    * `mediumLived`: 5 minutes
    * `longLived`: 1 hour
    * `private`: Personalized data
  </Tab>

  <Tab title="Custom Configuration" icon="screwdriver-wrench">
    **When you need fine-grained control**

    ```typescript theme={null}
    @api({
      httpMethod: 'GET',
      cacheControl: {
        visibility: 'public',
        maxAge: 120,  // 2 minutes
        staleWhileRevalidate: 300,  // Use stale for 5 minutes
      }
    })
    async getData() {
      return this.findMany({});
    }
    ```
  </Tab>

  <Tab title="Global Handler" icon="globe">
    **Apply to all APIs at once**

    ```typescript theme={null}
    // sonamu.config.ts
    export const config: SonamuConfig = {
      server: {
        apiConfig: {
          cacheControlHandler: (req) => {
            if (req.type !== 'api') return undefined;

            // Only cache GET requests
            if (req.method === 'GET') {
              return CachePresets.shortLived;
            }

            // POST, PUT, DELETE use no-store
            return CachePresets.noStore;
          }
        }
      }
    };
    ```
  </Tab>
</Tabs>

## Priority Order

Cache-Control settings are applied in the following order:

```mermaid theme={null}
graph LR
    A[@api decorator] -->|1st priority| D[Applied]
    B[cacheControlHandler] -->|2nd priority| D
    C[Default: none] -->|3rd priority| D
```

1. **@api decorator's cacheControl** (highest priority)
2. **cacheControlHandler return value**
3. **Default** (no Cache-Control header)

### Example

```typescript theme={null}
// sonamu.config.ts
cacheControlHandler: (req) => {
  if (req.type === 'api' && req.method === 'GET') {
    return CachePresets.shortLived;  // Default 1 minute
  }
}

// Model
@api({
  httpMethod: 'GET',
  cacheControl: CachePresets.mediumLived,  // Override to 5 minutes
})
async getData() { ... }
```

**Result**: `mediumLived` applied (decorator takes priority)

## Strategies by HTTP Method

### GET Requests

**Read APIs can be cached**.

<Tabs>
  <Tab title="Frequently Changed" icon="bolt">
    ```typescript theme={null}
    // Post list (frequently added)
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.shortLived,  // 1 minute
    })
    async getPosts(page: number) {
      return this.findMany({ page, num: 20 });
    }
    ```
  </Tab>

  <Tab title="Occasionally Changed" icon="clock">
    ```typescript theme={null}
    // Product info (occasionally modified)
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.mediumLived,  // 5 minutes
    })
    async getProduct(id: number) {
      return this.findOne(['id', id]);
    }
    ```
  </Tab>

  <Tab title="Rarely Changed" icon="lock">
    ```typescript theme={null}
    // Categories (rarely changed)
    @api({
      httpMethod: 'GET',
      cacheControl: CachePresets.longLived,  // 1 hour
    })
    async getCategories() {
      return this.categories;
    }
    ```
  </Tab>
</Tabs>

### POST, PUT, DELETE Requests

**Mutation requests should not be cached**.

```typescript theme={null}
// ✅ Correct example
@api({
  httpMethod: 'POST',
  cacheControl: CachePresets.noStore,  // No caching
})
async create(data: ProductSave) {
  return this.saveOne(data);
}

@api({
  httpMethod: 'PUT',
  cacheControl: CachePresets.noStore,
})
async update(id: number, data: Partial<ProductSave>) {
  return this.updateOne(['id', id], data);
}

@api({
  httpMethod: 'DELETE',
  cacheControl: CachePresets.noStore,
})
async delete(id: number) {
  return this.deleteOne(['id', id]);
}
```

<Warning>
  **Why you should not cache mutation requests**:

  * Sending the same POST request multiple times causes **duplicate creations**
  * Browser reuses previous response → appears as if nothing was actually created

  ```typescript theme={null}
  // ❌ Never do this
  @api({
    httpMethod: 'POST',
    cacheControl: { maxAge: 60 },  // Dangerous!
  })
  async create(data: ProductSave) { ... }
  ```
</Warning>

## Strategies by Data Characteristics

### Public Data (public)

Same response for all users.

```typescript theme={null}
class ProductModelClass extends BaseModelClass {
  // Product list
  @api({
    httpMethod: 'GET',
    cacheControl: {
      visibility: 'public',  // CDN cacheable
      maxAge: 60,
    }
  })
  async findAll() {
    return this.findMany({});
  }
}
```

**Characteristics**:

* Cached by browser + CDN
* Minimizes network traffic
* Reduces server load

### Private Data (private)

Different response for each user.

```typescript theme={null}
class OrderModelClass extends BaseModelClass {
  // My order history
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.private,  // Browser only caching
  })
  async getMyOrders(ctx: Context) {
    return this.findMany({
      where: [['user_id', ctx.user.id]]
    });
  }
}
```

**Characteristics**:

* Cached only in browser (not CDN)
* Prevents exposure to other users

### Sensitive Data (no-store)

Data that should not be cached.

```typescript theme={null}
class PaymentModelClass extends BaseModelClass {
  // Payment information
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.noStore,  // No caching
  })
  async getPaymentInfo(id: number) {
    return this.findOne(['id', id]);
  }
}
```

## Practical Examples

### 1. E-commerce API

```typescript theme={null}
class ProductModelClass extends BaseModelClass {
  // Product list (frequently changed, public)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.shortLived,  // 1 minute
  })
  async findAll(page: number) {
    return this.findMany({ page, num: 20 });
  }

  // Product detail (occasionally changed, public)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.mediumLived,  // 5 minutes
  })
  async findById(id: number) {
    return this.findOne(['id', id]);
  }

  // Categories (rarely changed, public)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.longLived,  // 1 hour
  })
  async getCategories() {
    return this.categories;
  }

  // Stock (real-time, no-cache)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.noCache,  // Revalidate every time
  })
  async getStock(productId: number) {
    return this.checkStock(productId);
  }

  // Shopping cart (personal, private)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.private,  // Browser only
  })
  async getMyCart(ctx: Context) {
    return this.getCart(ctx.user.id);
  }

  // Order creation (Mutation, no-store)
  @api({
    httpMethod: 'POST',
    cacheControl: CachePresets.noStore,  // No caching
  })
  async createOrder(data: OrderSave) {
    return this.saveOne(data);
  }
}
```

### 2. Blog API

```typescript theme={null}
class PostModelClass extends BaseModelClass {
  // Post list (frequently added, 5 minutes)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.mediumLived,
  })
  async findAll(page: number) {
    return this.findMany({ page, num: 10 });
  }

  // Post detail (occasionally modified, 10 minutes)
  @api({
    httpMethod: 'GET',
    cacheControl: {
      visibility: 'public',
      maxAge: 600,  // 10 minutes
      staleWhileRevalidate: 1800,  // Use stale for 30 minutes
    }
  })
  async findById(id: number) {
    return this.findOne(['id', id]);
  }

  // Popular posts (1 hour)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.longLived,
  })
  async getPopular() {
    return this.findPopular();
  }
}
```

### 3. User API

```typescript theme={null}
class UserModelClass extends BaseModelClass {
  // Public profile (public, 5 minutes)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.mediumLived,
  })
  async getPublicProfile(username: string) {
    return this.findOne(['username', username]);
  }

  // My profile (private, revalidate)
  @api({
    httpMethod: 'GET',
    cacheControl: CachePresets.private,
  })
  async getMyProfile(ctx: Context) {
    return this.findOne(['id', ctx.user.id]);
  }

  // Profile update (no-store)
  @api({
    httpMethod: 'PUT',
    cacheControl: CachePresets.noStore,
  })
  async updateProfile(ctx: Context, data: Partial<UserSave>) {
    return this.updateOne(['id', ctx.user.id], data);
  }
}
```

## CDN Optimization

### Using s-maxage

You can apply different TTLs for browser and CDN.

```typescript theme={null}
@api({
  httpMethod: 'GET',
  cacheControl: {
    visibility: 'public',
    maxAge: 60,       // Browser: 1 minute
    sMaxAge: 300,     // CDN: 5 minutes
  }
})
async getData() {
  return this.findMany({});
}
```

**Effect**:

* Browser: Requests CDN every minute
* CDN: Requests server every 5 minutes
* Minimizes server load

### Stale-While-Revalidate

CDN returns stale response immediately while updating in background:

```typescript theme={null}
@api({
  httpMethod: 'GET',
  cacheControl: {
    visibility: 'public',
    maxAge: 60,
    staleWhileRevalidate: 300,  // Allow stale for 5 minutes
  }
})
async getProducts() {
  return this.findMany({});
}
```

**CloudFront Support**: AWS CloudFront supports `stale-while-revalidate`.

## Vary Header

Uses different caches based on request headers.

```typescript theme={null}
@api({
  httpMethod: 'GET',
  cacheControl: {
    visibility: 'public',
    maxAge: 300,
    vary: ['Accept-Language'],  // Separate cache by language
  }
})
async getProducts(ctx: Context) {
  const locale = ctx.locale;  // 'ko' or 'en'
  return this.getLocalizedProducts(locale);
}
```

**Result**:

```
GET /api/products (Accept-Language: ko) → Korean response cached
GET /api/products (Accept-Language: en) → English response cached
```

## Global Handler

You can apply Cache-Control to all APIs at once.

```typescript theme={null}
// sonamu.config.ts
import { CachePresets, type SonamuConfig } from "sonamu";

export const config: SonamuConfig = {
  server: {
    apiConfig: {
      cacheControlHandler: (req) => {
        // Only handle API requests
        if (req.type !== 'api') {
          return undefined;
        }

        // Handle by HTTP method
        if (req.method !== 'GET') {
          return CachePresets.noStore;  // Mutations use no-store
        }

        // Handle by API path
        if (req.path.includes('/admin/')) {
          return CachePresets.noStore;  // Admin APIs use no-store
        }

        if (req.path.includes('/private/')) {
          return CachePresets.private;  // Private APIs use private
        }

        // Default: 1 minute cache
        return CachePresets.shortLived;
      }
    }
  }
};
```

### Using API Object

```typescript theme={null}
cacheControlHandler: (req) => {
  if (req.type !== 'api') return undefined;

  // Use ExtendedApi information
  if (req.api) {
    const { modelName, methodName } = req.api;

    // Long cache for specific Model only
    if (modelName === 'ConfigModel') {
      return CachePresets.longLived;
    }

    // No caching for specific methods
    if (methodName.startsWith('create') || methodName.startsWith('update')) {
      return CachePresets.noStore;
    }
  }

  return CachePresets.shortLived;
}
```

## Using with BentoCache

Combining server cache with HTTP cache provides maximum performance.

```typescript theme={null}
@cache({ ttl: "10m", tags: ["product"] })  // Server cache: 10 minutes
@api({
  httpMethod: 'GET',
  cacheControl: { maxAge: 60 }  // HTTP cache: 1 minute
})
async getProducts() {
  return this.findMany({});
}
```

**Effect**:

1. **0-60 seconds**: Browser cache (no server request)
2. **60 seconds - 10 minutes**: Server request but uses BentoCache (no DB query)
3. **After 10 minutes**: DB query → cache again

```mermaid theme={null}
graph LR
    A[Browser] -->|60 seconds| B[HTTP Cache]
    B -->|Expired| C[Server]
    C -->|10 minutes| D[BentoCache]
    D -->|Expired| E[DB]
```

## Precautions

<Warning>
  **Precautions when using API Cache-Control**:

  1. **Only cache GET**: POST, PUT, DELETE must use `noStore`
     ```typescript theme={null}
     // ❌ Dangerous
     @api({ httpMethod: 'POST', cacheControl: { maxAge: 60 } })

     // ✅ Safe
     @api({ httpMethod: 'POST', cacheControl: CachePresets.noStore })
     ```

  2. **Private data must be private**: Prevent CDN caching
     ```typescript theme={null}
     // ❌ Dangerous: Personal data cached on CDN
     @api({ cacheControl: { visibility: 'public', maxAge: 60 } })
     async getMyOrders(ctx: Context) { ... }

     // ✅ Safe
     @api({ cacheControl: CachePresets.private })
     async getMyOrders(ctx: Context) { ... }
     ```

  3. **Short TTL for frequently changing data**: Prevent stale data
     ```typescript theme={null}
     // ❌ Stock is real-time but cached for 1 hour
     @api({ cacheControl: CachePresets.longLived })
     async getStock() { ... }

     // ✅ Short TTL or no-cache
     @api({ cacheControl: CachePresets.noCache })
     async getStock() { ... }
     ```

  4. **Be careful with authenticated APIs**: Consider Authorization header
     ```typescript theme={null}
     @api({
       cacheControl: {
         visibility: 'private',  // private required
         maxAge: 60,
         vary: ['Authorization'],  // Separate cache by auth token
       }
     })
     async getMyData(ctx: Context) { ... }
     ```
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="What is Cache-Control?" icon="circle-question" href="/en/advanced-features/cache-control/what-is-cache-control">
    HTTP caching fundamentals
  </Card>

  <Card title="Cache Presets" icon="list" href="/en/advanced-features/cache-control/cache-presets">
    Predefined cache configurations
  </Card>

  <Card title="Using in SSR" icon="window" href="/en/advanced-features/cache-control/using-in-ssr">
    Apply Cache-Control to SSR pages
  </Card>
</CardGroup>
