Using function proxies with Azure Static Web Apps
Tags: csharp, azure, dotnet, azurestaticwebapps
Published: 2021-02-19 04:18:20

[Azure Static Web Apps](https://azure.microsoft.com/en-us/services/app-service/static/) is a service that provides you with a way to serve static content with an Azure Functions backend API. It's currently in preview, but it already looks promising, giving you a variety of useful features to build a web app. You can pull your code directly from GitHub, then Azure will add a GitHub Actions workflow to build and deploy to Azure Static Web Apps. It truly is a great service, and as of the day of writing this blog post, it's completely free, since it's on preview. You can read about all of Azure Static Web Apps features [here](https://docs.microsoft.com/en-us/azure/static-web-apps/overview#key-features).

The integrated API is a nice way to not worry about things like CORS, since it will be hosted on the same domain as the website on the `/api` route. However, it currently imposes some limitations on what you can do with your function app:
- Not every language is supported. You can only create your function app on JavaScript, C# and Python. This will probably be fixed in the future, as there's no good reason to not support other languages IMO.
- You can only add HTTP triggers. This one probably is going to remain this way, since it's purpose is to process requests from the frontend, but maybe I'm wrong. Input and output bindings works normally.
- Logs are only available through Application Insights. This means that you can't really troubleshoot the resource without creating an Application Insights resource along with your Static Web App. I'm not sure if this means that you cannot add other log providers through DI, but I'll investigate further later.

With that in mind, being limited to only HTTP triggers is not so bad, as you can use the Static Web App's function app as a proxy to other services using **function proxies**.

## Function Proxies

Azure Function Proxies allow you to specify routes that will be implemented by another resource, with the option to override the HTTP method, the query string or the request headers. It's a pretty good tool for breaking a big service into smaller parts in a serverless architecture.

To add a proxy to your function app, you just need to define a `proxies.json` file on your project (Don't forget to copy the file to the build output!) and configure it according to your needs. This is how a proxies file look like:

```json
{
    "$schema": "http://json.schemastore.org/proxies",
    "proxies": {
        "<proxy-name>": {
            "matchCondition": {
                "route": "/api/todo"
            },
            "backendUri": "%service-url%/api/todo",
            "requestOverrides": {
                "backend.request.querystring.<querystring-name>": "example-value",
                "backend.request.header.<header-name>": "example-value",
            },
            "responseOverrides": {
                "response.body": {
                    "message": "Hello, world!"
                },
                "response.statusCode": "418"
            }
        }
    }
}
``` 

The example above does some things that are worth explaining, we're doing the following:
- Creating a proxy with the name `<proxy-name>` that will be triggered when the app receives a request on the `/api/todo` route. You can also specify HTTP methods on the match condition.
- Setting the `backendUri` to the service that implements our functionality. This is what will be called when the request matches the condition defined. `%service-url%` is the syntax used for configuration (your `local.settings.json` file or your environment variables).
- Overriding the request's `<header-name>` header and `<querystring-name>` query string key with the value 'example-value'.
- Overriding the response's body and status code.

There are more things that can be configured in your proxies file, you can read more about it [here](https://docs.microsoft.com/en-us/azure/azure-functions/functions-proxies#modify-requests-responses).

## Function Proxies on SWAs

Since SWA allows us to use its function app as a proxy, I made a static app that calls two other function apps through function proxies. This is how our services will communicate:

![azf-prx-swa.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1613698087781/h5q44syBO.png)

The code for this blog post is available on this GitHub repository:

{% github https://github.com/danielreis-dev/function-proxies-on-static-web-apps %}

### [Catalog Service](https://github.com/danielreis-dev/function-proxies-on-static-web-apps/tree/master/catalog)

This function app has one HTTP function that returns an array of catalog items. You can page the results by changing the `index` value on the query string.

### [Orders Service](https://github.com/danielreis-dev/function-proxies-on-static-web-apps/tree/master/orders)

This function app also has one HTTP function that return an order creation result. If you send any order item on the request, you'll receive a 200 OK with an order ID and a status. If you send an empty array, you'll receive a 400 BAD REQUEST.

### [SWA Frontend](https://github.com/danielreis-dev/function-proxies-on-static-web-apps/tree/master/app)

This is a Blazor WebAssembly app that allows you to browse through catalog items and add them to an in-memory shopping cart. After that, you can finish your shopping by creating an order. It also has a `routes.json` configured to serve the same file (`index.html`) on every route, this is required and you can read more about it  [here](https://docs.microsoft.com/en-us/azure/static-web-apps/deploy-blazor#fallback-route).

###  [SWA Backend](https://github.com/danielreis-dev/function-proxies-on-static-web-apps/tree/master/bff) 

This is a simple function app that includes a [`proxies.json`](https://github.com/danielreis-dev/function-proxies-on-static-web-apps/blob/master/bff/proxies.json) file that maps to the two other function apps' `backendUri` using environment variables (`%CONNECTIONSTRINGS__CATALOG%` and `%CONNECTIONSTRINGS__ORDERS%`). Both proxies override the query string's `code` value to include the function app's function key that is required for authentication in production, we'll see how to set these values later on, but they're not required in your development environment.

> **NOTE**: Currently, you can't add configuration settings on a SWA that doesn't have any functions in it, so I also included a dummy HTTP function to allow configuration in production. I believe that it should be a valid scenario to only have proxies on your backend function app, so I created an [issue](https://github.com/Azure/static-web-apps/issues/289) on the Static Web App's GitHub repository. I'll update this post with any info they provide me.

On the  [`local.settings.json`](https://github.com/danielreis-dev/function-proxies-on-static-web-apps/blob/master/bff/local.settings.json) file, there are two **important** settings that are worth explaining:

```
    "AZURE_FUNCTION_PROXY_DISABLE_LOCAL_CALL": true,
    "SERVICE__CATALOG__KEY": "catalog_key" ,
    "SERVICE__ORDERS__KEY": "orders_key"
  },
  "Host": {
    "CORS": "*"
```

- `AZURE_FUNCTION_PROXY_DISABLE_LOCAL_CALL`: For some reason, when calling your proxy in development gives an error saying that a recursive call was detected, even though it's correctly configured to call another function app listening in another port. Setting this variable to `true` fixes the problem.
- `CORS:*`: Since we're running this app locally, you'll receive an error if this setting is not present. That's because the Blazor app is running on a different port than the Backend function app. It will work correctly on production.

## Running the application

To make testing easier, I've added a `tye.yaml` file that spins up all four services with one command. It also gives us a nice dashboard with logs and other useful info about the running services. If you never heard about  [Tye](https://github.com/dotnet/tye), you should really check it out, it's a wonderful tool that helps a lot with the development of microservices, but all you need to know for now is how to install the tool (`dotnet tool install -g Microsoft.Tye --version "0.6.0-alpha.21070.5"`) and run it (`tye run`).


```yaml
name: my-store
services:
- name: frontend
  project: app/MyStore.App.csproj
  bindings:
    - protocol: http
- name: bff
  azureFunction: bff/
  bindings:
  - protocol: http
    port: 7070
- name: catalog
  azureFunction: catalog/
  bindings:
    - protocol: http
      connectionString: ${protocol}://${host}:${port}
- name: orders
  azureFunction: orders/
  bindings:
    - protocol: http
      connectionString: ${protocol}://${host}:${port}
``` 


The YAML file configures the SWA backend app to always run on the port 7070 and assign the other three services random ports. It also sets the environment variables required for the SWA backend to call the Orders and Catalog service, that happens through the `connectionString` binding defined on the file. When running the command `tye run`, you should see the following:

![tye run](https://cdn.hashnode.com/res/hashnode/image/upload/v1613700895681/XtzaDw-_6.png)

The dashboard is hosted on `http://localhost:8000`, it should list you all four services:

![tye dashboard](https://cdn.hashnode.com/res/hashnode/image/upload/v1613701058509/ZpdG-hWvp.png)

Finally, when you access the frontend app, you should be able to add items to your card and finish your order!

{% youtube _ngGj506e7w %}

> **NOTE**: For some reason, requests going through the proxy always take **at least** two seconds. Do not panic, as this does not seem to be happening in production.

## Creating and configuring your Azure Static Web App

Now that you've seen the app in action, it's time to deploy it to production and configure it. If you need instructions on how to create an Azure Static Web App, check out this Microsoft [article](https://docs.microsoft.com/en-us/azure/static-web-apps/get-started-portal).

Now we need to configure our SWA function app and add our function keys and URLs. You can get the function key for each function app running the following commands on the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/) :

```shell
az functionapp function keys list -g <ResourceGroupName> -n <CatalogFunctionAppName> --function-name get-catalog
```
```shell
az functionapp function keys list -g <ResourceGroupName> -n <OrdersFunctionAppName> --function-name create-order
``` 

These should be added in the Configuration section as `SERVICE__CATALOG__KEY` and `SERVICE__ORDERS__KEY`, respectively. As for the URLs, they should be add as `CONNECTIONSTRINGS__CATALOG` and `CONNECTIONSTRINGS__ORDERS`. You can get them using the following command:

```shell
az functionapp config hostname list -g <ResourceGroupName> --webapp-name <FunctionAppName>
```

After all that, your should be up and running, and your Configuration section should be looking like this:

![swa configuration section](https://cdn.hashnode.com/res/hashnode/image/upload/v1613704182443/iCXs8218W.png)

## Conclusion

I hope you found this post useful. Static Web Apps have amazing potential and I'm hopeful the service will only get better, if you have any questions or feedback, please let me know in the comments!