Express </> HTMX Components
Getting Started
Install
Obviously you need express for this to work. The following is the minimal set of dependencies I'd recommend:
npm install express
npm install express-session
npm install express-htmx-components
I'd also suggest you start with the following simple project structure:
my-project/
├ components/ -- pages
│ └ lib/ -- ui library
├ lib/ -- utility library
├ static/ -- static files
├ package.json
└ server.js
Usage
server.js
#! /usr/bin/env node
const express = require('express');
const path = require('path');
const session = require('express-session');
const components = require('express-htmx-components');
const app = express();
const COMPONENTS_DIR = path.join(path.resolve(__dirname), 'components');
const SESSION_SECRET = 'xxx';
const PORT = 8888;
app.disable('x-powered-by');
app.enable('trust proxy');
app.use(
session({
secret: SESSION_SECRET,
resave: true,
saveUninitialized: true,
cookie: {
secure: false,
httpOnly: true,
},
})
);
app.use('/static', express.static('static'));
app.use(express.urlencoded({ extended: false }));
// Auto-load components:
components.init(app, COMPONENTS_DIR).then(() => {
app.use((req, res) => {
console.log('404: Not Found');
res.status(404);
res.send('404: Not Found.');
})
app.listen(PORT, () => {
console.log(`Server started, listening on ${PORT} ..`)
});
});
The components.init()
function will recursively scan the components
directory to load the htmx components. Because each component is also an Express
route each component is also a web page. Anything that's not a web page should
be stored outside the components
directory.
Since each component is a web page in its own right it's easy to debug each component in isolation. It's one of the advantages of express-htmx-component.
/components/lib/name.js
Let's create a simple component that remembers your name. For a real app you'd want a database to store your name but for this simple test let's just store it in the session:
const component = require("express-htmx-components");
const { html } = require("express-htmx-components/tags");
const name = component.get("/name", ({ session, name }) => {
if (name) {
session.name = name;
}
if (session.name) {
return html`
<div id="name-container">
Hello <span class="name">${session.name}</span>!
</div>
`;
} else {
return html`
<div id="name-container">
<form hx-get="/name" hx-target="#name-container" hx-swap="outerHTML">
What's your name? <input name="name" id="name" type="text" />
<button type="submit">Submit</button>
</form>
</div>
`;
}
});
module.exports = { name };
This component simply returns a div and the content of that div depends on if
the session has saved your name. If you submit your name using the form it will
add your name as a query param to the request which will then be passed to the
component as the name
property.
The session
property is a special prop that is linked to Express
req.session
. This allows your component to access the session.
Since this component has a URL you can test it by going to
http://localhost:8888/name
. Each component is also a web page.
/components/home.js
Now let's include the name component above into another page:
const component = require("express-htmx-components");
const { html } = require("express-htmx-components/tags");
const { name } = require("./lib/name");
const home = component.get("/", ({ session }) => {
return html`
<h1>Welcome to HTMX</h1>
$${name.html({ session })}
`;
});
module.exports = { home };
Including a component in another component is simply calling the component's
.html()
function. Here we're just passing the session from the page and
let the component's own internal logic handle how to display the name.
We use a special substitution with $${}
instead of the regular ${}
because
the component returns html. The html
tag escapes special characters such as
<
and >
with <
and >
in order to prevent XSS attacks from html
content in user submitted data. But this interferes with including components
into your html string. To overcome this the html
tag implements a special
$${}
substitution that does not do any replacement of special characters.
The rule of thumb is:
- If it's a component use
$${}
- If it's user data use
${}
Now we can access the page by going to http://localhost:8888
.
Developer comfort
Express-htmx-component mainly uses javascript template literal to define html. But writing code inside string sucks because you don't get nice tools like syntax highlighting or autocompletion. To remedy this, if you're using VSCode install the Inline HTML extension.