Building a "redirector" with Airtable and Azure Functions
Tags: c, azure, azurefunctions
Published: 2017-10-08 15:22:35

---
title: "Building a \"redirector\" with Airtable and Azure Functions"
published: true
tags: C#,Azure,Azure Functions
canonical_url: https://blog.jonstodle.com/building-a-redirecter-with-airtable-and-azure-functions/
---

Once in a while I want a simple url alias. Say I want the link to my Stack Overflow profile, but I want a simple, short url that's easy to remember. I want an easy way to register the complex and specify a simple and short alias url that redirects to my Stack Overflow profile.

That's what we're going to build here.

There are two parts to the application. The first part is the storage: I need to store the url aliases somewhere. I've opted for Airtable. Airtable is simple online table storage; it's like a light weight version of Excel. Airtable also have a nice JSON API to access the tables in your account.

The second part is the code itself. This is basically just a simple handler for incoming requests. Take the path after the domain and look it up in the table of aliases. If there's a match, redirect to the specified url. For this part I've chosen to use Azure Functions.

With Azure Functions you only pay for each execution of the function, but the first 1 million executions each month are free(!). That means it's free for my small use case.

As with Azure Web Apps (Azure Functions is just a Web App under the covers), a function is turned off when it's not used. In contrast with a web app the function is quite fast to spin up. A web app might take 10-20 seconds to spin up, but the function responds in just 2-5 seconds. This makes the user experience better, as they don't have to wait as long for the redirect.

Let's set things up:

# Airtable

