How to setup "Eta JS" in bun, express and Typescript?

mr zlaam (admin)

Published: July 17th, 2025

How to setup "Eta JS"  in bun, express and Typescript?

Image by : LogRocket

πŸŒ€ Using Eta Templating Engine with Bun + Express + TypeScript

Eta is a super lightweight, blazing fast templating engine written in TypeScript. It works perfectly with Bun, and this guide walks you through setting it up with Express in a Bun-powered project.


Prerequisites

Make sure you have:


1. Project Structure Example


β”œβ”€β”€ bun.lock
β”œβ”€β”€ package.json
β”œβ”€β”€ README.md
β”œβ”€β”€ src
β”‚   β”œβ”€β”€ app.ts
β”‚   β”œβ”€β”€ feature
β”‚   β”‚   └── test
β”‚   β”‚       β”œβ”€β”€ test.controller.ts
β”‚   β”‚       └── test.routes.ts
β”‚   β”œβ”€β”€ router
β”‚   β”‚   └── default.routes.ts
β”‚   β”œβ”€β”€ server.ts
β”‚   β”œβ”€β”€ utils
β”‚   β”‚   └── asynchandler.util.ts
β”‚   └── views
β”‚       └── index.eta
└── tsconfig.json

2. Install Eta

bun add eta

3. Configure Eta in Express

In your src/app.ts :


import express from "express";
import { Eta } from "eta";
export const eta = new Eta({
  views: path.resolve("src/views"),
  cache: true,
});

import { defaultRouter } from "./router/default.routes";
import path from "path";
const app = express();
app.engine("eta", buildEtaEngine());
app.set("view engine", "eta");
app.set("views", path.resolve("src/views"));
app.use(express.json());
app.use("/", defaultRouter);

function buildEtaEngine() {
  return (
    path: string,
    opts: object,
    callback: (arg1: null | Error, arg2?: string) => void
  ) => {
    try {
      const fileContent = eta.readFile(path);
      const renderedTemplate = eta.renderString(fileContent, opts);
      callback(null, renderedTemplate);
    } catch (error: unknown) {
      callback(error as Error);
    }
  };
}
export default app;


4. Create Your View

File: src/views/index.eta


<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
      crossorigin="anonymous"
    />
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
      crossorigin="anonymous"
    ></script>
    <title>Home</title>
  </head>
  <body class="container">
    <h1>This is index page</h1>
    <div class="input-group mb-3">
      <span class="input-group-text" id="basic-addon1">@</span>
      <input
        type="text"
        class="form-control"
        placeholder="Username"
        aria-label="Username"
        aria-describedby="basic-addon1"
      />
    </div>
    <div class="alert alert-warning" role="alert">
      A simple warning alertβ€”check it out!
    </div>
    <div class="alert alert-info" role="alert">
      A simple info alertβ€”check it out!
    </div>
    <div class="alert alert-light" role="alert">
      A simple light alertβ€”check it out!
    </div>
    <div class="alert alert-dark" role="alert">
      A simple dark alertβ€”check it out!
    </div>
    device
  </body>
</html>

5. Create Server file

File:src/server.ts


import app from "./app";
const port = process.env.PORT || 4000;
; (function() {
  app.listen(port, () => {
    console.log(`Server is running at http://localhost:${port}`)
  })
})()

6. Create Routes

File:src/routes/default.routes.ts


import { Router } from "express";
import { testRouter } from "../feature/test/test.routes";
export const defaultRouter: Router = Router();
defaultRouter.use("/test", testRouter);

&

Files: src/feature/test/test.routes.ts


import { Router } from "express";
import { testController } from "./test.controller";
export const testRouter: Router = Router();
testRouter.route("/").get(testController.testFunction);

7. Create Controller

File:src/feature/test/test.controller.ts


import { asyncHandler } from "../../utils/asynchandler.util";
class TestController {
  testFunction = asyncHandler(async (_, res) => {
    const data = [
      { id: 1, name: "Test Item 1" },
      { id: 2, name: "Test Item 2" },
      { id: 3, name: "Test Item 3" },
    ];
    res.render("index", { data: data }); // it will directly recognize the eta files in views
  });
}

export const testController = new TestController();

8. Run the Server

In package.json:


{
  "name": "my-randomo-app",
  "module": "index.ts",
  "type": "module",
  "private": true,
  "scripts": {
    "start": "bun run ./src/server.ts",
    "start:dev": "bun run --watch ./src/server.ts"
  },
  "devDependencies": {
    "@types/bun": "latest",
    "@types/ejs": "^3.1.5",
    "@types/express": "^5.0.3"
  },
  "peerDependencies": {
    "typescript": "^5"
  },
  "dependencies": {
    "eta": "^3.5.0",
    "express": "^5.1.0"
  }
}

Run with:

bun run start:dev

Optional: Enable Syntax Highlighting in VS Code and auto completion

VS Code doesn’t recognize .eta by default. Fix that:

Stpe 1: Open Vs code and press Ctrl+Shift+P and the search workspace setting and then hit Enter
Step 2: Open .vscode/setting.json and add the following lines:


"files.associations": {
  "*.eta": "html"
}

and save the file. Congratulations You've added autocompletion and syntax highlighting.

Done!

You now have a blazing fast, lightweight, TypeScript-native templating engine running on Bun and Express.