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.
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
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.
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.
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:
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-examplego 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.
package mainimport ("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 dataev := 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 exitdefer 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 exitingdefer libhoney.Close()// Make the handler available for Remote Procedure Call by AWS Lambdalambda.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
.
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.
build:yarn workspace site buildGOBIN=${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
.
If we test by running make
, we get a Gatsby build followed
by a go build.
makeyarn workspace site buildyarn workspace v1.17.3yarn run v1.17.3$ gatsby buildsuccess open and validate gatsby-configs - 0.056 ssuccess load plugins - 0.512 ssuccess onPreInit - 0.019 ssuccess delete html and css files from previous builds - 0.040 ssuccess initialize cache - 0.024 ssuccess copy gatsby files - 0.120 ssuccess onPreBootstrap - 0.028 ssuccess source and transform nodes - 0.114 ssuccess Add explicit types - 0.027 ssuccess Add inferred types - 0.223 ssuccess Processing types - 0.114 ssuccess building schema - 0.461 ssuccess createPages - 0.019 ssuccess createPagesStatefully - 0.071 ssuccess onPreExtractQueries - 0.023 ssuccess update schema - 0.047 ssuccess extract queries from components - 0.370 ssuccess write out requires - 0.022 ssuccess write out redirect data - 0.018 ssuccess Build manifest and related icons - 0.167 ssuccess onPostBootstrap - 0.201 s⠀info bootstrap finished - 5.078 s⠀success run static queries - 0.076 s — 3/3 59.40 queries/secondsuccess Building production JavaScript and CSS bundles - 5.656 ssuccess Rewriting compilation hashes - 0.027 ssuccess run page queries - 0.044 s — 4/4 270.24 queries/secondsuccess Building static HTML for pages - 0.747 s — 4/4 16.92 pages/secondinfo 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 latestgo: finding github.com/aws/aws-lambda-go/events latestgo: finding github.com/honeycombio/libhoney-go/transmission latestgo: finding github.com/aws/aws-lambda-go v1.13.0go: downloading github.com/aws/aws-lambda-go v1.13.0go: extracting github.com/aws/aws-lambda-go v1.13.0go: finding github.com/klauspost/compress/zstd latestgo: finding github.com/facebookgo/muster latestgo: finding github.com/facebookgo/limitgroup latestgo: 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 configuration11:50:23 PM: Different functions path detected, going to use the one specified in the toml file: 'netlify-functions' versus '' in the site11:50:23 PM: Creating functions prep folder...11:51:59 PM: Function Dir: /opt/build/repo/netlify-functions11:51:59 PM: TempDir: /tmp/zisi-16213274911:51:59 PM: Prepping functions with zip-it-and-ship-it 0.3.111: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
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 invoked11: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"}