本文 AI 產出,尚未審核

ExpressJS (TypeScript) – Static Files 與 Template Engines

EJS / Handlebars / Pug 介紹(如需模板)


簡介

Node.js 生態系中,Express 是最常被使用的 Web 框架,而 TypeScript 則為 JavaScript 帶來靜態型別,讓程式碼更安全、更易於維護。
開發一個完整的網站時,除了 API 邏輯,靜態資源(CSS、圖片、前端套件)與 動態頁面模板 同樣不可或缺。透過 Express 內建的 static middleware,我們可以快速提供檔案服務;而使用模板引擎(如 EJS、Handlebars、Pug)則能在伺服器端產生 HTML,讓資料與版面緊密結合。

本篇文章將以 TypeScript 為基礎,說明如何在 Express 中設定靜態檔案與三大熱門模板引擎,並提供實作範例、常見陷阱與最佳實踐,幫助讀者快速上手、在實務專案中靈活選擇合適的渲染方式。


核心概念

1. 靜態檔案服務

Express 的 express.static 中介層可以把目錄映射成 URL 路徑,讓瀏覽器直接取得 CSS、JS、圖片等資源。

// src/app.ts
import express, { Request, Response, NextFunction } from 'express';
import path from 'path';

const app = express();

// 1️⃣ 設定靜態目錄
app.use('/public', express.static(path.resolve(__dirname, '..', 'public')));

// 測試路由
app.get('/', (req: Request, res: Response) => {
  res.send('Hello Express + TypeScript');
});

export default app;

重點

  • path.resolve 可保證在不同作業系統上取得正確的絕對路徑。
  • 建議將靜態路徑掛載在子路徑(如 /public),避免與 API 路由衝突。

2. 為什麼需要模板引擎?

  • 分離關注點:把 HTML 標記與程式邏輯分開,讓前端設計師與後端開發者可以平行作業。
  • 資料驅動:在伺服器端直接把資料注入 HTML,減少前端額外的 AJAX 請求。
  • 重用版型:透過 layout、partial(部件)機制,避免重複撰寫相同結構。

目前最常見的三種模板引擎:

引擎 語法風格 優點 常見使用情境
EJS 類似 HTML,使用 <% %> 標籤 上手快、與 HTML 幾乎無縫 小型專案、快速原型
Handlebars Mustache 風格,支援 block helpers 強大的 helper 系統,易於擴充 中大型專案、需要自訂 helper
Pug 縮排式 DSL(類似 Python) 省去大量閉合標籤,寫起來更簡潔 喜歡「寫程式碼」感的開發者

以下分別示範在 TypeScript + Express 中的設定與基本使用。


3. EJS 設定與範例

3.1 安裝與設定

npm i ejs
npm i -D @types/ejs
// src/app.ts(續)
import ejs from 'ejs';

// 2️⃣ 設定 view engine 為 EJS
app.set('view engine', 'ejs');
// 設定 views 目錄(預設是 /views)
app.set('views', path.resolve(__dirname, '..', 'views'));

3.2 基本範例

檔案結構

/views
  └─ index.ejs
/public
  └─ css
       └─ style.css
<!-- views/index.ejs -->
<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <title><%= title %></title>
  <link rel="stylesheet" href="/public/css/style.css">
</head>
<body>
  <h1>Hello <%= user.name %>!</h1>
  <ul>
    <% users.forEach(u => { %>
      <li><%= u.name %> - <%= u.age %>歲</li>
    <% }) %>
  </ul>
</body>
</html>
// src/routes/home.ts
import { Router, Request, Response } from 'express';
const router = Router();

router.get('/', (req: Request, res: Response) => {
  const data = {
    title: 'EJS 範例',
    user: { name: '小明' },
    users: [
      { name: '阿華', age: 28 },
      { name: '小美', age: 22 },
    ],
  };
  // 3️⃣ 渲染模板
  res.render('index', data);
});

export default router;

技巧:EJS 允許直接在模板中寫 JavaScript 表達式,若需要較複雜的邏輯,建議在路由層先處理好資料,保持模板「只負責呈現」的原則。


4. Handlebars 設定與範例

4.1 安裝與設定

npm i express-handlebars
npm i -D @types/express-handlebars
// src/app.ts(續)
import exphbs from 'express-handlebars';

// 2️⃣ 設定 Handlebars
app.engine('hbs', exphbs.engine({
  extname: '.hbs',          // 使用 .hbs 副檔名
  defaultLayout: 'main',   // 版型檔案 (views/layouts/main.hbs)
  layoutsDir: path.resolve(__dirname, '..', 'views', 'layouts'),
  partialsDir: path.resolve(__dirname, '..', 'views', 'partials'),
}));
app.set('view engine', 'hbs');
app.set('views', path.resolve(__dirname, '..', 'views'));

4.2 Layout 與 Partial

檔案結構

/views
  ├─ layouts
  │    └─ main.hbs
  ├─ partials
  │    └─ header.hbs
  └─ home.hbs
{{!-- views/layouts/main.hbs --}}
<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <title>{{title}}</title>
  <link rel="stylesheet" href="/public/css/style.css">
</head>
<body>
  {{> header}}   {{!-- 引入 partial --}}
  {{{body}}}     {{!-- 渲染子模板 --}}
</body>
</html>
{{!-- views/partials/header.hbs --}}
<header>
  <h1>My Site</h1>
  <nav>
    <a href="/">Home</a> |
    <a href="/about">About</a>
  </nav>
