Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

Providing next event as API with Eleventy’s Global Data Files and Netlify Functions

Code in this blog post was written with versions: eleventy: 3.0.0, netlify-cli: 18.0.1

I have previously written about how I use Eleventy’s global data files to build community websites. For a while now, I’ve wanted to provide a programmatic way to read the next event information to use in other websites and this week I finally undertook the project. What started as a project to combine Eleventy’s global data files with Netlify’s serverless Functions turned into an interesting exploration about API design. I’ll write about both of those in this post.

Two key design motivators were:

  1. I wanted it to be automated without the webmaster having to remember to change yet another file.
  2. I wanted it to be easy for the consumer of the API to get this individual event date without having to do any logic in their code.

Part 1: Let’s build it

1 Prerequisites

To run the code in this project, you need to have npm and Netlify CLI installed. Once you have npm, you can install Netlify CLI with npm install -g netlify-cli. This will install it as a global CLI tool that you can use across your projects.

2 Project set up

I’ve built a demo repository for this project so you can see the entire code. You can clone that project or follow up by building from scratch. If you clone the project, you need to run npm install in the repository before continuing and skip the rest of this part 2.

First, we’ll need to create an npm project and install Eleventy by running these two commands in the command line:

npm init -y 
npm install @11ty/eleventy

Next, we need to do two changes to our package.json file:

  1. Change type field to "module" to enable ESM style Javascript
  2. Add a build field to scripts and set it to "eleventy"

To set up global data files in Eleventy, create a directory _data/ into the project root and add a file events.json inside it:

[
  {
    "name": "April",
    "date": "2025-04-20"
  },
  {
    "name": "May",
    "date": "2025-05-20"
  },
  {
    "name": "June",
    "date": "2025-06-20"
  }
]

This will allow us to access the data from anywhere in our Eleventy project.

If you’re running these examples later than June 20th, 2025, you need to manually adjust the dates to add at least one event in a future date.

4 Create index file

To be able to access the global data files in the configuration file (that we’ll do in a bit), we need to use it somewhere in the templates. I’m not quite sure yet why that happens but it will prevent us from getting errors later on.

Create index.njk in the project root and add the following to it:

<ul>
{% for event in events %} 
<li>{{ event.date }}: {{event.name}}</li>
{% endfor %}
</ul>

This will display the dates and events in a list on the front page of the project. If you’re adding this functionality to an existing project, just make sure events is being accessed somewhere.

5 Set up Netlify Functions

Netlify Functions will by default look for a folder netlify/functions . There you can store multiple different functions in separate folders. Let’s create our first Function by creating the necessary folders:

mkdir -p netlify/functions/api

-p flag tells mkdir to create any necessary missing folders along the path.

We’ll leave the folder empty for now as we’ll be dynamically creating the Javascript file itself as part of our build process.

We should then add this folder into .eleventyignore to avoid dev server running around in an endless loop every time we change something (as a change will trigger a build which will change api.js which will trigger a build and so on and so on):

# Ignore here to avoid triggering a rebuild loop.
netlify

Netlify Functions folder will be configured in Netlify’s end when creating a site so it doesn’t get built in into the _site/ folder.

6 Create Eleventy config

Eleventy’s config lives in eleventy.config.js and the function exported from that file will be run on each build.

// Import Node's file system library
import fs from 'fs'

export default function (eleventyConfig) {
  // We create a new collection to gain access to collectionApi
  eleventyConfig.addCollection('next_event', collectionApi => {
    // We can access global data through items
    // If you get an error here about not able to access data 
    // on undefined, you need to check step 4
		const events = collectionApi.items[0].data.events;
		
		// We sort by date
		events.sort((a, b) => {
      return new Date(a.date) - new Date(b.date);
    });
    
    // Find the first event where its date 
    // is in the future
    const nextEvent = events.find(event => {
      const now = new Date();
      const eventDate = new Date(event.date);

      return now < eventDate;
    });
    
    // Create an API response object
    let response;
    if(!nextEvent) {
	    response = {
        status: "NO_EVENT_SCHEDULED",
        data: null,
      };
    } else {
	    response = {
	      status: "OK",
	      data: nextEvent
	    }
    }
    
    // Write the API endpoint function to 
    // our Netlify Function folder
    fs.writeFileSync(
      "netlify/functions/api/api.js",
      `export default function(request, response) {
        return Response.json(${JSON.stringify(response)})    
    }`
    );
    
    // Return list with our next event so
    // this collection can be used in other
    // parts of the website too.
    return [nextEvent];
  });
}

