Simple build step for Netlify Functions


Netlify Functions are a fantastic way to extend the capabilities of a static site and enable you to easily build scalable and dynamic applications.

Netlify Functions are AWS Lambda functions, so yes you could totally do this without Netlify, but Netlify makes the whole process so much easier. You simply write your functions in the same repo with the rest of your code, deploy and Netlify takes care of the rest. This is made even easier with Netlify Dev which allows you run your local development environment and have Netlify environment variables available as well as have your functions available at local endpoints so you can easily test your entire integration.

What I had previously found lacking with the out of the box setup for Netlify Functions was a build step which I frequently want in order to import utility functions that I can share across multiple functions. You may have other needs such as typescript or running babel transforms which require this build step.

Netlify have recently addressed this issue (see this post) in way that embraces the previous simplicity but with much greater power.

In order to demonstrate the leap forward that has been made, in this post I'll first walk you through the old way I had to set things up in order to make it work and then compare to the new way.

The old way

Netlify Lambda is an additional package you can pull in which adds a simple webpack/babel build step. First step, install Netlify Lambda

yarn add netlify-lambda --dev

Then add a new script to package.json

"build_functions": "netlify-lambda build functions/development -c functions/webpack.functions.js"

Let me explain what this does. Netlify Functions need to live in a separate folder in your repo which is functions in my case. For functions I have 2 folders, development and production. Development is where I write my function code during development and production is where Netlify Lambda will output the code following the build step.

In order to tell Netlify Lambda where to build to and also let Netlify know where to find the production functions during a build, you have to add to the netlify.toml file at the root of your repo.

[build]
  functions = "functions/production"
  ...

At this point when I was setting things up, it's not particularly clear how Netlify Lambda and Netlify Dev actually work together. I eventually came across these docs that made things clearer. Netlify Dev offers function builder detection where it automatically looks for scripts with netlify-lambda build $SRCFOLDER when you run the netlify dev command and will execute it. It will also watch the files in the $SRCFOLDER and run the build command again when any changes are made.

I also add functions/production/ to my .gitignore file because, as mentioned above, when you run netlify dev locally it will automatically run the build_functions script and output the bundled code to the functions/production/. I would rather the final function production code is generated during my Netlify deploys in the same way my other JavaScript is bundled during a build and therefore I add yarn build_functions to the start of my Netlify build command.

The final part of my build_functions command is -c functions/webpack.functions.js. Netlify Lambda has a default webpack config baked in but this is a way to extend it and add any additional configuration you need. In my case I have a simple addition

module.exports = {
  optimization: { moduleIds: "hashed" }
};

I also have added a functions/.babelrc file in order to maintain a separate config file to the rest of my JavaScript whicin this case is

{
  "plugins": ["@babel/plugin-transform-runtime"]
}

and this allows me to run the modern async/await syntax in my functions.

For the function example I'm going to have a very simple hello world function which calls a utility function to generate a random number after the string. This is a stupidly basic use case for Netlify Lambda but it works nicely for a demo.

First up, create the utility function at functions/development/utils/random_number_generator.js

export function randomNumberGenerator() {
  return Math.floor(Math.random() * 10)
}

and then create your function at functions/development/hello_world.js

import { randomNumberGenerator } from "./utils/random_number_generator"

export async function handler(event, context) {
  return {
    statusCode: 200,
    body: JSON.stringify({ message: `Hello world ${randomNumberGenerator()}` })
  };
}

When you run netlify dev, you'll see the bundled production file generated at functions/production/hello_world.js. You can see the output of the function at http://localhost:8888/.netlify/functions/hello_world (your localhost port may vary depending on your Netlify Dev setup). Make a change and you'll see the success message in the terminal, refresh the page and you can see the update.

The new way

The recent updates by Netlify have simplified things greatly. First up, disregard the changes made in the old way of doing things and ensure you have the latest version of Netlify CLI installed

npm install netlify-cli -g

Now make the following updates to your netlify.toml file

[functions]
  directory = "functions"
  node_bundler = "esbuild"

This makes use of esbuild which Netlify Dev is now using under the hood in order to bundle your Netlify Functions. It is very fast and I'm definitely noticing the difference in bundle times!

The folder structure is also stripped down with my function living at functions/hello_world.js

import { randomNumberGenerator } from "./utils/random_number_generator"

export async function handler(event, context) {
  return {
    statusCode: 200,
    body: JSON.stringify({ message: `Hello world ${randomNumberGenerator()}` })
  };
}

and utility function at functions/utils/random_number_generator.js

export function randomNumberGenerator() {
  return Math.floor(Math.random() * 10)
}

The functions/.babelrc and functions/webpack.functions.js files are no longer needed and I also have no need for an additional build script in package.json. Once again, run netlify dev, and you will see the output of the function at http://localhost:8888/.netlify/functions/hello_world. If you make any updates, you can see how quickly the changes take effect.

That's it! It is so much easier now to get up and running with a build step for your functions and means you can just get on with the job of shipping code rather than working on build configuration.