Using Netlify Functions in a Hugo website

Published by Joost Baaij on 29 August 2022

Enabling Netlify Functions for a Hugo website (this one!) is pretty straightforward. There are a couple gotchas, because the documentation is out-of-date.

Coding the function

For a hardware project I’m working on (more about that in a future article) I needed a simple webservice to transform JSON. My hardware only accepts JSON input in a certain format, and the data I want to ingest isn’t in that format. Since you’re asking, that data is the current power of my solar installation.

dalle-2022-08-29-124326-a-ukiyo-e-scroll-of-a-rooftop-with-solar-panels-with-binary-data-streaming-out-of-the-panels.jpg
DALL·E 2022-08-29 12.43.26 - a ukiyo-e scroll of a rooftop with solar panels, with binary data streaming out of the panels

It’s the perfect use case for something serverless.

As it happens, the website is hosted by Netlify. They have excellent support for serverless functions built into the platform: build-time functions, background functions and edge functions. Let’s roll with a plain build-time function.

The Netlify docs has an example front and center that is perfect for this use case. We’ll see in a bit that this code example has some problems.

const fetch = require("node-fetch");

const API_ENDPOINT = 'https://cat-fact.herokuapp.com/facts';

exports.handler = async (event, context) => {
  try {
    const response = await fetch(API_ENDPOINT);
    const data = await response.json();
    return { statusCode: 200, body: JSON.stringify({ data }) };
  } catch (error) {
    console.log(error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: 'Failed fetching data' }),
    };
  }
};

The idea is that this function fetches the live statistics from my panels, and then spits it right back out in a different format.

Running the function locally

I won’t repeat here how to code this function in depth. Suffice to say you end up with a file called functions/autarco/index.js (autarco is my inverter’s brand).

Praise the Netlify lords for creating command-line tools that can work with functions. There is a complete local development environment called Netlify Dev, and there is a command-line program that just builds the function. I went with that one:

netlify functions:serve

◈ Injected netlify.toml file env var: HUGO_VERSION
◈ Injected netlify.toml file env var: NODE_VERSION
◈ Injected netlify.toml file env var: RUBY_VERSION
◈ Injected netlify.toml file env var: HUGO_ENABLEGITINFO
◈ Ignored general context env var: LANG (defined in process)
◈ Loaded function autarco.
◈ Functions server is listening on 9999

Problems with example code

Sidebar: what is it with JavaScript and the never-ending changes in tools, syntax, APIs, and libraries? I’ve been coding JavaScript for almost three decades and the constant change is really problematic. Most documentation is outdated after three months.

Right out the gate the example doesn’t run. It’s the require in there which needs to be an import:

import fetch from 'node-fetch';

Also problematic: where is this node-fetch thing coming from? I suspect that it’s in the Netlify stack by default, and available using invisble magic. I’m not a fan of invisible magic. For one thing, node-fetch underwent a dramatic change between versions 2 and 3. Which one is Netlify using? Your guess is as good as mine.

So instead of relying on magic, let’s include it into our project.

joost@silo:~/Sites/www.spacebabies.nl$ yarn add node-fetch --dev

yarn add v1.22.19
[1/5] Validating package.json...
[2/5] Resolving packages...
[3/5] Fetching packages...
[4/5] Linking dependencies...
[5/5] Building fresh packages...
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ node-fetch@3.2.10
info All dependencies
└─ node-fetch@3.2.10
Done in 0.42s.

Easy as pie. To fetch our data, we’ll store the required credentials in environment variables.

const site = process.env.SITE;
const API_ENDPOINT = `https://my.autarco.com/api/m1/site/${site}/power`;
const basic = Buffer.from(process.env.USERNAME + ":" + process.env.PASSWORD)
                    .toString('base64');
const headers = {
  'Authorization': `Basic ${basic}`
};

Sidebar: more magic! I’m using a thing called Buffer. Apparently this is part of Node JS. From googling example code you wouldn’t know though.

The rest of the code is pretty straighforward.

exports.handler = async (event, context) => {
  try {
    const response = await fetch(API_ENDPOINT, { headers: headers });
    const data = await response.json();
    const power = {
      "frames": [
        {
          "text": `${data.stats.kpis.pv_now} W`,
          "icon": 33038,
          "index": 0
        }
      ]
    };

    return {
      statusCode: 200,
      body: JSON.stringify(power)
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify(error)
    };
  }
};

Why this is not ideal

For all intents and purposes, the above code works. But it’s hardly good enough. I’ll highlight some problems:

No caching: this function will fetch the same data again and again, even when it hasn’t changed. Wasteful and potentially expensive.

Poor HTTP support: at the very least, this example should list a bunch of HTTP headers that will help clients. I’m thinking of Last-Modified, ETag, the correct Content-Type and so on.

Happy path coding: if something goes wrong, as it sometimes does, all you would see is a generic “something broke” message. Happy hunting. Why not show the contents of the error object?

And that’s on top of using outdated libraries.

Rating: five stars

Poor example code aside, what I’ve done here is nothing short of magic. Not so long ago, you would have to instantiate a backend server, code something similar on the back end, and manage it. Now, Netlify does that for me for a very modest fee. And they make sure it keeps running.