Go register an account at [Airtable.com](https://airtable.com/). First we create a _team_. A team can contain multiple users which have access to the same resources in Airtable. We use it organize our bases. I've created a team called _blog.jonstodle.com_ for demonstration purposes.

Next we create a _base_. A base is like an Excel document, it can contain multiple tables of data. Click the plus icon and select _Start from scratch_. I've called the base _Redirects_.

![Airtable team](https://blog.jonstodle.com/content/images/2017/09/Screenshot-170918-104327.png)

Click on the icon to open the base. Right click on the table tab (where it says _Table 1_) and rename it to _Paths_. Next we'll define three columns with the following types. Right click the existing column headers and click on _Customize field type_ to change the name and field type:

<style>  
    thead{
        font-weight: bolder;
    }
</style>

| Content type | Name |
| Single line text | Path |
| URL | Url |
| Single line text | Description |

I've also added some data to work with.

![Redirects table](https://blog.jonstodle.com/content/images/2017/09/Screenshot-170918-105256.png)

To have a look at how to access the data through the API, click on the question mark in the upper right corner and click on _API documentation_.

![](https://blog.jonstodle.com/content/images/2017/09/Screen-Shot-2017-09-19-at-10.15.28.png)

On this page you'll be able to see the API key (by toggling _show API key_ in the upper right corner) and the REST endpoints to get the data from the base. There are some nice examples of responses you'll get from the API, with live data from your actual base.

The important thing wee need here is the base url ([https://api.airtable.com](https://api.airtable.com)) and the id of the base (the part after _v0_ in the path). In my case the endpoint to my base is [https://api.airtable.com/v0/appxpeLm4zw7rVbMV](https://api.airtable.com/v0/appxpeLm4zw7rVbMV).

Note down the API key and the endpoint to your base for later.

That's everything we need from Airtable, so let's head over to the Azure Function we're going to use.

# Azure Function

Launch the [Azure Portal](https://portal.azure.com/) and click the plus sign in the sidebar. Under the _Compute_ category, you'll find _Function App_. Fill out the fields, just make sure _Hosting Plan_ is set to _Consumption Plan_. Below's how I filled it out.

![Azure Function setup](https://blog.jonstodle.com/content/images/2017/09/Screenshot-170918-111244.png)

Click on _Create_ and Azure will deploy a new function app for you. After it's done deploying, you'll be able to find the function app in the sidebar under _More Services_ \> _Function Apps_.

Select your function app in the list, and you'll be presented with this:

![Function app overview](https://blog.jonstodle.com/content/images/2017/09/Screenshot-170918-112337.png)

## Storing the API key

It's not good practice to store API keys directly in your code, so we'll add it as an environment variable. That way it get's stored in a secure way by Azure and we can get the key at run time.

Click on _Application settings_ under _Configured features_. Under _Application settings_, click on _Add new setting_ and give it the name `AirtableApiKey` and paste the API key into the value field.

![](https://blog.jonstodle.com/content/images/2017/09/Screen-Shot-2017-09-19-at-10.49.30.png)

## Creating the function

Click on the blue plus icon on the _Functions_ node to the right and click on _Custom function_, hidden below _Get started on your own_. Click on _HttpTrigger - C#_, name you function _Redirect_ and set _Authorization level_ to _Anonymous_. Click on _Create_.

![](https://blog.jonstodle.com/content/images/2017/09/Screen-Shot-2017-09-19-at-10.46.55.png)

You'll be met with a text editor containing the contents of _run.csx_, a C# script. Remove all the code, we're going to do our own thing. Before we start adding code however, we need to specify the route template.

## Configuring the route

Click on _Integrate_ below the _HttpRedirect_ function in the sidebar. Set _Allowed HTTP methods_ to _Selected methods_ and remove the check mark from all but _GET_ in the _Selected HTTP methods_ section that appears. This ensures only `HTTP GET` requests are accepted. Not terribly important, but it's a good habit.

Next, set the _Route template_ to `{path}`. This specifies we want to capture the path after the domain and store it in a variable called `path`. `path` will also be the name of the parameter that's passed into our handler.

![Configuring the route](https://blog.jonstodle.com/content/images/2017/09/Screenshot-170918-131339.png)

## Removing the _/api_ part of the path

By default you can access your function by going to _\<appname\>.azurewebsites.net/api/_, but we don't want the _/api_ part of the url.

This can be removed by editing the _host.json_ file of the app. The easiest way for us to edit this is to go to _\<appname\>.scm.azurewebsites.net_ and click on _Debug console_ \> _CMD_. Then navigate to _site_ \> _wwwroot_ and click on the pencil icon next to the file _host.json_. Inside this file, add the following JSON:

```
{
    "http": {
        "routePrefix": ""
    }
}
```

Then click on _Save_ to the upper left.

## The function code

Let's go back to the function app in the Azure Portal and click on _HttpRedirect_ in the sidebar. We'll start adding the code to handle the requests:

We'll reference _Json.NET_ to make handling the JSON responses from Airtable easy.

```
#r "Newtonsoft.Json"
```

Then we add the necessary `using` statements.

```
using static System.Environment;  
using System.Net.Http;  
using System.Net.Http.Headers;  
using System.Net;  
using Newtonsoft.Json;  
using System.Threading.Tasks;
```

We'll also add some model classes to represent the responses from Airtable. The `Record` class represents a row in our Airtable table. It contains some metadata about the record, while the data we entered in the table is stored in the `Fields` property.

```
public class Record  
{
    public string Id { get; set; }
    public Redirect Fields { get; set; }
    public DateTimeOffset CreatedTime { get; set; }
}
```

The `Redirect` class represents the data we entered in the table.

```
public class Redirect  
{
    public string Path { get; set; }
    public string Url { get; set; }
    public string Description { get; set; }
}
```

Next, it's the `Run` function. This will be executed when someone navigates to our function app.

he part that's interesting to us is the `path` parameter. This will contain the path the visitor navigated to. If the user navigates to _\<appname\>.azurewebsites.net/githubt_, the `path` parameter will contain the value `github`.

```
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, string path, TraceWriter log)  
{
```

We start by creating an instance of `HttpClient` and specify the base url.

```
var client = new HttpClient
    {
        BaseAddress = new Uri("https://api.airtable.com/")
    };
```

We set the _Authorization_ header to contain the API key we got from Airtable. We retrieve the key from the environment variable we set up earlier.

```
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GetEnvironmentVariable("AirtableApiKey"));
```

Next we retrieve the correct record from the Airtable table. We access the base using the id we got from the API documentation, we then specify we want to access the `Paths` table in the base. Lastly we specify a filter to to only receive the records we want.

You can read about the filter syntax in the [Airtable Docs](https://support.airtable.com/hc/en-us/articles/203255215-Formula-Field-Reference)

```
var response = await client.GetStringAsync($"/v0/appxpeLm4zw7rVbMV/Paths?filterByFormula=AND(LOWER({{Path}}) = '{WebUtility.UrlEncode(path.ToLowerInvariant())}')");
```

After getting the response, we create an anonymous object (remember those?) which provides a definition for Json.NET when parsing the JSON response.

```
var definition = new { Records = new List<Record>() };
    var result = JsonConvert.DeserializeAnonymousType(response, definition);
```

We then try to get the data from the first record. If there isn't one, `redirect` will be null and we return a `404` error.

```
var redirect = result.Records.FirstOrDefault()?.Fields;

    if(redirect == null)
    {
        return new HttpResponseMessage(HttpStatusCode.NotFound); // Return 404
    }
```

If we found data we'll create a response message which specifies the browser should redirect (`301`). We set the _Location_ header to be the URL the browser should redirect to, and return the response message.

```
var responseMessage = new HttpResponseMessage(HttpStatusCode.Redirect);
    responseMessage.Headers.Location = new Uri(redirect.Url);

    return responseMessage;
}
```

<details> <br>
  <summary>See complete code</summary></details>

```
#r "Newtonsoft.Json"

using static System.Environment;  
using System.Net.Http;  
using System.Net.Http.Headers;  
using System.Net;  
using Newtonsoft.Json;  
using System.Threading.Tasks;

public class Record  
{
    public string Id { get; set; }
    public Redirect Fields { get; set; }
    public DateTimeOffset CreatedTime { get; set; }
}

public class Redirect  
{
    public string Path { get; set; }
    public string Url { get; set; }
    public string Description { get; set; }
}

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, string path, TraceWriter log)  
{
    var client = new HttpClient
    {
        BaseAddress = new Uri("https://api.airtable.com/")
    };
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GetEnvironmentVariable("AirtableApiKey"));

    var response = await client.GetStringAsync($"/v0/appxpeLm4zw7rVbMV/Paths?filterByFormula=AND(LOWER({{Path}}) = '{WebUtility.UrlEncode(path.ToLowerInvariant())}')");

    var definition = new { Records = new List<Record>() };
    var result = JsonConvert.DeserializeAnonymousType(response, definition);

    var redirect = result.Records.FirstOrDefault()?.Fields;

    if(redirect == null)
    {
        return new HttpResponseMessage(HttpStatusCode.NotFound); // Return 404
    }

    var responseMessage = new HttpResponseMessage(HttpStatusCode.Redirect);
    responseMessage.Headers.Location = new Uri(redirect.Url);

    return responseMessage;
}
```

# And that's it!

By adding paths to your Airtable table _Paths_, you'll add new redirects to your "redirector". You can do this through the web UI or the excellent apps they have for iOS and Android.

Happy coding!