Add a Comments Section to an Eleventy Website with MongoDB and Netlify
Rate this tutorial
I'm a huge fan of static generated websites! From a personal level, I have
, all three of which are static generated websites built with either Hugo or Eleventy. In addition to being static generated, all three are hosted on Netlify.
I didn't start with a static generator though. I started on WordPress, so when I made the switch to static HTML, I got a lot of benefits, but I ended up with one big loss. The comments of my site, which were once stored in a database and loaded on-demand, didn't have a home.
Fast forward to now, we have options!
In this tutorial, we're going to look at maintaining a static generated website on
, but the big thing here is that we're going to see how to have comments for each of our blog pages.
To get an idea of what we want to accomplish, let's look at the following scenario. You have a blog with X number of articles and Y number of comments for each article. You want the reader to be able to leave comments which will be stored in your database and you want those comments to be loaded from your database. The catch is that your website is static and you want performance.
A few things are going to happen:
- When the website is generated, all comments are pulled from our database and rendered directly in the HTML.
- When someone loads a page on your website, all rendered comments will show, but we also want all comments that were created after the generation to show. We'll do that with timestamps and HTTP requests.
- When someone creates a comment, we want that comment to be stored in our database, something that can be done with an HTTP request.
It may seem like a lot to take in, but the code involved is actually quite slick and reasonable to digest.
There are a few moving pieces in this tutorial, so we're going to assume you've taken care of a few things first. You'll need the following:
- A Netlify account connected to your GitHub, GitLab, or Bitbucket account.
- Node.js 16+.
We're going to be using MongoDB Atlas to store the comments. You'll need a cluster deployed and configured with proper user and network rules. If you need help with this, check out my
on the subject.
We're going to be serving our static site on Netlify and using their build process. This build process will take care of deploying either Realm Functions (part of MongoDB Atlas) or Netlify Functions.
Node.js is a requirement because we'll be using it for Eleventy and the creation of our serverless functions.
Before we get into the comments side of things, we should probably get a foundation in place for our static website. We're not going to explore the ins and outs of Eleventy. We're just going to do enough so we can make sense of what comes next.
Execute the following commands from your command line:
The above commands will create a new and empty directory and then navigate into it.
Next we're going to initialize the project directory for Node.js development and install our project dependencies:
Alright, we have quite a few dependencies beyond just the base Eleventy in the above commands. Just roll with it for now because we're going to get into it more later.
Open the project's package.json file and add the following to the
The above script commands will make it easier for us to serve our Eleventy website locally or build it when it comes to Netlify.
Now we can start the actual development of our Eleventy website. We aren't going to focus on CSS in this tutorial, so our final result will look quite plain. However, the functionality will be solid!
Execute the following commands from the command line:
We made quite a few directories and empty files with the above commands. However, that's going to be pretty much the full scope of our Eleventy website.
Multiple files in our example will have a dependency on the src/_includes/layouts/base.njk file, so we're going to work on that file first. Open it and include the following code:
Alright, so the above file is, like, 90% complete. I left some pieces out and replaced them with comments because we're not ready for them yet.
This file represents the base template for our entire site. All other pages will get rendered in this area:
That means that every page will have a comments section at the bottom of it.
We need to break down a few things, particularly the
<script>tag at the bottom of the page.
<script>tag, we have two variables and two functions.
The above two variables hold the current page URL, which will be important for identifying which page should have which comments as well as the last build date for our Eleventy website. The
page.urlvariable is part of Eleventy, but we'll be creating our
config.lastBuildDatevariable in a moment.
Knowing the last build date of our website is important for pulling only the comments we need for any given page rather than all comments, but more on that soon.
Next we have a function that looks like this:
Remember, our comments are going to be stored externally. For this reason, we need to be doing HTTP requests. Ignoring the endpoint URL for now, we're expecting an array of comments to come back from the request. In the above function, we loop through the comments and create a new tag to be injected into our HTML for each comment in the array. While we'll be storing more information, we're only presenting the
commenton the screen.
Creating comments is similar in design:
In the above code, we are doing three things:
- Extract the comment information from the form.
- Inject the comment information into the HTML like the previous function.
- Send the comment information to our HTTP endpoint.
It may look like a lot, but most of the code is related to injecting HTML. The heavy lifting is done by the endpoint that we'll handle later.
So I mentioned the last build date. Let's check that out. Open the project's src/_data/config.js file and include the following:
So we have the base template for our website. Let's make the child pages and have them use it.
Open the project's src/index.html file and include the following code:
Because we're defining a layout, anything on this page will get injected into the appropriate part of that template. The purpose of this page is just to show each of our blog pages.
Now open the project's src/blog/article1.md file:
Not too much different than the HTML page, but this time, we're using Markdown—something we'd use for writing blog articles.
Finally, we have our other blog page in the src/blog/article2.md file:
I realize we created three files that are more or less the same, but they are necessary to prove our point. We are going to have a multiple page static generated site and the comments should only appear on the correct page of that site.
Alright, so on the src/index.html page, we had a loop for our blog posts. This is great, but we need to define that our blog posts can be looped. For this, we need to create an Eleventy configuration file beyond the build-related configuration file that we previously defined.
Open the .eleventy.js file at the root of your project and add the following code:
We added a little more to this file than we need at the moment, but I'll explain it while we're here.
First, you'll notice the following:
The above snippet says that all Markdown files in the src/blog directory and child directories should be added to an Eleventy collection called
poststhat can be looped over.
Then we have the following:
The above filter says that when we loop over something in our project, we can filter to only return results that match our URL in this case. This will be useful when showing only comments that should be shown for a particular page. Remember, we know the page URL and that URL will also be stored in MongoDB for any particular comment.
With the exception of this mysterious src/_data/comments.js file and our placeholder endpoint URLs, the Eleventy site is done! We'll circle back to the other outstanding items when we need them.
Now we need to figure out how we want to engage with MongoDB to obtain those comments and work with them. For this example, we're going to explore two options, going all in with MongoDB by using Realm or Netlify Functions.
We're not following any particular order here, but we're going to start with Realm for interacting with our comment data. This will be modular and you can choose to use it or not.
To be successful, you're going to need to have API keys in hand for working with Realm from the CLI—something we plan to do locally and part of the Netlify build process.
, create a new Realm application and tell it which cluster to use. Next, you'll want to click the ellipsis menu next to the cluster name at the top left of the screen.
Click the "Project Settings" link followed by "Access Manager." When you create a new API key, you'll want to give it "Project Owner" permissions. The name of this key is not important, but it should be a name meaningful to you.
Save both the public key and private key because we'll need to enter them in two places, one for the local CLI and one for Netlify.
Let's log into the Realm CLI. Execute the following from your command line:
Enter the keys when prompted. You can also add them as environment variables and do something like this instead:
After you log into the Realm CLI successfully, we need to pull down our Realm project into our Eleventy project.
At the root of your Eleventy project, execute the following:
You should be prompted to choose which Realm project you want to use. Select the correct project and it will be downloaded to a realm directory within your project.
With Realm, HTTP endpoints rely on Realm Functions. There's also some configuration on top of this, but our bottom layer is the Realm Functions. We're going to start from the bottom and work our way to the top.
Create the following files with the command line:
We're going to start by working on the function for creating our comment within MongoDB.
Open the realm/functions/create_comment.js file and include the following:
The above function will take a request payload and parse it. Next, we'll take the current timestamp information and insert it into MongoDB.
For this example, we're not doing any data validation. Take this into consideration when attempting to take this material to the next level.
Let's look at our function for obtaining comments. Open the project's realm/functions/get_comments.js file and add the following:
The above code is slightly more extravagant.
In the above code, we are looking at the query parameters of the request. We want the URL, which represents our page URL, and we want the last build date of our Eleventy website. Both of these query parameters are optional but will play an important role in what's to come.
Using those optional parameters, we can do a query and return the results.
For both the get_comments.js and create_comments.js functions, we are using a "netlify" database and "comments" collection within MongoDB. Neither need to exist prior to runtime as they will be created if they don't exist.
With the function logic in place, let's configure those functions. Open the project's realm/functions/config.json file and add the following:
In the above configuration, we are defining the levels of permissions they should use. Take a moment to brush up on the type of roles you can use for Realm Functions as my choice in roles might not be the best for you.
The functions are good to go, but now we need to configure HTTP endpoints to interact with them.
Create a realm/http_endpoints/config.json file if it doesn't exist and add the following:
What we're doing is we're defining the HTTP endpoint URL route to a particular function. We're also specifying if data should be returned, among other things. Your best bet is to look into the documentation if you need a more thorough explanation on any of the fields.
The Realm side of things should be good to go. Try deploying the HTTP endpoints with the following:
If you want to interact with any of your HTTP endpoints, you should be able to do something like this:
Just add "$REALM_APP_ID" as an environment variable with your own application ID. Remember that we have a GET endpoint as well as a POST endpoint.
Let's wire these functions into our Eleventy website.
Start by opening the project's src/_includes/layouts/base.njk file and slipping the Realm HTTP endpoint URLs into the correct
If you did nothing else, you'd end up with comments on your site when people visit it. However, all comments from all pages would end up on every page. Let's fix that with a query parameter.
Remember, you have the page URL in the base.njk file. We just needed to pass it with our
fetchoperation to the endpoint for retrieving comments. Just like that, you're only getting comments for the page you're on.
We can do better though!
Right now, all comments for a particular page are loaded at runtime. While it's asynchronous, retrieving all comments could be slow if you have a lot of comments. Remember, one of the main selling points of static generated websites is that they are fast.
Let's modify our project to pre-load our comments and render them directly into the HTML prior to deployment.
Open the project's src/_data/comments.js file and add the following:
We're doing two big things in the above chunk of code:
- We're caching the comments locally between website builds.
- We're obtaining access to comments at build time rather than just runtime.
So what does this mean?
Every time we build our website or every time we hit the save button while serving our website, Eleventy will try to request all comments from our function. If you're like me, you hit the save button every half a second, so this would be a lot of function calls. To get beyond this, we cache those requests and read from the cache until it expires.
Whether we need to update the cache or make a request, we're going to have access to all the comments by the time this runs.
To get the comments pre-loaded into our HTML, let's circle back to the src/_includes/layouts/base.njk file and add the following in the HTML:
In the above HTML, we are looping through the
commentsarray of data. Remember in Eleventy, the variable is the same name as the file in the _data directory. However, we don't want to render all comments, so we use the
getCommentsForUrlfilter that we added to the .eleventy.js file. This filter will take the current page URL and filter out only the comments we want.
<script>tag at the bottom of this file? Both functions in that tag will look for the
commentselement id when injecting HTML on the fly. So we can still have both pre-loaded comments as well as on-demand comments.
So how do we prevent duplicates?
Remember the last recorded build time? We can use that to get only the difference in comments. To be clear, the pre-loading should get all comments for any given URL. The on-demand comments should be all comments after the build date. This means we're getting only the comments that haven't been baked into the HTML.
Let's change our
fetchURL a bit more:
This will work pretty slick. If you build every day like I do, the amount of on-demand comments through client-side HTTP requests will be minimal, giving you the best possible performance without missing any of the conversation.
Our Eleventy website should work fine locally with Realm and MongoDB at this point. We'll explore deployments later in this tutorial.
You just saw our first possible approach to comments in an Eleventy website. Realm is a perfectly suitable option and is probably a lot easier than I lead you to believe in this tutorial. However, we have another option. We can abandon Realm and use Netlify Functions for the job as well.
To get us where we need to be, we're going to use Netlify Functions and the
. To be clear, you could install the MongoDB Node.js driver and use it in a Netlify Function, but the Data API is so much easier for this scenario!
While you don't need the Netlify CLI for what comes next, you will get good value out of it for testing things locally. To install the Netlify CLI, execute the following command from the command line:
It's up to you if you want to sign into your Netlify account from the CLI. It's not absolutely necessary.
From the project directory, execute the following:
It's possible you'll need a netlify.toml configuration file in place prior to creating these functions. You can create one with the following code:
With the function files created, let's start adding our code. Start with the functions/create_comment/create_comment.js file.
Because we're using the MongoDB Atlas Data API, we're using HTTP requests in our functions rather than the code we saw for Realm.
The above code will create a timestamp for our comment and then it will do an HTTP request, passing in the payload as well as other information required by the API. Because we're using an API, we need some keys for security.
, click on the "Data API" link and proceed to create an API key. Make note of this API key because you'll need it in a later step beyond this section. Also make note of the "URL Endpoint" as it will be required for your HTTP requests from the Netlify Function.
In the function code, I'm using environment variables because that's what you'd use in Netlify. Add the key information and app id information to your system environment variables if you wish to test this locally.
Now we have the next endpoint. Open the project's functions/get_comments/get_comments.js file and add the following:
We're still obtaining the query parameters from our base.njk file, but we are making an HTTP request to the Data API for the list of comments.
Our functions are done, but we need to make a few modifications to our Eleventy website to do things the Eleventy way instead of Realm.
First open the project's src/_includes/layouts/base.njk file and update the
The above fetch will use the
get_commentsfunction instead of a Realm HTTP endpoint. We can do something similar for creating comments:
This works great for on-demand comments at runtime, but we can't use these local paths during build time. We'll end up with a bunch of errors.
So how do we get the src/_data/comments.js file working?
Open the project's src/_data/comments.js file and modify it to look like the following:
If you ran
netlify devfrom the command line, you should be able to locally test these Netlify functions as well as the Eleventy build.
As of right now, we have two different approaches to comments in a static website, but they're working semi-locally. We need to get them properly deployed to Netlify.
First up, let's add a few more scripts to our package.json file:
realmscript will log into Realm using whatever we have in our environment variables and the
deployscript will run our
buildscripts. Netlify will rely on the
deployscript in our pipeline.
Remember, we're assuming you're already set up with Netlify and it's connected to one of your Git accounts.
Create a new site. In the "Site Settings," find the "Build & Deploy -> Environment" settings.
If you're been using all my environment variable names, this is where you're going to want to add them. If you haven't been keeping track of your keys, you may have to generate new ones.
Once the environment variables are in place, you should end up with the same experience as you got when running things locally.
Try pushing your repository to your Git remote. Check out the Netlify deployment logs and see if everything worked out.
You just saw how to add comments to a static website hosted on Netlify. In particular, I chose to use Eleventy, but you should be able to accomplish similar things regardless of the static site generator that you chose.
In this example, we saw two of many possible options, the first being to use Realm Functions and Realm HTTP Endpoints, the other to use Netlify Functions and the MongoDB Atlas Data API. Both work well, and because our example pre-loads all comments at build time, you're going to end up with a blazing fast site.