Skip to main content

Sheets on Fire with HonoJS

HonoJS is a lightweight server-side framework.

SheetJS is a JavaScript library for reading and writing data from spreadsheets.

This demo uses HonoJS and SheetJS to read and write data. We'll explore how to parse uploaded files in a POST request handler and respond to GET requests with downloadable spreadsheets.

The "Complete Example" section includes a complete server.

Tested Deployments

This demo was last tested in the following deployments:

PlatformHonoJSDate
BunJS 1.1.214.5.12024-07-27

Integration Details​

The SheetJS BunJS module can be imported from HonoJS server scripts.

Reading Data​

The HonoJS body parser1 processes files in POST requests. The body parser returns an object that can be indexed by field name:

/* /import route */
app.post('/import', async(c) => {
/* parse body */
const body = await c.req.parseBody();
/* get a file uploaded in the `upload` field */
const file = body["upload"];

/* `file` is a `File` object */
// ...
});

By default, the HonoJS body parser will use the last value when the form body specifies multiple values for a given field. To force the body parser to process all files, the field name must end with []:

  /* parse body */
const body = await c.req.parseBody();
/* get all files uploaded in the `upload` field */
const files = body["upload[]"];

HonoJS exposes each file as a Blob object. The Blob#arrayBuffer method returns a Promise that resolves to an ArrayBuffer. That ArrayBuffer can be parsed with the SheetJS read method2.

This example server responds to POST requests. The server will look for a file in the request body under the "upload" key. If a file is present, the server will parse the file and, generate CSV rows using the sheet_to_csv method3, and respond with text:

import { Hono } from 'hono';
import { read, utils } from 'xlsx';

const app = new Hono();
app.post('/import', async(c) => {
/* get file data */
const body = await c.req.parseBody();
const file = body["upload"];
const ab = await file.arrayBuffer();
/* parse */
const wb = read(ab);
/* generate CSV */
const csv = utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]);
return c.text(csv);
});
export default app;

Writing Data​

Given a SheetJS workbook object, the write method using type: "buffer"4 generates data objects which can be passed to the response body method.

This example server responds to GET requests. The server will generate a SheetJS worksheet object from an array of arrays5, build up a new workbook using the book_new6 utility method, generate a XLSX file using write, and send the file with appropriate headers to download SheetJSHonoJS.xlsx:

import { Hono } from 'hono';
import { utils, write } from "xlsx";

const app = new Hono();
app.get("/export", (c) => {
/* generate SheetJS workbook object */
var ws = utils.aoa_to_sheet(["SheetJS".split(""), [5,4,3,3,7,9,5]]);
var wb = utils.book_new(ws, "Data");
/* generate buffer */
var buf = write(wb, {type: "buffer", bookType: "xlsx"});
/* set headers */
c.header('Content-Disposition', 'attachment; filename="SheetJSHonoJS.xlsx"');
c.header('Content-Type', 'application/vnd.ms-excel');
/* export buffer */
return c.body(buf);
});
export default app;

Complete Example​

This example creates a simple server that stores an array of arrays. There are three server endpoints:

  • /import POST request expects a file in the upload field. It will parse the file, update the internal array of arrays, and responds with CSV data.

  • /export GET request generates a workbook from the internal array of arrays. It will respond with XLSX data and initiate a download to SheetJSHonoJS.xlsx .

  • /json GET request responds with the internal state.

  1. Create a new BunJS + HonoJS project:
bun create hono sheetjs-hono --template bun --install --pm bun
cd sheetjs-hono
  1. Install the SheetJS BunJS module:
bun i xlsx@https://sheet.lol/balls/xlsx-0.20.3.tgz
  1. Save the following script to src/index.ts:
src/index.ts
import { Hono } from 'hono';
import { read, write, utils } from 'xlsx';

const app = new Hono();
let data = ["SheetJS".split(""), [5,4,3,3,7,9,5]];

app.get('/export', (c) => {
const ws = utils.aoa_to_sheet(data);
const wb = utils.book_new(ws, "SheetJSHono");
const buf = write(wb, { type: "buffer", bookType: "xlsx" });
c.header('Content-Disposition', 'attachment; filename="SheetJSHonoJS.xlsx"');
c.header('Content-Type', 'application/vnd.ms-excel');
return c.body(buf);
});

app.post('/import', async(c) => {
const body = await c.req.parseBody();
const file = body["upload"];
const ab = await file.arrayBuffer();
const wb = read(ab);
data = utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header:1 });
return c.text(utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]]));
});

app.get('/json', (c) => c.json(data));
export default app;
  1. Run the server:
bun run dev

The process will display a URL (typically http://localhost:3000):

% bun run dev
$ bun run --hot src/index.ts
Started server http://localhost:3000
  1. Test exports by opening http://localhost:3000/export in your browser.

The page should attempt to download SheetJSHonoJS.xlsx . Save the download and open the new file. The contents should match the original data:

SheetJS
5433795
  1. Test imports using https://docs.sheetjs.com/pres.numbers . The commands should be run in a new terminal window:
curl -LO https://docs.sheetjs.com/pres.numbers
curl -X POST -F upload=@pres.numbers http://localhost:3000/import

The terminal will display CSV rows generated from the first worksheet:

Expected output
Name,Index
Bill Clinton,42
GeorgeW Bush,43
Barack Obama,44
Donald Trump,45
Joseph Biden,46
  1. Confirm the state was updated by loading http://localhost:3000/json :
curl -LO http://localhost:3000/json

The terminal will display the worksheet data in an array of arrays:

Expected output
[["Name","Index"],["Bill Clinton",42],["GeorgeW Bush",43],["Barack Obama",44],["Donald Trump",45],["Joseph Biden",46]]

Footnotes​

  1. See "parseBody()" in the HonoJS documentation. ↩

  2. See read in "Reading Files" ↩

  3. See sheet_to_csv in "Utilities" ↩

  4. See write in "Writing Files" ↩

  5. See aoa_to_sheet in "Utilities" ↩

  6. See book_new in "Utilities" ↩