Skip to content

Instantly share code, notes, and snippets.

@MansourM61
Last active January 15, 2026 14:46
Show Gist options
  • Select an option

  • Save MansourM61/493677c095f86f124669b1c95a260e2c to your computer and use it in GitHub Desktop.

Select an option

Save MansourM61/493677c095f86f124669b1c95a260e2c to your computer and use it in GitHub Desktop.

HTMX Project Setup

htmx is a small, free-dependency library that gives access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes.

Project Setup

  1. Start a new project:

    npm init -y
  2. Install the dependencies:

    npm i express handlebars express-handlebars dotenv
    npm i -D typescript @types/express @types/node ts-node-dev
  3. Install Inline HTML extension in VSCode.

  4. Update package.json contents:

    {
        "name": "server",
        "version": "1.0.0",
        "description": "",
        "main": "server.js",
        "scripts": {
            "dev": "ts-node-dev --respawn --debug --deps --rs src/server.ts",
            "build": "tsc",
            "start": "node dist/server.js"
        },
        "keywords": [],
        "author": "",
        "license": "ISC",
        "type": "commonjs",
        //...
    }
  5. Create tsconfig.json in the project root folder with the following content:

    {
        "compilerOptions": {
            "target": "ES6",
            "module": "CommonJS",
            "outDir": "./dist",
            "rootDir": "./src",
            "strict": true,
            "esModuleInterop": true,
            "paths": {
                "@/views/*": [
                    "./src/views/*"
                ]
            },
        },
        "include": [
            "src/**/*",
            "./typings.d.ts",
            "templates/**/*"
        ],
        "exclude": [
            "node_modules"
        ],
    }
  6. Create typings.d.ts in the project root folder with the following content:

    declare module "*.css"
  7. Create src/lib, src/views, templates/layout, public/css and public/js folder in the root directory.

  8. Create src/server.ts file with the following content:

    // https://medium.com/@ayobamieae/a-detailed-guide-on-how-to-set-up-a-simple-server-with-typescript-72c1594e5692
    // https://dev.to/shayy/create-templates-in-nodejs-with-handlebar-256c
    import express, { Request, Response } from 'express';
    import dotenv from 'dotenv';
    import { engine } from 'express-handlebars';
    import path from 'path';
    
    const templateDir = path.join(process.cwd(), "templates")
    
    // .env configuration
    dotenv.config();
    const PORT = process.env.PORT || 8000;
    
    // create app
    const app = express();
    app.use(express.urlencoded({ extended: false }));
    
    // handlebars setup
    app.engine('handlebars', engine({ // Here we define what format you will use (That means what's at the end of each file, for example test.handlebars or test.hbs)
        defaultLayout: 'main', // That's the name of your template file. In my case it's main.handlebars
        layoutsDir: path.join(templateDir, 'layouts') // That's the directory where the template file is
    }));
    app.set('views', templateDir); // Here you give express the information that it has to look at all files that are in the path /views
    app.set('view engine', 'handlebars'); // Here you say express that you are using handlebars to build your website
    app.enable('view cache');
    
    // static assets
    app.use(express.static('public'));
    
    /* Routes to be added here*/
    
    // listen to port
    app.listen(PORT, () => {
        console.log(`Server is running at http://localhost:${PORT}`);
    });
  9. Create src/lib/template-utils.ts with the following content:

    import path from "path";
    import fs from 'fs';
    import Handlebars from 'handlebars';
    import HandlebarsTemplateDelegate from 'handlebars';
    
    const templateDir = path.join(process.cwd(), "templates")
    
    export function loadTemplate(page: string[], options?: { [x: string]: unknown }): HandlebarsTemplateDelegate<unknown> {
        const templateString = fs.readFileSync(path.join(templateDir, page) + ".handlebars", 'utf8');
        return Handlebars.compile(templateString, options);
    }
  10. Create public/css/styles.css with the required styles.

  11. Download htmx.min.js and save it as public/js/htmx.min.js.

  12. Create templates/layout/main.handlebars with the following content:

    <html>
    <head>
        <title>Website Name</title>
        <link rel="stylesheet" href="/css/styles.css" />
        <script src="/js/htmx.min.js"></script>
    </head>
    <body>
    
        {{{body}}}
    
    </body>
    </html>

Once the project is setup, create routes and their corresponding HTML files/templates. The htmx attributes can added to the HTMLs tags. As long as the main.html file that loads /js/htmx.min.js is used as the base, HTMX will work.

Add a Page Using express-handlebars

  1. Add a new route to server.ts:

    // landing page routes
    app.get('/route-name/:id', (req: Request, res: Response) => {
        const { id } = req.params;
        const data = extractData(id);
    
        res.render("index", {
            showTitle: true,
            param: data
        });
    });

    route-name is the unique route name that is picked up by express-handlebars, which will look for a handlebars file with the same name in the templates folder.

  2. Add templates/route-name.handlebars file with the following content:

    <header>
    <h1>Website</h1>
    </header>
    
    <main>
        <!-- ... -->
        <div>
            <p>{{param.title}}</p>
            <!-- ... -->
        </div>
        <!-- ... -->
    </main>

    This file is transpiled by express-handlebars and then sent to the browser as landing page.

Add a Page Using handlebars

  1. Add a new route to server.ts:

    import * as route from './views/route';
    
    // ...
    
    // landing page routes
    app.get('/route-name/:id', (req: Request, res: Response) => {
        const { id } = req.params;
        const data = extractData(id);
    
        res.send(route.default(data));
    });
  2. Create src/views/route.ts file with the following content:

    import { loadTemplate } from "../lib/template-utils";
    import "./partial-page"
    
    const PAGE = "route"
    
    const template = loadTemplate([PAGE])
    
    export default template;
  3. Create templates/route.handlebars file with the following content:

        <div>
            {{#each parameter}}
                {{>PartialPage param=.}}
            {{/each}}
        </div>
  4. Create src/views/partial-page.ts file with the following content:

    import { loadTemplate } from "../lib/template-utils";
    import Handlebars from "handlebars"
    
    const PAGE = "partial-page"
    
    const template = loadTemplate([PAGE])
    Handlebars.registerPartial("PartialPage", template)
    
    export default template;
  5. Create templates/partial-page.handlebars file with the following content:

    <div data-id="{{param.id}}">
        <p>{{param.title}}</p>
        <!-- ... -->
    </div>

Add a Page Using Template Literals

  1. Add a new route to server.ts:

    import * as route from './views/route';
    
    // ...
    
    // landing page routes
    app.get('/route-name/:id', (req: Request, res: Response) => {
        const { id } = req.params;
        const data = extractData(id);
    
        res.send(route.default(data));
    });
  2. Create src/views/route.ts file with the following content:

    const createBookTemplate = (data) => /*html*/`
    <li data-id="${data.id}">
        <p>${data.title}</p>
        <!-- ... -->
    </li>
    `;
    
    export default createBookTemplate;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment