---
name: azion-write-and-test-a-function
description: >-
  Write the ES Modules handler of a function in Azion Console, read its response with Preview deployment, and find its log output.
---

# Write and test a function

You write the code of a function in JavaScript and read the response it returns, both in Azion Console. The function serves no traffic until an application or a firewall runs it.

Writing a function does not run it. A function runs when an application or a firewall instantiates it and a [Rules Engine](/en/documentation/platform/applications/rules-engine/) rule selects that instance. Four other pages carry the parts this one leaves out:

- For a guided first run, from an empty account to a response, refer to [Functions quickstart](/en/documentation/platform/functions/quickstart/).
- For what invokes a function and where it executes, refer to [How Functions works](/en/documentation/platform/functions/how-it-works/).
- To run the function on an application, refer to [Run a function on an application](/en/documentation/guides/application-development/functions-and-runtime/serverless-functions/).
- To run the function on a firewall, refer to [Run a function on a firewall](/en/documentation/guides/application-development/functions-and-runtime/firewall/).

---

## Prerequisites

- An Azion account. To create one, refer to [How to create an account on Azion](/en/documentation/fundamentals/creating-account/).
- The **Edit Functions** permission on the account. It also requires the permission **View Functions**. Refer to [Teams Permissions](/en/documentation/fundamentals/teams-permissions/).
- Familiarity with JavaScript, including [Strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode).

---

## Write the handler

A function exports a default object, and each property of that object is a handler. Where the instance is bound decides which handler runs: an instance on an application runs the `fetch` handler, and an instance on a firewall runs the `firewall` handler. This is the ES Modules pattern, the recommended way to structure a function.

A `fetch` handler receives the request and returns a `Response`:

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

The handler takes three parameters:

| Parameter | Type      | What it carries                                                                                                                 |
| --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `request` | `Request` | The incoming HTTP request: its method, its URL, its headers, and its body.                                                      |
| `env`     | Object    | The [environment variables](/en/documentation/platform/functions/environment-variables/) and bindings of the function.          |
| `ctx`     | Object    | The execution context. `ctx.waitUntil(promise)` extends the lifetime of the execution past the point where the handler returns. |

`request` is the Web `Request` object. The handler branches on `request.method`, reads a cookie from `request.headers`, or parses the path with `new URL(request.url)`. To call another system while the handler runs, use [Fetch API](/en/documentation/devtools/runtime/api-reference/fetch/).

The response carries whatever headers the code sets. This handler returns markup and declares the format in the `content-type` header:

```javascript
const html = `<!DOCTYPE html>
<body>
  <h1>Hello World</h1>
  <p>This markup was generated by Azion Functions.</p>
</body>`;

export default {
  async fetch(request, env, ctx) {
    return new Response(html, {
      headers: {
        'content-type': 'text/html;charset=UTF-8',
      },
    });
  },
};
```

A `firewall` handler decides whether the request reaches the application. `ctx.deny()` blocks the request immediately, and the request continues to the `fetch` handler when the code does not call it:

```javascript
export default {
  async firewall(request, env, ctx) {
    const userAgent = request.headers.get('User-Agent');

    if (userAgent && userAgent.includes('bot')) {
      ctx.deny();
    }
  },
};
```

Azion Runtime makes strict mode the default and required option for the JavaScript of a function. Strict mode turns several silent JavaScript errors into thrown errors, and it blocks syntax that later ECMAScript versions may define. For the Web APIs the runtime implements and the full handler reference, refer to [Azion Runtime](/en/documentation/devtools/runtime/).

> **Note**
>
> Functions also supports the Service Worker pattern, which declares a handler with `addEventListener`. That pattern is the legacy one. To move existing code onto ES Modules, refer to [Migrate handler patterns in Functions](/en/documentation/guides/application-development/functions-and-runtime/migrate-handler-patterns/).

---

## Save the code in Azion Console

The [code editor](/en/documentation/platform/functions/code-editor/) of a function is its **Code** tab in Azion Console. It is built on the Monaco Editor, with syntax highlighting, IntelliSense, and debugging. The context menu of the editor also carries a **Format Document** command. A new function opens with placeholder code that you replace.

