Creating a Github profile search component in htmx
Tags: htmx, search, hyperscript
Published: 2021-03-18 06:27:25

In this post we are going to take a look at creating a simple Github profile search component using [htmx](https://htmx.org).

# What is htmx?
[htmx](https://htmx.org) 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](https://expressjs.com/) 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](https://pugjs.org) as our template engine for the express app and the [body-parser](https://github.com/expressjs/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.

```js
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.

```pug
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.

![You have no power here](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vvjhqztuhp93ptmi8ufa.jpeg)


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.

```js
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.

```json
{
  ...
  "bio": '',
  "name": '',
  "login": '',
  "avatar_url": '',
  "html_url": ''
  ...
}
```

We are using the [compileFile](https://pugjs.org/api/reference.html#pugcompilefilepath-options) 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.

```js
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
```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
![demo.gif](https://raw.githubusercontent.com/rajasegar/htmx-github-search/main/demo.gif)

The code for this example is hosted in this [Github](https://github.com/rajasegar/htmx-github-search) repository. You can view the live demo in action [here](https://htmx-github-search.herokuapp.com/).