How to setup "Eta JS" in bun, express and Typescript?
mr zlaam (admin)
Published: July 17th, 2025

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:
- Bun installed: https://bun.sh/docs/installation
- A project already initialized with Express and TypeScript
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.