# Migrating handler patterns in Functions

Azion Functions supports two handler patterns: **ES Modules** (recommended) and **Service Worker** (legacy). This guide explains the differences between them and how to migrate your existing functions to the ES Modules pattern.

## Supported patterns

### ES Modules (recommended)

The ES Modules pattern is the recommended way to structure your functions on Azion. It provides a modern, clean syntax with native support in production and better performance.

```javascript
export default {
  fetch: (request, env, ctx) => {
    return new Response('Hello World');
  },
  firewall: (request, env, ctx) => {
    // Firewall logic
    ctx.deny();
  }
};
```

### Service Worker (legacy)

The Service Worker pattern is maintained for backward compatibility. If you're using this pattern, Azion recommends migrating to ES Modules.

```javascript
addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

addEventListener('firewall', (event) => {
  // Firewall logic
  event.deny();
});

async function handleRequest(request) {
  return new Response('Hello World');
}
```

---

## Handler parameters

### `fetch(request, env, ctx)`

| Parameter | Type | Description |
|---|---|---|
| `request` | [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) | The incoming HTTP request object |
| `env` | Object | Environment variables and bindings |
| `ctx` | Object | Execution context. Use `ctx.waitUntil(promise)` to extend the function's lifetime for async tasks |

### `firewall(request, env, ctx)` — ES Modules

| Parameter | Type | Description |
|---|---|---|
| `request` | [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) | The incoming HTTP request object |
| `env` | Object | Environment variables and bindings |
| `ctx` | Object | Execution context. Call `ctx.deny()` to block the request immediately. If `ctx.deny()` is not called, the request continues to the `fetch` handler |

### Firewall event — Service Worker

| Property | Description |
|---|---|
| `event.request` | Access to the Request object |
| `event.deny()` | Blocks the request immediately. If not called, the request continues to the `fetch` handler |

---

## Migrating from Service Worker to ES Modules

### Basic fetch handler

**Before (Service Worker):**

```javascript
addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  const url = new URL(request.url);

  if (url.pathname === '/api/hello') {
    return new Response(JSON.stringify({ message: 'Hello World' }), {
      headers: { 'Content-Type': 'application/json' }
    });
  }

  return new Response('Not Found', { status: 404 });
}
```

**After (ES Modules):**

```javascript
export default {
  fetch: async (request, env, ctx) => {
    const url = new URL(request.url);

    if (url.pathname === '/api/hello') {
      return new Response(JSON.stringify({ message: 'Hello World' }), {
        headers: { 'Content-Type': 'application/json' }
      });
    }

    return new Response('Not Found', { status: 404 });
  }
};
```

### Firewall handler

**Before (Service Worker):**

```javascript
addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

addEventListener('firewall', (event) => {
  const clientIP = event.request.headers.get('X-Forwarded-For');
  const userAgent = event.request.headers.get('User-Agent');

  // Block bot requests
  if (userAgent && userAgent.includes('bot')) {
    event.deny();
    return;
  }

  // Block specific IPs
  if (clientIP === '192.168.1.100') {
    event.deny();
    return;
  }

  // Allow request to continue to fetch handler
});

async function handleRequest(request) {
  return new Response('Hello World');
}
```

**After (ES Modules):**

```javascript
export default {
  fetch: async (request, env, ctx) => {
    return new Response('Access granted');
  },

  firewall: async (request, env, ctx) => {
    const clientIP = request.headers.get('X-Forwarded-For');
    const userAgent = request.headers.get('User-Agent');

    // Block bot requests
    if (userAgent && userAgent.includes('bot')) {
      ctx.deny();
      return;
    }

    // Block specific IPs
    if (clientIP === '192.168.1.100') {
      ctx.deny();
      return;
    }

    // Allow request to continue to fetch handler
    return;
  }
};
```

### Using `waitUntil` for async tasks

```javascript
export default {
  fetch: async (request, env, ctx) => {
    // Use waitUntil for async tasks that should not block the response
    ctx.waitUntil(logRequest(request));

    return new Response('Hello World');
  }
};

async function logRequest(request) {
  console.log(`Request to: ${request.url}`);
}
```

### Advanced firewall with path-based rules

```javascript
export default {
  fetch: async (request, env, ctx) => {
    return new Response('Access granted');
  },

  firewall: async (request, env, ctx) => {
    const url = new URL(request.url);
    const userAgent = request.headers.get('User-Agent');
    const clientIP = request.headers.get('X-Forwarded-For');

    // Block bot requests
    if (userAgent && userAgent.includes('bot')) {
      ctx.deny();
      return;
    }

    // Restrict access to admin paths by IP range
    if (url.pathname.startsWith('/admin')) {
      if (!clientIP || !clientIP.startsWith('192.168.')) {
        ctx.deny();
        return;
      }
    }

    // Allow request to continue to fetch handler
    return;
  }
};
```

---

## Unsupported patterns

The following patterns are **not** supported by Azion Functions. If your code uses any of them, migrate to the ES Modules pattern.

```javascript
// ❌ Direct function export
export default function(request) {
  return new Response('Hello');
}

// ❌ Named exports
export function fetch(request) {
  return new Response('Hello');
}

// ❌ No export
function handleRequest(request) {
  return new Response('Hello');
}
```

---

## Troubleshooting

### "Unsupported handler pattern detected"

This error appears when your code doesn't follow any of the supported patterns. To resolve it, migrate to the ES Modules pattern:

```javascript
export default {
  fetch: async (request, env, ctx) => {
    return new Response('Hello World');
  }
};
```

Alternatively, use the Service Worker pattern as a temporary measure:

```javascript
addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
  return new Response('Hello World');
}
```

---

## Related resources

- [Functions first steps](/en/documentation/products/guides/edge-functions/first-steps/)
- [Functions overview](/en/documentation/products/build/applications/functions/)
- [Azion Runtime API reference](/en/documentation/runtime/overview/)
- [Functions with Firewall](/en/documentation/products/guides/edge-functions/firewall/)