Iteration Layer Sheet Generation vs ExcelJS: Node.js Library or One API Call?

7 min read Sheet Generation

ExcelJS Is the Good One

If SheetJS is the spreadsheet library you find first, ExcelJS is the one you switch to when you realize SheetJS’s free tier doesn’t support formatting. ExcelJS is open source, MIT licensed, and it actually includes cell styles without a paywall. Bold headers, background colors, number formats, merged cells, formulas — all free.

It also supports streaming for large files, which is genuinely useful when you’re generating spreadsheets with tens of thousands of rows and don’t want to hold the entire workbook in memory.

So what’s the catch? ExcelJS is a Node.js library. If your backend isn’t Node.js, it’s not an option. And even within Node.js, the API is verbose — styling a workbook means constructing objects for every cell, managing column definitions separately from data, and handling the output stream yourself. It works, but it’s more code than you’d expect for what amounts to “put this data in a spreadsheet.”

The Code You Write

Here’s a formatted spreadsheet in ExcelJS — styled headers, currency formatting, column widths:

import ExcelJS from "exceljs";

const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Q1 Revenue");

worksheet.columns = [
  { header: "Company", key: "company", width: 20 },
  { header: "Region", key: "region", width: 20 },
  { header: "Revenue", key: "revenue", width: 15 },
];

worksheet.addRow({
  company: "Acme Corp",
  region: "North America",
  revenue: 48500.00,
});
worksheet.addRow({
  company: "Globex Ltd",
  region: "Europe",
  revenue: 37200.00,
});

// Style the header row
const headerRow = worksheet.getRow(1);
headerRow.font = { bold: true, color: { argb: "FFFFFFFF" }, size: 12 };
headerRow.fill = {
  type: "pattern",
  pattern: "solid",
  fgColor: { argb: "FF1A1A2E" },
};
headerRow.alignment = { horizontal: "center" };
headerRow.commit();

// Format currency cells
worksheet.getColumn("revenue").numFmt = "$#,##0.00";

const buffer = await workbook.xlsx.writeBuffer();

That’s about 30 lines for a 2-row spreadsheet. The column definitions are separate from the data. The header styling happens after the rows are added — you get the row, set properties, then call commit(). Currency formatting is applied to the entire column using a raw Excel format string.

It’s better than SheetJS. But it’s still a lot of ceremony for what you actually want to express: “here are my columns, here are my rows, make the headers bold, format this column as currency.”

What ExcelJS Doesn’t Do

ExcelJS is focused on XLSX. That focus comes with trade-offs.

  • CSV is an afterthought. ExcelJS can write CSV, but it strips all formatting. You get plain values separated by commas. No style information carries over, and there’s no formula evaluation — formulas are written as literal strings.
  • No Markdown output. If you need a Markdown table for documentation, a Slack message, or a README, that’s a separate implementation.
  • No formula evaluation. ExcelJS writes formulas into cells, and Excel evaluates them when the file opens. If you need the computed values for a non-XLSX format, you calculate them yourself.
  • Streaming adds complexity. The streaming API (ExcelJS.stream.xlsx.WorkbookWriter) is useful for large files, but it changes the programming model. You can’t go back and modify cells after writing them. Header styles need to be applied before any data rows. It’s a different way of thinking about workbook construction, and mixing it with the standard API is error-prone.
  • Node.js only. Your Python backend, your Go microservice, your Elixir application — none of them can use ExcelJS without a Node.js sidecar.

A Direct Comparison

Same spreadsheet, both approaches.

ExcelJS (Node.js only, you host this):

import ExcelJS from "exceljs";

const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Q1 Revenue");

worksheet.columns = [
  { header: "Company", key: "company", width: 20 },
  { header: "Region", key: "region", width: 20 },
  { header: "Revenue", key: "revenue", width: 15 },
];

worksheet.addRow({
  company: "Acme Corp",
  region: "North America",
  revenue: 48500.00,
});
worksheet.addRow({
  company: "Globex Ltd",
  region: "Europe",
  revenue: 37200.00,
});

const headerRow = worksheet.getRow(1);
headerRow.font = { bold: true, color: { argb: "FFFFFFFF" }, size: 12 };
headerRow.fill = {
  type: "pattern",
  pattern: "solid",
  fgColor: { argb: "FF1A1A2E" },
};
headerRow.alignment = { horizontal: "center" };
headerRow.commit();

worksheet.getColumn("revenue").numFmt = "$#,##0.00";