</header>
{{!-- views/home.hbs --}}
{{!-- 只寫內容,layout 會自動套用 --}}
<h2>Welcome, {{user.name}}!</h2>
<p>你目前有 {{items.length}} 件商品在購物車。</p>
<ul>
  {{#each items}}
    <li>{{this.name}} - ${{this.price}}</li>
  {{/each}}
</ul>
// src/routes/home.ts(續)
router.get('/', (req: Request, res: Response) => {
  const ctx = {
    title: 'Handlebars 範例',
    user: { name: '阿珍' },
    items: [
      { name: '筆記型電腦', price: 29900 },
      { name: '滑鼠', price: 850 },
    ],
  };
  res.render('home', ctx);
});

小技巧:Handlebars 的 {{> partialName}} 讓部件化變得非常直觀;若需要自訂 helper,可在 exphbs.engine({ helpers: {...} }) 中註冊。


5. Pug 設定與範例

5.1 安裝與設定

npm i pug
npm i -D @types/pug
// src/app.ts(續)
app.set('view engine', 'pug');
app.set('views', path.resolve(__dirname, '..', 'views'));

5.2 Pug 語法示例

檔案結構

/views
  └─ dashboard.pug
//- views/dashboard.pug
doctype html
html(lang="zh-TW")
  head
    meta(charset="UTF-8")
    title= title
    link(rel="stylesheet", href="/public/css/style.css")
  body
    h1 Dashboard
    p Hello #{user.name},今天是 #{date}
    ul
      each item in items
        li #{item.name} - $#{item.price}
// src/routes/dashboard.ts
router.get('/dashboard', (req: Request, res: Response) => {
  const data = {
    title: 'Pug 範例',
    user: { name: '小王' },
    date: new Date().toLocaleDateString('zh-TW'),
    items: [
      { name: '鍵盤', price: 1200 },
      { name: '螢幕', price: 8500 },
    ],
  };
  res.render('dashboard', data);
});

注意:Pug 以縮排代表階層,若縮排不正確會拋出 IndentationError,建議在 IDE 中開啟「顯示空白字元」功能,避免混用 Tab 與 Space。


常見陷阱與最佳實踐

陷阱 說明 解決方案 / 最佳實踐
靜態檔案路徑錯誤 express.static 的相對路徑寫錯會導致 404。 使用 path.resolve(__dirname, '..', 'public') 絕對化路徑。
模板檔案副檔名不一致 Express 會根據 view engine 判斷副檔名,若檔名錯誤會無法渲染。 統一設定 extname(如 Handlebars 設 .hbs),檔案名稱須相符。
資料注入未經驗證 直接把使用者輸入渲染到模板,容易產生 XSS。 使用模板引擎的自動 escaping(EJS、Handlebars、Pug 預設皆有),若需原始 HTML,使用 {{{rawHtml}}}(Handlebars)或 !{rawHtml}(EJS)前務必做好 sanitize-html 處理。
過度在模板內寫商業邏輯 例如在 EJS 內大量迴圈或條件,難以維護。 把資料整理的邏輯放在路由或服務層,模板只負責顯示。
Cache 設定不當 開發階段若開啟模板快取,修改模板不會即時生效。 app.set('env', 'development') 時關閉快取;正式環境可啟用 app.enable('view cache') 提升效能。
缺少 Layout/Partial 每個頁面都重寫相同的 <head><header>,維護成本高。 使用 layout(如 Handlebars 的 defaultLayout)與 partial(EJS include、Pug include)做部件化。

最佳實踐總結

  1. 統一路徑管理:所有檔案路徑皆使用 path.resolve,避免平台差異。
  2. 環境分離:開發環境關閉 view cache,正式環境開啟。
  3. 安全第一:依賴模板引擎自動 escaping,若需 raw HTML 必須先 sanitize。
  4. 部件化設計:盡可能將共用區塊抽成 partial / include,減少重複。
  5. 型別安全:在 TypeScript 中為 res.render 的資料建立介面(interface),提升 IDE 自動完成與錯誤檢查。

實際應用場景

場景 推薦模板引擎 為什麼選它
企業內部管理系統(大量表單、共用版型) Handlebars 強大的 helper 與 layout 機制,易於維護大型版面。
快速原型或小型部落格 EJS 直接寫 HTML,學習曲線最低,適合快速上線。
技術文件、開發者工具 UI Pug 縮排語法讓程式碼更乾淨,特別適合大量動態產生的標籤(如表格、樹狀結構)。
多語系網站 任一(配合 i18n 中介層) 只要在渲染前注入翻譯字典,三者皆可支援。
SEO 友好的服務端渲染 Handlebars / Pug 兩者皆支援完整的 HTML 輸出,搜尋引擎可直接爬取。

範例:假設要建置一個「商品列表」頁面,若資料量不大且設計師熟悉 HTML,使用 EJS 能讓設計稿直接貼上;若未來需要加入「購物車」共用區塊,則可逐步遷移到 Handlebars,把購物車區塊抽成 partial,減少改動範圍。


總結

Express + TypeScript 的開發環境裡,靜態檔案服務 只需要一行 express.static 就能完成;而 模板引擎 則提供了從簡單到進階的多樣選擇:

  • EJS:寫起來最像原生 HTML,適合快速開發與小型專案。
  • Handlebars:支援 layout、partial 與自訂 helper,適合中大型應用。
  • Pug:縮排式語法讓標記更精簡,適合喜歡「程式碼即文件」的開發者。

掌握上述設定與最佳實踐,配合 TypeScript 的型別保護,能大幅提升專案的可讀性、可維護性與安全性。未來在實際專案中,只要根據 功能需求、團隊熟悉度與維護成本 來挑選合適的模板引擎,即可在前後端協作上事半功倍。祝開發順利,玩得開心!