Creating a Community Board with HTMX & Express

Creating a Community Board with HTMX & Express

In this article, I create a community board project using HTMX on the client, Express on the server and Handlebars for server-side templating.

Step 1. Basic app

We create a folder for our application. Then we cd into the folder and run npm init -y.

Then we install express and hbs using npm or any other Node package manager.

npm init -y
npm install express hbs --save

Create index.js with the following contents:

const express = require('express');
const app = express();
const hbs = require('hbs');
const port = 3000;

app.set('view engine', 'hbs');
app.use(express.static('static'));
app.use(express.urlencoded({ extended: true })); // support encoded bodies
app.get('/', (req, res) => {
    res.render("index");
})

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
})

Create two folders called views and static in the root of your application directory.

  • The views folder contains the templates used for rendering the application.

  • The static folder contains static files like images, CSS, etc.

Create a file called index.hbs in the views folder with this content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Community Board</title>
    <script src="https://unpkg.com/htmx.org"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1.5.11/css/pico.min.css">
</head>
<body class="container" hx-boost="true">
    <nav>
        <ul>
            <li>Home</li>
            <li><a href="/about">About</a></li>
            <li><a href="/submit">Submit</a></li>
        </ul>
    </nav>
</body>
</html>

This loads htmx and PicoCSS (a classless styling framework) from a CDN. In production, this is not recommended and should be loaded from your origin, but for this demo it should be okay.

Start the server using node index.js (this script is compatible with Bun too)

💡
Every time the index.js file is changed, you must restart the server with the above command. However, changing the templates does not require a server restart.

If you load this page, you should see this:

A navbar with three options for "Home", "About" and "Submit"

Step 2. Listing articles

Create a file in the views directory with the name articles.hbs with this content:

{{#each articles}}
<article>
    <h1>{{this.name}}</h1>
    <p>{{this.description}}</p>
</article>
{{/each}}

Then we get the list of articles (using a database query). For this example, I am using a simple object to store articles, however a database could be added in later.

var articles = [
    {
        "name": "Live GitHub Status (for GNOME Wayland)",
        "description": "I had this cool idea to show the currently focused application on my system as part of my GitHub status."
    },
    // ...
];

We define a path to return a rendered list of articles in our Express server:

app.get('/articles/list', (req, res) => {
    res.render("articles", {articles: articles});
})

And in index.hbs, we add a div that will fetch the articles and swap the returned HTML into itself.

<div hx-get="/articles/list" hx-trigger="load" hx-swap="outerHTML" aria-busy="true">Loading articles...</div>

Two articles showing on the page

Step 3. About page

On the about page, we follow similar steps as the index page.

Step 4. Submitting posts

We create a file called submit.hbs in the views directory.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Submit a Post</title>
    <script src="https://unpkg.com/htmx.org"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1.5.11/css/pico.min.css">
</head>
<body class="container" hx-boost="true">
    <nav>
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
        <li>Submit</li>
      </ul>
    </nav>

    <form action="/submit" hx-post="/submit" method="post">
      <input type="text" name="title" placeholder="Title" />
      <input type="text" name="description" placeholder="Description" />
      <input type="submit" value="Submit" />
    </form>
  </body>
</html>

We add this page to a /submit path (using a GET request, POST is used for the form action)

app.get('/submit', (req, res) => {
    res.render("submit");
})

Then we configure a path on our server to add to the list of articles, returning a message Post submitted!

app.post('/submit', (req, res) => {
    articles.unshift({name: req.body.title, description: req.body.description});
    res.send('<p>Post submitted!</p>');
})
💡
As this is a simple response, I've not created a template for it. Also, this code is purely for adding to the beginning of the articles object. This should be replaced with database queries.

Then a user can submit posts.

Submit form with title and description filled in

Homepage showing new post

That's it! If you have any feedback, please leave a comment. Click the ❤️ if you liked this.