In this post we are going to take a look at creating a simple Github profile search component using htmx.
What is htmx?
htmx allows you to build modern user interfaces with the simplicity and the power of hypertext. It lets you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes.
It is small ~9KB
(minified and gzipped), dependency-free, extendable and IE11 compatible.
Setting up the project
We are going to setup a simple Express.js application in Node for our demo. The reason being htmx
is dealing with server-rendered html and we don't need any Javascript on the client side to make this work. Every piece of UI is rendered by the server, whether it is the initial full-page markup or the dynamically rendered profile component. All the HTML for the page is served from the server side.
Let's get started bootstrapping our project. Open up the terminal and issue the following commands to setup a simple express.js
application boilerplate.
mkdir github-search
cd github-search
npm init -y
npm install --save express pug body-parser
mkdir views
touch index.js views/index.pug
Server : index.js
Our server application is a very simple one. We just render a simple HTML page with a form and input field to search for the user name. And we are using pug as our template engine for the express app and the body-parser library which is Node.js body parsing middleware to parse incoming request bodies in a middleware before your handlers, available under the req.body
property.
const express = require('express');
const fetch = require('node-fetch');
const bodyParser = require('body-parser');
const pug = require('pug');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine','pug');
app.use(express.static(__dirname + '/assets'));
app.get('/', (req, res) => {
res.render('index');
});
app.listen(PORT);
console.log('root app listening on port: 3000');
views/index.pug
Our index.html
will be a pug template where we will render our search form.
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Github Profiles
link(rel="stylesheet", href="style.css")
script(src="https://unpkg.com/htmx.org")
body
.container
h1 Github Profile
form(hx-post="/users", hx-target="#profile")
input#search(type="search", placeholder="Search here...",name="username",required)
button#searchBtn(type="submit") Search
main#profile
h2 Search for a Github Profile !
The important thing to note here is the hx-post
attribute in the form element where we will specify our api endpoint for getting the user information from the Github api. Using the hx-post
attribute means, htmx will send an AJAX - POST request to the http://localhost/users
api endpoint, when you submit the form.
hx-post
is a set of attributes that allow you to issue AJAX requests directly from HTML. htmx supports all the HTTP request methods like get, post, put, patch and delete.
And the hx-target
attribute tells us that once we get the response from the server, which is actually an HTML markup we will replace the innerHTML
of the #profile
element with the same. All these things are taken care internally by the htmx
library, we just need to give the CSS selector for the target element to be replaced.
/users
route
In the server, we will create a new route for handling POST requests for the /users
endpoint. There we will make a call to Github api to fetch the user information based on the username
parameter we got from the response body.
app.post('/users', async (req, res) => {
const { username } = req.body;
const response = await fetch(`https://api.github.com/users/${username}`);
const data = await response.json();
const profile = pug.compileFile('views/components/profile.pug');
res.send(profile(data));
});
Using the JSON response we got from Github, we will construct the html markup based on a pug template which will be something like below.
img(src=avatar_url)
h3
| Login :
a(href=html_url) #{login}
h2
| Name : #{name}
p
| Bio : #{bio}
The response JSON will contain data in the following format, from which we will use the fields like bio, name, login and so on to render the data in our pug templates.
{
...
"bio": '',
"name": '',
"login": '',
"avatar_url": '',
"html_url": ''
...
}
We are using the compileFile function from the pug library to compile the pug template and pass on the data to the compiled template to get the generated markup and finally send it in the api response. htmx will automatically pick this response and replace the innerHTML of the appropriate target element we specified earlier in our HTML using the hx-target
attribute of the form element.
full index.js
This is the full and final code for the server.
const express = require('express');
const fetch = require('node-fetch');
const bodyParser = require('body-parser');
const pug = require('pug');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.set('view engine','pug');
app.use(express.static(__dirname + '/assets'));
app.get('/', (req, res) => {
res.render('index');
});
app.post('/users', async (req, res) => {
const { username } = req.body;
const response = await fetch(`https://api.github.com/users/${username}`);
const data = await response.json();
const profile = pug.compileFile('views/components/profile.pug');
res.send(profile(data));
});
app.listen(PORT);
console.log('root app listening on port: 3000');
You can add a start
script to the package.json which can be later used to start our server.
package.json
...
"scripts": {
"start": "node index.js"
}
...
Now you can fire up our Node.js server and view the app in action at http://localhost:3000
.
npm start
Code and Demo
The code for this example is hosted in this Github repository. You can view the live demo in action here.
Top comments (3)
Nice Test case (as all your others about HTMX..)
I am however still struggling with the HTMX-JavaScript necessary (?) combination.
You mention the following :
"The reason being HTMX is dealing with server-rendered html and we don't need any JavaScript on the client side to make this work."
I guess this means that you can not really bypass JavaScript as soon as you work inside a web-framework (flask for me). So if, for example, a date is needed from an HTML input tag, for further python processing, you would still require some heavy JavaScript lifting to possibly customize a date picker and make the web data accessible to python.
Customizing a date picker and using htmx are two different things in my opinion. If you want to send the date value to your server, you can still do it via htmx. I think understanding the strengths and limits of htmx is a great starting point.
htmx.org/essays/hypermedia-driven-...
Thank you for your answer. I totally agree with you that they are two different things. I am an exclusive Flask-Python person and furthermore very allergic to JavaScript. So I am always looking for ways to totally avoid JS-which of course is doomed to fail-.
At present I use a JS piece of code which listens to the date input ,converts the web variable to a python variable (json..) and launches a flask route to set the python variable as global. This in turn allows a possible hx-put to the web page in a separate flask route.
All in ..an extremely cumbersome process- I will take pleasure in reading the reference you provided.