By leveraging custom handlers in Azure Functions, we can use any programming language to build serverless applications and host them in Azure.Having the freedom to use your preferred programming language to craft Azure Functions is awesome. Developers can use their existing knowledge, tools, and frameworks. The integration with the surrounding runtime is done via a thin configuration layer.

For example, this post demonstrates how to build an Azure Functions project with a simple HTTP API written in Go. To keep things simple, we will use Gin, a minimalistic web framework for Go.

Required tools

This article assumes that the following software is installed and configured on your development machine:

Additionally, access to an Azure subscription is required. If you don’t have a subscription yet, consider creating an account for free now.

Setup the Azure Functions project

Although you can use Visual Studio Code to spin up new Azure Functions projects, I encourage you to use Azure Functions Core Tools directly. The func CLI is straightforward and provides decent documentation for all its commands.

cd my-projects

# create a project dir and cd into it
mkdir functions-with-go
cd functions-with-go

# initialize an Azure Functions project for custom handlers
func init --worker-runtime custom

Setup the Go project

For the sake of this article, we will spin up the Go project also in the project folder. For real-world implementations, you should consider establishing a more feasible project structure 🙃

# cd into the project dir
cd my-projects/functions-with-go

# Initialize the Go project
go mod init fn

# Add Gin as a dependency
go get -u github.com/gin-gonic/gin

# create the source file
mkdir cmd
touch cmd/api.go

Implement the Web API

We will build a simple API that will return either a list of products or a single product by a given identifier for demonstration purposes. The entire source code for the API goes to cmd/api.go:

package main

import (
    "net/http"
    "os"
    "strconv"

    "github.com/gin-gonic/gin"
)

var products map[int]Product = map[int]Product{
    1: {"Milk"},
    2: {"Butter"},
}

type Product struct {
    Name string
}

func getProducts(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, products)
}

func getProduct(c *gin.Context) {
    idParam := c.Param("id")
    id, err := strconv.Atoi(idParam)
    if err != nil {
        c.String(http.StatusBadRequest, "invalid product identifier")
        return
    }
    p, ok := products[id]
    if !ok {
        c.String(http.StatusNotFound, "Product not found")
        return
    }
    c.IndentedJSON(http.StatusOK, p)

}

func get_port() string {
    port := ":8080"
    if val, ok := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT"); ok {
        port = ":" + val
    }
    return port
}

func main() {
    r := gin.Default()
    r.GET("/api/products", getProducts)
    r.GET("/api/products/:id", getProduct)
    r.Run(get_port())
}

Besides the actual API implementation, take a look at the get_port method. It tries to load the port from the FUNCTIONS_CUSTOMHANDLER_PORT environment variable. Azure Functions runtime will store the desired port in this environment variable. If running the API standalone, it will start using the default port 8080.

Build the GO app for local execution

To run our Functions app locally, compile the go application using the following command:

cd my-projects/functions-with-go
go build ./cmd/api.go

# an executable will be generated side by side
# api on Linux and macOS
# api.exe on Windows

Create custom handlers

Custom Handlers are nothing more than a JSON document responsible for configuring request forwarding to your actual application. In its simplest form, we create custom handlers with matching names and HTTP verbs for all our operations. Before we configure HTTP request forwarding, let’s use the func CLI again and create the necessary handlers:

cd my-projects/functions-with-go

func new -l Custom -t HttpTrigger -n products -a anonymous
func new -l Custom -t HttpTrigger -n product-by-id -a anonymous

For our products function, we have to do just a single customization. Remove the HTTP verb post from ./products/function.json. After the modification, it should look like this:

{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

For the product-by-id function, we have to remote the HTTP verb post too. Additionally, provide the custom route for the function as shown here:

{
  "bindings": [
    {
      "authLevel": "Anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get"
      ],
      "route" : "products/{id:int}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

The final step is to modify the host.json and instruct Azure Functions runtime to

  • forward all HTTP requests to our GO API using enableForwardingHttpRequest
  • use the executable produced by the GO compiler in the previous step using defaultExecutablePath
{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[2.*, 3.0.0)"
  },
  "customHandler": { 
    "enableForwardingHttpRequest": true,
    "description": {
      "defaultExecutablePath": "api",
      "workingDirectory": "",
      "arguments": []
    }
  }
}

Note: If you are working on Windows set defaultExecutablePath to api.exe.

Test Azure Functions locally

To test the Azure Functions app on your local machine, again use func CLI:

cd my-projects/functions-with-go

func start

# omitted

# Functions:
#  product-by-id: [GET] http://localhost:7071/api/products/{id:int}
#  products: [GET] http://localhost:7071/api/products

# omitted

At this point, you can spin up a new terminal instance and use a tool like curl to test the app:

# send a GET request to receive all products
curl http://localhost:7071/api/products

# {
#   "1": {
#     "Name": "Milk"
#   },
#   "2": {
#     "Name": "Butter"
#   }
# }

# send a get request to get a single product
curl http://localhost:7071/api/products/2

# {
#   "Name": "Butter"
# }

Deploy the app to Azure

To bring the app to Azure Functions, we have to provision some infrastructure first, and ensure that our Go app is compiled for the desired os / architecture.

Create required services in Azure

To spin up the required services in Azure, we will use Azure CLI. If you are not familiar with Azure CLI 2.0, consider using Azure Portal or a different management interface to provision and configure everything.

# login
az login

# list subscriptions
az account list -o table

# set active subscription
az account set --subscription <SUBSCRIPTION_ID>

# create an Azure Resource Group 
az group create -n rg-functions-with-go \
  -l germanywestcentral

# create an Azure Storage Account (required for Azure Functions App)
az storage account create -n safnwithgo2021 \
  -g rg-functions-with-go \
  -l germanywestcentral

# create an Azure Functions App
az functionapp create -n fn-with-go \
  -g rg-functions-with-go \
  --consumption-plan-location germanywestcentral \
  --os-type Linux \
  --runtime custom \
  --functions-version 3 \
  --storage-account safnwithgo2021

Cross-compiling the Go app

Depending on your operating system, you have to cross-compile the Go app. The Azure Functions app we’ve created previously is running a Linux os. That said, ensure you compile the Go application with corresponding options to target Linux:

# Build the Go app for Linux/amd64
GOOS=linux GOARCH=amd64 go build cmd/api.go

Deploy using func CLI

The func CLI also provides commands to deploy the app directly to Azure. Let’s use func azure functionapp publish to deploy our sample:

cd my-projects/functions-with-go

# reference the Azure Functions app by its name
func azure functionapp publish fn-with-go
# Getting site publishing info...
# omitted
# Deployment completed successfully.
# Syncing triggers...
# Functions in fn-with-go:
#    product-by-id - [httpTrigger]
#        Invoke url: https://fn-with-go.azurewebsites.net/api/products/{id:int}
#    products - [httpTrigger]
#        Invoke url: https://fn-with-go.azurewebsites.net/api/products

What you’ve learned

Throughout this article, we have covered a lot, and you have successfully achieved the following goals 🚀:

  • ✅ Create Azure Functions with Go
  • ✅ Configure request forwarding using custom handlers
  • ✅ instruct Azure Functions runtime to use your custom Go binary
  • ✅ Debug your Go API locally behind Azure Functions custom handler
  • ✅ Build and Deploy Go-based Azure Functions to Azure

Conclusion

Custom handlers in Azure Function allow any developer to leverage serverless capabilities in cloud-native application architectures no matter which programming language they prefer. With Azure Functions Core Tools, Seamless integration guarantees a smooth development and debugging experience using familiar tools like Visual Studio Code. Additionally, you can finally adopt serverless in the Azure space without having to rely on the Azure Functions programming model.

In addition to simple HTTP request forwarding, you can use custom handlers in Azure Functions for more advanced scenarios like dealing with input and output bindings. Consult this page of the documentation to dive deeper.

Find the sample in its repository & have fun.