const buffer = await workbook.xlsx.writeBuffer();

Iteration Layer (any language, multiple formats):

Request
curl -X POST https://api.iterationlayer.com/sheet-generation/v1/generate \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "format": "xlsx",
    "styles": {
      "header": {
        "is_bold": true,
        "font_size_in_pt": 12,
        "background_color": "#1a1a2e",
        "font_color": "#ffffff",
        "horizontal_alignment": "center"
      }
    },
    "sheets": [
      {
        "name": "Q1 Revenue",
        "columns": [
          {
            "name": "Company",
            "width": 20
          },
          {
            "name": "Region",
            "width": 20
          },
          {
            "name": "Revenue",
            "width": 15
          }
        ],
        "rows": [
          [
            {
              "value": "Acme Corp"
            },
            {
              "value": "North America"
            },
            {
              "value": 48500.00,
              "format": "currency",
              "currency_code": "USD"
            }
          ],
          [
            {
              "value": "Globex Ltd"
            },
            {
              "value": "Europe"
            },
            {
              "value": 37200.00,
              "format": "currency",
              "currency_code": "EUR"
            }
          ]
        ]
      }
    ]
  }'
Response
{
  "success": true,
  "data": {
    "buffer": "UEsDBBQAAAAIAA...",
    "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  }
}
Request
import { IterationLayer } from "iterationlayer";
const client = new IterationLayer({
  apiKey: "YOUR_API_KEY",
});

const result = await client.generateSheet({
  format: "xlsx",
  styles: {
    header: {
      is_bold: true,
      font_size_in_pt: 12,
      background_color: "#1a1a2e",
      font_color: "#ffffff",
      horizontal_alignment: "center",
    },
  },
  sheets: [
    {
      name: "Q1 Revenue",
      columns: [
        {
          name: "Company",
          width: 20,
        },
        {
          name: "Region",
          width: 20,
        },
        {
          name: "Revenue",
          width: 15,
        },
      ],
      rows: [
        [
          {
            value: "Acme Corp",
          },
          {
            value: "North America",
          },
          {
            value: 48500.00,
            format: "currency",
            currency_code: "USD",
          },
        ],
        [
          {
            value: "Globex Ltd",
          },
          {
            value: "Europe",
          },
          {
            value: 37200.00,
            format: "currency",
            currency_code: "EUR",
          },
        ],
      ],
    },
  ],
});
Response
{
  "success": true,
  "data": {
    "buffer": "UEsDBBQAAAAIAA...",
    "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  }
}
Request
from iterationlayer import IterationLayer
client = IterationLayer(api_key="YOUR_API_KEY")

result = client.generate_sheet(
    format="xlsx",
    styles={
        "header": {
            "is_bold": True,
            "font_size_in_pt": 12,
            "background_color": "#1a1a2e",
            "font_color": "#ffffff",
            "horizontal_alignment": "center",
        },
    },
    sheets=[
        {
            "name": "Q1 Revenue",
            "columns": [
                {
                    "name": "Company",
                    "width": 20,
                },
                {
                    "name": "Region",
                    "width": 20,
                },
                {
                    "name": "Revenue",
                    "width": 15,
                },
            ],
            "rows": [
                [
                    {
                        "value": "Acme Corp",
                    },
                    {
                        "value": "North America",
                    },
                    {
                        "value": 48500.00,
                        "format": "currency",
                        "currency_code": "USD",
                    },
                ],
                [
                    {
                        "value": "Globex Ltd",
                    },
                    {
                        "value": "Europe",
                    },
                    {
                        "value": 37200.00,
                        "format": "currency",
                        "currency_code": "EUR",
                    },
                ],
            ],
        },
    ],
)
Response
{
  "success": true,
  "data": {
    "buffer": "UEsDBBQAAAAIAA...",
    "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  }
}
Request
package main

import il "github.com/iterationlayer/sdk-go"

func main() {
    client := il.NewClient("YOUR_API_KEY")

    result, err := client.GenerateSheet(il.GenerateSheetRequest{
        Format: "xlsx",
        Styles: &il.SheetStyles{
            Header: &il.CellStyle{
                IsBold:              true,
                FontSizeInPt:        12,
                BackgroundColor:     "#1a1a2e",
                FontColor:           "#ffffff",
                HorizontalAlignment: "center",
            },
        },
        Sheets: []il.Sheet{
            {
                Name: "Q1 Revenue",
                Columns: []il.SheetColumn{
                    {
                        Name:  "Company",
                        Width: 20,
                    },
                    {
                        Name:  "Region",
                        Width: 20,
                    },
                    {
                        Name:  "Revenue",
                        Width: 15,
                    },
                },
                Rows: []il.SheetRow{
                    {
                        {
                            Value: "Acme Corp",
                        },
                        {
                            Value: "North America",
                        },
                        {
                            Value:        48500.00,
                            Format:       "currency",
                            CurrencyCode: "USD",
                        },
                    },
                    {
                        {
                            Value: "Globex Ltd",
                        },
                        {
                            Value: "Europe",
                        },
                        {
                            Value:        37200.00,
                            Format:       "currency",
                            CurrencyCode: "EUR",
                        },
                    },
                },
            },
        },
    })
    if err != nil {
        panic(err)
    }
    _ = result
}
Response
{
  "success": true,
  "data": {
    "buffer": "UEsDBBQAAAAIAA...",
    "mime_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
  }
}

The ExcelJS version defines columns, adds rows, then goes back to style the header row and format the revenue column. Three separate steps for what’s conceptually one thing: “here’s my data, here’s how it should look.” The API version describes it all in one declaration — columns, rows, styles — and the server builds the file.

Change "format": "xlsx" to "format": "csv" and the same payload produces CSV with formulas evaluated to computed values. Change it to "format": "markdown" and you get a Markdown table. Same input, three outputs.

When ExcelJS Is the Right Choice

ExcelJS is a solid library. It’s the best open-source option for XLSX generation in Node.js, and it wins in specific scenarios.

  • You need streaming for very large files. ExcelJS’s streaming writer can generate spreadsheets with hundreds of thousands of rows without holding the entire workbook in memory. If you’re exporting database tables with 500,000+ rows, streaming matters.
  • You need to read and modify existing spreadsheets. ExcelJS can open an XLSX file, change specific cells, add rows, and save it. The Sheet Generation API generates new files; it doesn’t edit existing ones.
  • You need charts or advanced Excel features. ExcelJS supports charts, images, data validation, and conditional formatting. If your spreadsheet needs an embedded bar chart, ExcelJS can do that.
  • Offline or air-gapped environments. If your spreadsheets can’t leave your network, a cloud API isn’t an option.
  • Your team is already invested in ExcelJS. If your Node.js codebase has stable ExcelJS code that works well, migrating for the sake of it isn’t worth the effort.

When an API Makes More Sense

For most teams, especially those not running Node.js, the trade-off favors an API.

  • Your backend isn’t Node.js. Python, Go, Elixir, Ruby, Java — the API works with any language that can make HTTP requests. No sidecar service.
  • You need multiple output formats. XLSX, CSV, and Markdown from the same JSON payload. One definition, three formats. No separate code for each.
  • You want declarative over imperative. The API describes what you want. ExcelJS describes how to build it. “Make the header bold” vs “get row 1, create a Font object, set bold to true, assign it, commit.” Declarative code is shorter and harder to get wrong.
  • You don’t want to host a runtime for spreadsheet generation. The API runs wherever your application runs, because it’s an HTTP call. No Node.js process to deploy, no node_modules to manage, no memory to tune.
  • You need formulas that evaluate in non-XLSX formats. The API evaluates =SUM(B2:B4) server-side for CSV and Markdown. ExcelJS writes formula strings that only Excel can evaluate.

The Hosting Question

ExcelJS is free. No license fee, no per-request cost. But the code runs somewhere — your server, your container, your Lambda function — and that somewhere has a cost.

A typical spreadsheet generation endpoint in Node.js means: a running Node.js process, memory allocation for workbook construction (each workbook lives in memory until it’s serialized), error handling for malformed data, timeout handling for large files, and output routing to storage or HTTP response. It’s straightforward work, but it’s work you maintain.

With an API call, the hosting, memory management, error handling, and scaling are someone else’s concern. You send JSON, you get a file. Whether that trade-off makes sense depends on your volume, your team, and how much you value not maintaining spreadsheet generation infrastructure.

Get Started

Check the Sheet Generation docs for the full API reference — cell formats, style properties, formula support, cell merging, and multi-sheet workbooks. The TypeScript and Python SDKs handle authentication and response parsing.

Iteration Layer runs on EU infrastructure (Frankfurt), which matters if your data residency requirements rule out US-hosted services.

Start building in minutes

Free trial included. No credit card required.