70 %
Chris Biscardi

Getting started with Netlify functions and go modules

When building Gatsby sites we don't run a server, so to use any dynamic logic we can take advantage of serverless functions. In our case we'll deploy on Netlify and that means we'll go with golang for our functions.

Initial Project

The project we're working on is a yarn workspaces setup with a Gatsby project in packages/hello-world-site. We won't be running any of that code, it's there solely to frame the context of deploying golang functions in the context of a larger, multi-language project. Our filesystem layout for our project includes a yarn lockfile, package.json, and other assorted Gatsby related stuff.

.
├── node_modules
├── package.json
├── packages
└── yarn.lock

Go source code

We'll create a go-src directory since we'll be using go modules to maintain our application, and a hello-world directory inside of that to house our first lambda.

sh
mkdir go-src/hello-world

Using Go modules means we don't have to deal with $GOPATH or other environment variables.

Note: make sure GO_IMPORT_PATH is not set in your environment when deploying on Netlify or go modules will not work and your dependencies will not be found

We will have to initialize the modules though.

sh
go mod init

If you see the following message, it means go wasn't able to automatically determine the package, so you'll have to use the name when initializing.

go: cannot determine module path for source directory /Users/chris/github/christopherbiscardi/golang-modules-netlify-serverless-gatsby-example (outside GOPATH, no import comments)

My repo will be on github at github.com/christopherbiscardi/golang-modules-netlify-serverless-gatsby-example, so my init command is:

sh
go mod init github.com/christopherbiscardi/golang-modules-netlify-serverless-gatsby-example

Our newly created go.mod file is pretty empty.

module github.com/christopherbiscardi/golang-modules-netlify-serverless-gatsby-example
go 1.12

Next, we'll write some code for a hello-world golang function. In go-src/hello-world/main.go use the following code.

go
package main
import (
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/honeycombio/libhoney-go"
"github.com/honeycombio/libhoney-go/transmission"
)
func handler(request events.APIGatewayProxyRequest) (*events.APIGatewayProxyResponse, error) {
// Create an event, add some data
ev := libhoney.NewEvent()
ev.Add(map[string]interface{}{
"method": request.HTTPMethod,
"hostname": request.Resource,
"request_path": request.Path,
"name": "devtips",
})
// This event will be sent regardless of how we exit
defer ev.Send()
ev.AddField("status_code", 200)
return &events.APIGatewayProxyResponse{
StatusCode: 200,
Body: "Hello, World",
}, nil
}
func main() {
libhoney.Init(libhoney.Config{
// APIKey: "",
Dataset: "netlify-lambdas",
Transmission: &transmission.WriterSender{},
})
// Flush any pending calls to Honeycomb before exiting
defer libhoney.Close()
// Make the handler available for Remote Procedure Call by AWS Lambda
lambda.Start(handler)
}

This code does two things. The first is setting up Honeycomb for logging with the stdout transmission. This lets us log out events to the netlify console when our lambda function runs with the option to easily switch to sending them to an aggregation dataset later for querying if we want.

The second is our lambda handler setup. This is the code that handles receiving and responding to events. We send back a Hello, World string in response to an APIGatewayProxyRequest.

netlify setup

We'll take advantage of Netlify to build our functions (and the existing Gatsby site). To do that we need a netlify.toml (or you can use the web interface). We are going to use Make to orchestrate our build commands. Our functions are going to be built into a netlify-functions folder. Finally, our Gatsby site will get built into packages/hello-world-site/public.

[build]
command = "make build"
functions = "netlify-functions"
publish = "packages/hello-world-site/public"

Then in the root of our site create a Makefile with a build command. If you know Make well feel free to spice this up as much as you like with targets, etc. Our build command builds the Gatsby site first, then the go functions.

makefile
build:
yarn workspace site build
GOBIN=${PWD}/netlify-functions go install ./...

Since go build doesn't let us control the location of the resulting binaries, we use go install with a GOBIN environment variable pointing at our netlify-functions folder (which doesn't need to exist in our repo). ./... roughly means "build all of the binaries"; You can also choose to manually go build each one.

Note that because we used ./... we're set up to build as many functions as we want by adding new directories in go-src.

deploying

If we test by running make, we get a Gatsby build followed by a go build.

shell
make
yarn workspace site build
yarn workspace v1.17.3
yarn run v1.17.3
$ gatsby build
success open and validate gatsby-configs - 0.056 s
success load plugins - 0.512 s
success onPreInit - 0.019 s
success delete html and css files from previous builds - 0.040 s
success initialize cache - 0.024 s
success copy gatsby files - 0.120 s
success onPreBootstrap - 0.028 s
success source and transform nodes - 0.114 s
success Add explicit types - 0.027 s
success Add inferred types - 0.223 s
success Processing types - 0.114 s
success building schema - 0.461 s
success createPages - 0.019 s
success createPagesStatefully - 0.071 s
success onPreExtractQueries - 0.023 s
success update schema - 0.047 s
success extract queries from components - 0.370 s
success write out requires - 0.022 s
success write out redirect data - 0.018 s
success Build manifest and related icons - 0.167 s
success onPostBootstrap - 0.201 s
info bootstrap finished - 5.078 s
success run static queries - 0.076 s — 3/3 59.40 queries/second
success Building production JavaScript and CSS bundles - 5.656 s
success Rewriting compilation hashes - 0.027 s
success run page queries - 0.044 s — 4/4 270.24 queries/second
success Building static HTML for pages - 0.747 s — 4/4 16.92 pages/second
info Done building in 11.751965911 sec
✨ Done in 12.07s.
✨ Done in 12.69s.
GOBIN=/Users/chris/github/christopherbiscardi/golang-modules-netlify-serverless-gatsby-example/netlify-functions go install ./...
go: finding github.com/aws/aws-lambda-go/lambda latest
go: finding github.com/aws/aws-lambda-go/events latest
go: finding github.com/honeycombio/libhoney-go/transmission latest
go: finding github.com/aws/aws-lambda-go v1.13.0
go: downloading github.com/aws/aws-lambda-go v1.13.0
go: extracting github.com/aws/aws-lambda-go v1.13.0
go: finding github.com/klauspost/compress/zstd latest
go: finding github.com/facebookgo/muster latest
go: finding github.com/facebookgo/limitgroup latest
go: finding github.com/facebookgo/clock latest

Once we push to netlify, we'll see the build logs relating to our functions (as well as the gatsby site).

11:50:23 PM: Found netlify.toml. Overriding site configuration
11:50:23 PM: Different functions path detected, going to use the one specified in the toml file: 'netlify-functions' versus '' in the site
11:50:23 PM: Creating functions prep folder
...
11:51:59 PM: Function Dir: /opt/build/repo/netlify-functions
11:51:59 PM: TempDir: /tmp/zisi-162132749
11:51:59 PM: Prepping functions with zip-it-and-ship-it 0.3.1
11:52:00 PM: [ { path: '/tmp/zisi-162132749/hello-world', runtime: 'go' } ]
11:52:00 PM: Prepping functions complete
...
11:52:04 PM: 1 new functions to upload

Testing our Function

The function is deployed at https://our-site.netlify.com/.netlify/functions/hello-world so we'll simply visit that page to trigger the function, seeing Hello, World on the page as a result.

Our function logs show that we've hit the hello-world function

11:53:52 PM: hello-world invoked
11:53:52 PM: {"data":{"hostname":"","method":"GET","name":"devtips","request_path":"/.netlify/functions/hello-world","status_code":200},"time":"2019-08-23T06:53:52.833607933Z","dataset":"netlify-lambdas"}