Node.js 試算表利器:ExcelJS 完整實戰指南(概念、語法與 CRUD 操作)
在現代 Web 應用程式與後端系統開發中,Excel 檔案(.xlsx)的匯入、匯出與自動化處理是極為常見的需求。無論是財務報表生成、批次資料匯入,還是複雜試算表的客製化排版,選擇一個合適且穩健的工具至關重要。
在 Node.js 生態系中,ExcelJS 憑藉著物件導向的 API 設計、強大的樣式自訂能力,以及對大型檔案串流(Streaming)的完整支援,成為許多企業級專案的首選方案。本文將深入介紹 ExcelJS 的核心架構,並透過完整的 CRUD(Create, Read, Update, Delete)範例,協助您在專案中快速落地應用。
一、ExcelJS 套件介紹與核心優勢
ExcelJS 是一個專為 Node.js 與瀏覽器環境打造的 Excel 檔案處理函式庫,專注於提供現代 .xlsx 格式的深度控制。相較於其他純資料轉換的工具,ExcelJS 的優勢體現在以下面向:
- 完整的樣式控制(Styling):支援字型、前景色/背景色、外框、對齊方式、數字格式(Number Formats)以及富文本(Rich Text)。
- 結構化物件導向模型:按照
Workbook(活頁簿)$\rightarrow$Worksheet(工作表)$\rightarrow$Row(資料列)$\rightarrow$Cell(儲存格)的層級進行操作,邏輯清晰直覺。 - 優異的記憶體管理(Stream I/O):提供基於 Node.js Stream 的 Reader 與 Writer,即便處理數十萬筆的大型資料集,也能有效避免記憶體溢出(Out-of-Memory, OOM)。
- 進階試算表特性:支援公式運算結果保留、合併儲存格(Merge Cells)、資料驗證(Data Validation)、凍結窗格(Frozen Panes)及插入圖片。
二、安裝與環境準備
在 Node.js 專案中,透過 npm 或 yarn 即可完成安裝:
npm install exceljs
在程式碼中引入套件:
const ExcelJS = require('exceljs');
// 若使用 ES Module:
// import ExcelJS from 'exceljs';
三、ExcelJS 核心 CRUD 實戰
接下來,我們將以具體的程式碼範例展示如何對 Excel 檔案進行建立(Create)、讀取(Read)、修改(Update)與刪除(Delete)操作。
1. 建立(Create):建立活頁簿、設定欄位與寫入資料
建立 Excel 時,通常會先定義工作表的欄位結構(Columns),再逐筆或批次新增資料列(Rows),並可針對表頭進行樣式美化。
const ExcelJS = require('exceljs');
async function createExcelFile() {
// 1. 初始化活頁簿
const workbook = new ExcelJS.Workbook();
workbook.creator = 'System Admin';
workbook.created = new Date();
// 2. 新增工作表
const worksheet = workbook.addWorksheet('員工名單');
// 3. 定義欄位標頭與寬度
worksheet.columns = [
{ header: '員工編號', key: 'id', width: 15 },
{ header: '姓名', key: 'name', width: 20 },
{ header: '部門', key: 'dept', width: 20 },
{ header: '薪資 (TWD)', key: 'salary', width: 18, style: { numFmt: '#,##0' } },
{ header: '到職日期', key: 'joinedDate', width: 18, style: { numFmt: 'yyyy-mm-dd' } }
];
// 4. 新增資料列
worksheet.addRow({ id: 'EMP001', name: '王小明', dept: '技術部', salary: 75000, joinedDate: new Date('2022-03-15') });
worksheet.addRow({ id: 'EMP002', name: '李美華', dept: '行銷部', salary: 62000, joinedDate: new Date('2023-01-10') });
worksheet.addRow({ id: 'EMP003', name: '張志豪', dept: '產品部', salary: 81000, joinedDate: new Date('2021-08-01') });
// 5. 表頭樣式美化(深色背景、白色粗體字、置中)
const headerRow = worksheet.getRow(1);
headerRow.eachCell((cell) => {
cell.font = { name: 'Microsoft JhengHei', bold: true, color: { argb: 'FFFFFFFF' } };
cell.fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF1F4E79' } // 深藍色
};
cell.alignment = { vertical: 'middle', horizontal: 'center' };
});
headerRow.height = 25;
// 6. 儲存檔案
await workbook.xlsx.writeFile('員工清冊.xlsx');
console.log('檔案建立成功:員工清冊.xlsx');
}
createExcelFile();
2. 讀取(Read):載入檔案並解析內容
讀取既有檔案時,ExcelJS 提供了便利的走訪機制,可逐列(Row)或逐格(Cell)取得資料。
const ExcelJS = require('exceljs');
async function readExcelFile() {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile('員工清冊.xlsx');
// 取得第一個工作表(亦可使用 workbook.getWorksheet('工作表名稱'))
const worksheet = workbook.worksheets[0];
const records = [];
console.log(`讀取工作表:${worksheet.name},總列數:${worksheet.rowCount}`);
// 走訪每一列資料(排除第 1 列表頭)
worksheet.eachRow((row, rowNumber) => {
if (rowNumber === 1) return;
// 取得各儲存格數值
const rowData = {
rowNumber,
id: row.getCell(1).value,
name: row.getCell(2).value,
dept: row.getCell(3).value,
salary: row.getCell(4).value,
joinedDate: row.getCell(5).value
};
records.push(rowData);
});
console.log('解析結果:', records);
}
readExcelFile();
3. 修改(Update):更新內容、調整格式與增加計算公式
修改既有 Excel 時,可先讀入檔案,針對特定的儲存格重新賦值、套用樣式,甚至新增總計列與公式。
const ExcelJS = require('exceljs');
async function updateExcelFile() {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile('員工清冊.xlsx');
const worksheet = workbook.getWorksheet('員工名單');
// 情境 A:精準修改特定儲存格(例如:調升 EMP001 的薪資)
const targetCell = worksheet.getCell('D2');
targetCell.value = 80000;
targetCell.font = { color: { argb: 'FFC00000' }, bold: true }; // 標註為紅色加粗
// 情境 B:在底部新增「薪資總計」與公式計算
const totalRowNumber = worksheet.rowCount + 1;
const totalRow = worksheet.getRow(totalRowNumber);
totalRow.getCell(3).value = '薪資總計';
totalRow.getCell(3).font = { bold: true };
totalRow.getCell(3).alignment = { horizontal: 'right' };
// 設定 Excel 內建公式 SUM(D2:D4)
totalRow.getCell(4).value = { formula: `SUM(D2:D${totalRowNumber - 1})` };
totalRow.getCell(4).numFmt = '#,##0';
totalRow.getCell(4).font = { bold: true };
// 加上雙底線樣式以符合會計規範
totalRow.getCell(4).border = {
top: { style: 'thin' },
bottom: { style: 'double' }
};
// 儲存為新檔案或覆寫原檔
await workbook.xlsx.writeFile('員工清冊_已更新.xlsx');
console.log('檔案修改成功:員工清冊_已更新.xlsx');
}
updateExcelFile();
4. 刪除(Delete):刪除指定資料列或欄位
ExcelJS 支援動態移除指定的資料列(Row)或欄(Column),並自動重整後續的行索引。
const ExcelJS = require('exceljs');
async function deleteRowAndColumn() {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile('員工清冊.xlsx');
const worksheet = workbook.getWorksheet('員工名單');
// 1. 刪除指定的資料列:spliceRows(開始列號, 刪除列數)
// 例如:刪除第 3 列(EMP002)
worksheet.spliceRows(3, 1);
// 2. 若要刪除指定欄位:spliceColumns(開始欄號, 刪除欄數)
// 例如:刪除「到職日期」這一欄(第 5 欄)
// worksheet.spliceColumns(5, 1);
await workbook.xlsx.writeFile('員工清冊_已刪除部分資料.xlsx');
console.log('刪除作業完成。');
}
deleteRowAndColumn();
四、大型檔案高效能處理:Stream 寫入技巧
當匯出資料量達到數萬甚至數十萬筆時,將所有資料一次性載入記憶體容易導致 Node.js 程序耗盡記憶體而崩潰。ExcelJS 提供了串流寫入器(Stream Writer),能以極低且穩定的記憶體佔用完成匯出。
const ExcelJS = require('exceljs');
const fs = require('fs');
async function exportLargeDataStream() {
const options = {
filename: '大型報表.xlsx',
useStyles: true,
useSharedStrings: true
};
// 建立串流活頁簿寫入器
const workbook = new ExcelJS.stream.xlsx.WorkbookWriter(options);
const worksheet = workbook.addWorksheet('大數據清單');
worksheet.columns = [
{ header: '序號', key: 'id', width: 10 },
{ header: '交易代碼', key: 'code', width: 25 },
{ header: '金額', key: 'amount', width: 15 }
];
// 模擬寫入 50,000 筆資料
for (let i = 1; i <= 50000; i++) {
worksheet.addRow({
id: i,
code: `TXN-${Date.now()}-${i}`,
amount: Math.floor(Math.random() * 10000)
}).commit(); // 每一列寫入後立即 commit 釋放記憶體
}
// 完成工作表與活頁簿
await worksheet.commit();
await workbook.commit();
console.log('大型串流匯出完成。');
}
exportLargeDataStream();
五、實務開發的最佳實踐與建議
- 日期格式轉換:Excel 內部對日期的儲存是基於序列值。使用 ExcelJS 時,建議直接傳入 JavaScript
Date物件,並在欄位或儲存格中明確指定numFmt: 'yyyy-mm-dd hh:mm:ss',以確保跨平台開啟時格式一致。 - 公式計算特性:ExcelJS 寫入公式(如
{ formula: 'SUM(A1:A10)' })時,主要負責建立公式結構。若未指定result預算值,實際計算結果會在使用者以 Microsoft Excel 或 LibreOffice 開啟該檔案時自動運算呈現。 - Web API 檔案下載整合:在 Express 或 NestJS 等後端框架中,若需要讓使用者下載產生的 Excel,可利用
workbook.xlsx.write(res)直接將檔案內容寫入 HTTP Response 串流,無需在伺服器硬碟儲存暫存檔:
// Express.js 範例
app.get('/download-report', async (req, res) => {
const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet('Report');
worksheet.addRow(['項目', '數值']);
worksheet.addRow(['A', 100]);
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename="report.xlsx"');
await workbook.xlsx.write(res);
res.end();
});
六、結語
ExcelJS 兼具直覺的 API 設計與強大的排版樣式支援,同時在面對巨量資料時提供了穩健的 Stream 機制。在 Node.js 開發中,若您的應用場景需要產出專業排版、套用品牌視覺、撰寫公式,或是安全處理大型試算表匯出,ExcelJS 是相當成熟且值得信賴的選擇。