To store the handler on a function:

1. **Open the Functions page**

   Access [Azion Console](https://console.azion.com/) > **Products Menu** > **Libraries** > **Functions**.

2. **Open the function**

   Select the function you want to edit. To start a new one, select **+ Function** and enter a name.

3. **Replace the code in the Code tab**

   In the **Code** tab, replace the code with your handler.

4. **Select Save**

Azion Console stores the code and makes the function available to instantiate. An instance names the function instead of holding a copy of it. The new code therefore reaches every instance that binds the function.

The editor also calls the ChatGPT APIs, so code explanation, generation, and refactor run inside the editor. The integration uses the credentials of your own OpenAI account, declared as a comment in the source code. For the credentials and the editor commands, refer to [ChatGPT integration](/en/documentation/platform/functions/ai-integration/).

---

## Test the response with Preview deployment

[Preview deployment](/en/documentation/platform/functions/preview-deployment/) runs the function in Azion Console and shows the response the code returns, with no deployment. It calls `PreviewProvider`, an auxiliary function in the source code that builds a simulated request for the handler. The values inside `PreviewProvider` decide which path through the function runs. Change them and the same function answers a different request.

To read the response before the function serves traffic:

1. **Add a PreviewProvider function to the source code**

   `PreviewProvider` builds the request the preview sends and returns the response the preview renders. For its shape, refer to [PreviewProvider function](/en/documentation/platform/functions/preview-deployment/#previewprovider-function).

2. **Read the output in the code editor**

   The preview renders a `text/html` response as the page and an `application/json` response as the JSON body.

3. **(Optional) Select Open to read the same output in a separate browser tab**

4. **Change the values inside PreviewProvider**

   Change the method, the headers, or the URL of the simulated request to exercise another path through the handler. Read the output again.

The response of the function appears in Azion Console before any request reaches it.

> **Note**
>
> Azion Console renders no preview while the source code carries no `PreviewProvider` function. The preview shows a warning that names the missing function.

---

## Read the logs of a function

A function writes log messages with `console.log`, the same way JavaScript does in a browser. Azion collects that output and delivers it in three places: [Azion CLI](/en/documentation/devtools/cli/), [Data Stream](/en/documentation/platform/data-stream/), and [Real-Time Events](/en/documentation/platform/real-time-events/). Until you open one of them, a function that fails and a function that never runs look the same.

To read the output from a terminal:

```bash
azion logs cells --tail
```

The terminal prints the console messages of the last 5 minutes and keeps printing new ones. Add `--function-id` to restrict the output to one function.

To read the same output in Azion Console, go to **Real-Time Events** and select the **Functions Console** tab. Each entry carries the level of the message, its line, and the identifier of the request that produced it.

When the logs do not explain the behavior, the symptom decides where to look next:

- [Troubleshoot function execution and logs](/en/documentation/platform/functions/troubleshooting/) - the fixes for a function that never runs, stops early, or writes nothing.
- [Debug functions on Data Stream](/en/documentation/guides/platform/observability/debugging-functions-data-stream/) - send the log output of a function to an endpoint you own.
- [Query function logs with GraphQL API](/en/documentation/guides/application-development/functions-and-runtime/debugging-functions-graphql/) - query the events of a function from the GraphQL API.

---

## Next steps

- [Run a function on an application](/en/documentation/guides/application-development/functions-and-runtime/serverless-functions.md): Add the Rules Engine rule that runs the instance, from Azion Console or the Azion API.
- [Run a function on a firewall](/en/documentation/guides/application-development/functions-and-runtime/firewall.md): Instantiate the function on a firewall and add the rule that triggers it.
- [Environment variables](/en/documentation/platform/functions/environment-variables.md): Store configuration and secrets outside the function code, then read each one by key at run time.
- [JavaScript examples](/en/documentation/platform/functions/javascript-examples.md): Function code to adapt for your own logic.
- [Best practices](/en/documentation/platform/functions/best-practices.md): What each part of the handler does, and the design choices that keep an invocation inside its limits.
- [Azion Runtime](/en/documentation/devtools/runtime.md): The Web APIs, the handler signatures, and the Node.js compatibility list.