7 Build the project and test the API

If you now run npm run build in your project root, Eleventy should create a _site folder, create the netlify/functions/api/api.js and copy it to _site.

To get Netlify Functions to run on local development, we need to use Netlify CLI that will wrap up the Eleventy project inside a Netlify development server and serve all functions:

netlify dev

This will run Eleventy dev server (by default on port 8080) and a Netlify dev server (by default on port 8888) that routes the requests to Eleventy.

If you now point your browser to localhost:8888/.netlify/functions/api, you should see the API return an event object.

If you want to see what the response is when there are no events scheduled, remove all future events from _data/events.json.

Things to note

As with any static site project, the data in the API is static. It won’t change to a newer event just because time passed. It will only ever update on build time.

In my community projects, that’s not really a problem because there’s always website updates the day following an event. Or I can trigger a manual build the next morning.

It’s good to keep in mind though if you plan to do this with a site that doesn’t get updates often. In case like that, you might want to set up some automated daily builds or change the Netlify Function to include the all the future events and do the logic for finding the next event there.

Whatever API endpoints you end up creating and whatever their responses look like, it’s important to document them well so that whoever consumes your API can rely on the documentation to get their code functioning properly in all cases and they don’t have to reverse engineer it.

Part 2: API design

When I started working on this, I wanted to think hard about the response to make sure it’s nice to use. I asked around in two places for opinions and advice: in Mastodon and in Koodiklinikka community.

I found these discussions very insightful and interesting. Here are some options and ideas that were presented.

HTTP 200 with status field

I ended up choosing an option where the response always has an HTTP code of 200 (Success) and it has a status field that says if an event exists or not.

My considerations for this was that I wanted the shape of the response to be always the same. I also wanted to be explicit about such event not existing. Sometimes it can be hard to know if {} or [] in response means that there are no results or that something went wrong. I wanted to avoid that.

I thought about how this would be consumed by other code and checking for status: "OK" before parsing the event data seemed like an okay approach.

HTTP 200 for next event, 404 if no such event

A strong candidate was to send 404 (Not found) if no future events had been scheduled. This way, the consumer of the API could check the status code and continue based on that.

In RESTful API design, endpoints are considered to point to resources and if there are no events, it means the requested event is not found and 404 is the status code to use.

I’m by no means of school of thought where every response is 200 and errors are included in the response (which is something that has become somewhat popular with the influence of GraphQL) but I could not quite convince myself that “No event scheduled” would be an error case in similar way as if a resource is missing in RESTful design.

In my thinking, asking for the next event is always valid and sometimes due to real life situations, one has not been scheduled yet but asking for it was still the right thing to do.

This 404 approach seemed to spark the most discussion, with people arguing both for and against it.

Provide something else for missing case

Couple of suggestions were around the idea of the response for missing event being represented in a different format / structure as the other case. I find these the hardest or most annoying ones to consume because you don’t only need to check for values (like status codes or status strings) but also for the type of the data.

Respond with empty object

Another potential option was to return an empty object ({} ). In that case, if there’s content inside that object, an event exists. I did consider that one as well but as I mentioned earlier, I wanted to be more explicit in my response that the case is that there are no events scheduled. It’s a stronger message that the response is intended and not just accidentally empty.

204 No content

An interesting and spicy idea brought up in both places was using 204 (No content). On a very surface level glance, it sounds like it could be useful but 204 is really meant for cases where an action was performed successfully but there’s nothing to return to the client and no expectation for the client to do anything. So it’s not really a suitable option here.


If something above resonated with you, let's start a discussion about it! Email me at juhamattisantala at gmail dot com and share your thoughts. In 2025, I want to have more deeper discussions with people from around the world and I'd love if you'd be part of that.