Creating a Github profile search component in htmx

Rajasegar Chandran - Mar 18 '21 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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 !
Enter fullscreen mode Exit fullscreen mode

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.

You have no power here

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));
});
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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": ''
  ...
}
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode

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"
}
...
Enter fullscreen mode Exit fullscreen mode

Now you can fire up our Node.js server and view the app in action at http://localhost:3000.

npm start
Enter fullscreen mode Exit fullscreen mode

Code and Demo

demo.gif

The code for this example is hosted in this Github repository. You can view the live demo in action here.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player