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.