Within the first half of this text, we arrange an online improvement stack and created a easy instance software utilizing Bun, HTMX, Elysia, and MongoDB. Right here, we’ll proceed exploring our new stack whereas cleansing up and abstracting the instance software’s information entry layer and including extra complicated HTMX interactions. We’ll additionally add one other part to the tech stack: Pug, a well-liked JavaScript template engine that works effectively with HTMX and helps with configuring DOM interactions.
The instance software
Our instance software at the moment consists of a kind and a desk. The shape lets customers enter quotes together with their authors, which might then be searched and displayed utilizing the applying’s person interface. I’ve added a little bit of CSS to the interface to make it look extra fashionable than what we left off with in Half 1:
IDGRight here’s the front-end code for the up to date interface:
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<kind hx-post="/add-quote" hx-swap-oop="beforeend:#data-list" hx-trigger="each time">
<enter kind="textual content" identify="quote" placeholder="Enter quote">
<enter kind="textual content" identify="writer" placeholder="Enter writer">
<button kind="submit">Add Quote</button>
</kind>
<ul id="data-list"></ul>
<button hx-get="/quotes" hx-target="#data-list">Load Knowledge</button>
We’re utilizing HTMX to drive the method of submitting the shape and loading information into the desk. I’ve additionally cleaned up the applying’s again finish so the database connectivity is now shared. This is that portion of src/index.ts:
import { Elysia } from "elysia";
import { staticPlugin } from '@elysiajs/static';
const { MongoClient } = require('mongodb');
// Database connection particulars
const url = "mongodb://127.0.0.1:27017/quote?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.8.0";
const dbName = "quote";
const collectionName = "quotes";
let shopper = new MongoClient(url, { useUnifiedTopology: true });
// Hook up with the database (referred to as solely as soon as)
async perform connectToDatabase() {
attempt {
await shopper.join();
} catch (error) {
console.error(error);
throw error; // Re-throw the error to point connection failure
}
return { shopper, assortment: shopper.db(dbName).assortment(collectionName) };
}
// Shut the database connection
async perform closeDatabaseConnection(shopper) {
await shopper.shut();
}
What we’re doing right here is defining the database URL because the default MongoDB localhost tackle, together with a database and a set identify. Then, we use an async perform, connectToDatabase(), to attach the shopper and return it related to the gathering. Our code can then name this technique every time it must entry the database, and when it is performed it might probably name shopper.shut().
Utilizing the database connection
Let’s take a look at how our server endpoints will use this database help. For brevity, I’m simply exhibiting the /quotes endpoint that drives the desk:
// Shut the database connection
async perform closeDatabaseConnection(shopper) {
await shopper.shut();
}
async perform getAllQuotes(assortment) {
attempt {
const quotes = await assortment.discover().toArray();
// Construct the HTML desk construction
let html="<desk border="1">";
html += '<tr><th>Quote</th><th>Creator</th></tr>';
for (const quote of quotes) {
html += `<tr><td>${quote.quote}</td><td>${quote.writer}</td></tr>`;
}
html += '</desk>';
return html;
} catch (error) {
console.error("Error fetching quotes", error);
throw error; // Re-throw the error for correct dealing with
}
}
// Major software logic
const app = new Elysia()
.get("https://www.infoworld.com/", () => "Hi there Elysia")
.get("/quotes", async () => {
attempt {
const { shopper, assortment } = await connectToDatabase();
const quotes = await getAllQuotes(assortment);
await closeDatabaseConnection(shopper);
return quotes;
} catch (error) {
console.error(error);
return "Error fetching quotes";
}
})
.use(staticPlugin())
.pay attention(3000);
console.log(
` Elysia is operating at ${app.server?.hostname}:${app.server?.port}`
);
This offers us a back-end /quotes GET endpoint that we will name to get the quotes information. The endpoint calls the getAllQuotes() technique, which makes use of the gathering from connectToDatabase() to get the array of quotes and authors. It then generates the HTMX for the rows.
Lastly, we ship a response holding the rows as HTMX, and the rows are inserted into the desk.
Add the Pug templating engine
Manually creating the row HTMX could cause frustration and errors. A templating engine lets us outline the HTMX construction in an everlasting file with a clear syntax.
The preferred HTML templating engine for JavaScript is Pug. Utilizing it’ll make creating the views on the server a lot simpler and extra scalable than inlining within the JavaScript code. The essential thought is to take our information objects and cross them into the template, which applies the information and outputs HTML. The distinction right here is that we’re producing HTMX relatively than HTML. We are able to do that as a result of HTMX is basically HTML with extensions.
To begin, add the Pug library to the challenge with: $ bun add pug.
When that completes, create a brand new listing on the root of the challenge referred to as /views: ($ mkdir views), then add a brand new file referred to as quotes.pug:
doctype html
h1 Quotes
desk
thead
tr
th Quote
th Creator
th Actions
tbody
every quote in quotes
tr(id=`quote-${quote._id}`)
td #{quote.quote}
td #{quote.writer}
td
button(hx-delete=`/quotes/${quote._id}` hx-trigger="click on" hx-swap="closest tr" hx-confirm="Are you positive?") Delete
#{quote._id}
Pug makes use of indentation to deal with nested parts. Attributes are held inside parentheses. Plain textual content akin to the phrase Delete is offered as-is. All of this provides us a compact solution to describe HTML and/or HTMX. See the Pug homepage to be taught extra about its syntax.
Discover that inside a string, we have to use ${}. The #{} syntax helps you to reference any information objects that have been injected into the template. That is much like token interpolation in a framework like React. The essential thought is to outline the general HTML/HTMX construction, then present variables to the template which are referenced with #{} and ${}.
We offer the variables again on the server /quotes endpoint, which makes use of getAllQuotes():
import pug from 'pug';
//...
async perform getAllQuotes(assortment) {
attempt {
const quotes = await assortment.discover().toArray();
// Render the Pug template with the fetched quotes
const html = pug.compileFile('views/quotes.pug')({ quotes });
return html;
} catch (error) {
console.error("Error fetching quotes", error);
throw error; // Re-throw the error for correct dealing with
}
}
So, we get the quotes from the database, then compile the Pug template and cross the quotes in. Then Pug does the job of folding collectively the HTML and the information. The general circulate is:
- Request arrives at
GET /quotes. - Quotes are retrieved from MongoDB.
- The Pug template receives the quotes.
- The Pug template renders the quotes as HTML and/or HTMX.
- The populated HTML and/or HTMX is distributed because the response.
The ensuing display seems to be one thing like this:
IDGDOM interactions: Deleting a row
Now we have to get our Delete button working. Merely issuing a delete request and dealing with it on the server and database is simple to do with what we’ve seen already, however what about updating the desk to replicate the change?
There are a number of methods to method the replace. We may merely refresh your entire desk, or we may use JavaScript or HTMX to delete the row from the desk. Ideally, we’d like to make use of the latter possibility and maintain all the things as HTMX.
In our views/quotes.pug template, we will use pure HTMX to delete the row:
tbody(hx-target="closest tr" hx-swap="outerHTML")
every quote in quotes
tr(id=`quote-${quote._id}`)
td #{quote.quote}
td #{quote.writer}
td
button(hx-delete=`/quotes/${quote._id}` hx-trigger="click on" hx-confirm="Are you positive?") Delete
The important components listed below are the hx-target=”closest tr” and hx-swap=”outerHTML” on the tbody. (The hx-confirm permits you to present a verify dialog.) The hx-target says to interchange the closest tr to the set off aspect (the button) with the response. The outHTML in hx-swap ensures we take away the entire desk row aspect, not simply its contents. On the server aspect, we return a profitable (HTTP 200) with an empty physique, so HTMX will merely delete the row:
async perform deleteQuote(assortment, quoteId) {
attempt {
const outcome = await assortment.deleteOne({ _id: new ObjectId(quoteId) });
if (outcome.deletedCount === 1) {
return "";
} else {
throw new Error( "Quote not discovered");
}
} catch (error) {
console.error("Error deleting quote", error);
throw error; // Re-throw the error for correct dealing with
}
}
Right here, we’re simply beginning to get into extra concerned DOM interactions. HTMX can even add easy transition results to swaps in a row-deletion state of affairs like ours. You may see an instance on the HTMX homepage.
Conclusion
Though this two-part tutorial incorporates newer applied sciences like Bun and Elysia, essentially the most noticeable part is HTMX. It really modifications the way in which an software works as in comparison with typical JSON APIs.
When mixed with a templating engine like Pug and a database like MongoDB, the work of producing UIs and dealing with requests is clean. As an software grows in dimension, Pug options like template inheritance additionally come in useful.
For DOM interactions, HTMX sports activities versatile performance out of the field through hx-swap and hx-target. For extra concerned use circumstances, you may at all times fall again on JavaScript.
Generally, this complete stack works effectively collectively. You may additionally recognize the pace of Bun every time you’ll want to drop into the command line to do one thing like add a dependency.
Yow will discover the code for this tutorial on my GitHub repository.
Copyright © 2024 IDG Communications, Inc.


