Adding a "Like" button to a static 11ty blog

One of my kids has a simple blog about their favorite video game. The blog uses the default Glitch 11ty blog template, and it was quick & easy to set up - took about 5 minutes. Thanks, Glitch! As soon as the blog was deployed, things got a little more complicated. My budding blogger requested a "Like" button so they could get some positive feedback on their posts. This meant I had to add a tiny Node/Express API connected to a SQLite database which allowed us to POST and GET likes for each blog post. Let's walk through how I did it, and how you can, too! Add a Server & API The first step is to add Express to our project with npm install express. Then, add a backend/ directory with a server.js file and routes/api.js . ├── backend/ │ ├── routes/ │ │ └── api.js { console.log(`Server running on http://localhost:${port}`); }); backend/routes/api.js const express = require("express"); const router = express.Router(); router.post("/likePost", (req, res) => { // This is where we'll query the database in the next step! }); router.get("/getLikes", (req, res) => { // ... }); module.exports = router; The Database Next, we'll install sqlite3 for a database that the API will connect to. First, install sqlite npm install sqlite3, and then create /backend/db/database.js: . ├── backend/ | ├── db/ | | └── database.js

Mar 4, 2025 - 07:41
 0
Adding a "Like" button to a static 11ty blog

One of my kids has a simple blog about their favorite video game. The blog uses the default Glitch 11ty blog template, and it was quick & easy to set up - took about 5 minutes. Thanks, Glitch!

As soon as the blog was deployed, things got a little more complicated. My budding blogger requested a "Like" button so they could get some positive feedback on their posts. This meant I had to add a tiny Node/Express API connected to a SQLite database which allowed us to POST and GET likes for each blog post. Let's walk through how I did it, and how you can, too!

Add a Server & API

The first step is to add Express to our project with
npm install express.

Then, add a backend/ directory with a server.js file and routes/api.js

.
├── backend/
│   ├── routes/
│   │   └── api.js <--
│   └── server.js <--
├── public/
├── src/
├── .eleventy.js
└── ...

Our server is pretty simple - it serves the static content from the build/ folder that 11ty generates, and handles a couple API routes from the routes/api.js file:

backend/server.js

const express = require("express");
const app = express();
const apiRoutes = require("./routes/api");

app.use(express.json());

app.use("/api", apiRoutes);

app.use(express.static("build"));

const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

backend/routes/api.js

const express = require("express");
const router = express.Router();

router.post("/likePost", (req, res) => {
  // This is where we'll query the database in the next step!
});

router.get("/getLikes", (req, res) => {
    // ...
});

module.exports = router;

The Database

Next, we'll install sqlite3 for a database that the API will connect to.

First, install sqlite npm install sqlite3, and then create /backend/db/database.js:

.
├── backend/
|   ├── db/
|   |   └── database.js <--   
│   ├── routes/
│   └── server.js
├── public/
├── src/
├── .eleventy.js
└── ...

This file does a few things:

  • On startup
    1. Connect to the database called ./data.db, or create it if it doesn't exist
    2. Create a Posts table if it doesn't exist
    3. Log the contents of the Posts table if it does exist
  • getLikesByPostId is self-explanatory - it gets the likeCount for a given postId, and if the post doesn't have a record in the database yet, it creates that row and sets the likeCount value to 0
  • likePost increments the likeCount for a given postId by +1

❗Make sure to add data.db to your .gitignore at this point!

backend/db/database.js

const sqlite3 = require("sqlite3").verbose();

const db = new sqlite3.Database("./data.db", (err) => {
  if (err) {
    console.error(err.message);
  } else {
    console.log("Connected to the data.db SQLite database.");
  }
});

db.serialize(() => {
  db.run("CREATE TABLE IF NOT EXISTS Posts (postId INT, likeCount INT)");

  db.each("SELECT postId, likeCount FROM Posts", (err, row) => {
    if (err) {
      console.error(err.message);
    }
    console.log("postId:", row.postId, "likeCount:", row.likeCount);
  });
});

function getLikesByPostId(postId, callback) {
  db.get(
    "SELECT likeCount FROM Posts WHERE postId = ?",
    [postId],
    (err, row) => {
      if (err) {
        console.error(err.message);
      } else if (row) {
        callback(row.likeCount);
      } else {
        db.run(
          "INSERT INTO Posts (postId, likeCount) VALUES (?, 0)",
          [postId],
          function (err) {
            if (err) {
              console.error(err.message);
            } else {
              callback(0);
            }
          },
        );
      }
    },
  );
}

function likePost(postId, callback) {
  db.run(
    "UPDATE Posts SET likeCount = likeCount + 1 WHERE postId = ?",
    [postId],
    function () {
      if (!this.changes) {
        db.run(
          "INSERT INTO Posts (postId, likeCount) VALUES (?, 1)",
          [postId],
          function (err) {
            if (err) {
              console.error(err.message);
            } else {
              callback(this.lastID);
            }
          },
        );
      } else {
        callback(this.changes);
      }
    },
  );
}

module.exports = { getLikesByPostId, likePost };

Now we can update our two API routes to run these queries:

backend/routes/api.js

const express = require("express");
const router = express.Router();
const db = require("../db/database");

router.post("/likePost", (req, res) => {
  db.likePost(req.body.postId, (changes) => {
    res.json({ changes });
  });
});

router.get("/getLikes", (req, res) => {
  db.getLikesByPostId(req.query.postId, (likeCount) => {
    res.json({ likeCount });
  });
});

module.exports = router;

Test your API

Let's test it out. Spin up your server node backend/server.js. Your output should look something like this:

λ node backend/server.js
Server running on http://localhost:3000
Connected to the data.db SQLite database.

Now, using Postman, curl, or any API tool you like, try sending a GET request to localhost:3000/api/getLikes?postId=42. (Our database is still empty, so this will create a new record for postId: 42 since it doesn't exist.)

You should get a response of { likeCount: 0 } This means SQLite has successfully created and connected to the data.db database, created a Posts table, added a new record for postId: 42 (with 0 likes), and then returned that row data.

Great! But how do we know it's really doing working? Let's add a like to our imaginary postId: 42 and then see if we can GET it.

Send a POST to localhost:3000/api/likePost, and set the request body to { postId: 42 }. (Here's how to curl that if you don't use Postman: curl --location 'localhost:3000/api/likePost' \ --header 'Content-Type: application/json' \ --data '{"postId": 42}') You should get a response confirming 1 change has been posted to your database: { changes: 1 }.

Now let's try that GET again using the same request as before: localhost:3000/api/getLikes?postId=42. If everything is working correctly, you should now see a response of { likeCount: 1 }!

Now that you've confirmed your API is working to POST and GET likeCounts, let's hook it up to the view.

Show likes on the page

In .eleventy.js, add a postId data property for each post. This file already exists, we just need to add one line:

.eleventy.js

// Find this block in your file...
eleventyConfig.addCollection("posts", function (collection) {
  const coll = collection
    .getFilteredByTag("posts");

  for (let i = 0; i < coll.length; i++) {
    // There will be other content in this file,
    // but we only need to add the line below
    coll[i].data["postId"] = i;
  }

  return coll;
});

Then, we add the postId to the existing post template. Here, we're using the postId to identify a container for the likeCount. We'll add an id of likes-container-{{postId}} and a data-post-id attribute to the container:

.
├── backend/
├── public/
├── src/
|   └── _includes
|       └──layouts
|          └──post.njk <-- 
├── .eleventy.js
└── ...

src/_includes/layouts/post.njk

...
    {{ content | safe }}

    // Add this line below the post content
     id="likes-container-{{postId}}"  data-post-id={{postId}}>
class="controls"> {# The previous and next post data is added in .eleventy.js #} ...

Now we're going to use good old-fashioned vanilla JS to add likes to the page. Create a new folder src/scripts/likes.js:

.
├── backend/
├── public/
├── src/
│   └── scripts/
│       └── likes.js <-- 
└── .eleventy.js

Which should look like this to start:

src/scripts/likes.js

document.addEventListener("DOMContentLoaded", async () => {
  async function updateLikes(postId) {
    const res = await fetch(`/api/getLikes?postId=${postId}`);
    const { likeCount } = await res.json();

    if (!likeCount) {
      likesContainer.innerHTML = "0 likes ❤️";
    } else {
      const likesText =
        likeCount > 1
          ? `${likeCount.toString()} likes ❤️`
          : `${likeCount.toString()} like ❤️`;
      likesContainer.innerHTML = likesText;
    }
  }

  // Find the new likes-container
  const likesContainer = document.querySelector('[id^="likes-container-"]');

  // If we're not on a Post with a likesContainer, do nothing
  if (!likesContainer) {
    return;
  }

  // Get the postId
  const postId = likesContainer.getAttribute("data-post-id");

  // Get likes for this post
  updateLikes(postId);
});

Import this script to your base.njk

src/_includes/layouts/base.njk

    // Add this line inside the 
    

...and add this line anywhere in your .eleventy.js config too, so this file is included in the build:

  eleventyConfig.addPassthroughCopy("./src/scripts/");

Now you can build your static blog with npm start, and serve the files by running node backend/server.js again. Even though eleventy is running on localhost:8080, we're going to view the blog at localhost:3000 since that's where the server is running and serving up our build folder. Click into any blog post, and you'll see something like this at the bottom (unless you happen to be looking at a post with ID 42, which we liked earlier!)

0 Likes

We don't have a button to add likes on the UI yet, but if you want to test displaying an updated likeCount, you can use the API to add a like to a post. The first post is easiest - postId: 0 (You can use the first of the example posts that come with the template, or delete them and add one of your own.)

Go ahead and "Like" postId: 0 with
curl --location 'localhost:3000/api/likePost' \ --header 'Content-Type: application/json' \ --data '{"postId": 0}', and now visit that post in your browser.

Voila!

1 Like

Create the button

Next, let's add a button below the likesContainer:

src/_includes/layouts/post.njk:

     id="likes-container-{{postId}}" data-post-id="{{postId}}">
// New button! id="like-btn-{{postId}}"> Like this post