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.
This demo was last tested in the following deployments:
Platform | HonoJS | Date |
---|---|---|
BunJS 1.1.21 | 4.5.1 | 2024-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_new
6 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 theupload
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 toSheetJSHonoJS.xlsx
. -
/json
GET request responds with the internal state.
- Create a new BunJS + HonoJS project:
bun create hono sheetjs-hono --template bun --install --pm bun
cd sheetjs-hono
- Install the SheetJS BunJS module:
bun i xlsx@https://sheet.lol/balls/xlsx-0.20.3.tgz
- Save the following script to
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;
- 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
- 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:
S | h | e | e | t | J | S |
5 | 4 | 3 | 3 | 7 | 9 | 5 |
- 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:
Name,Index
Bill Clinton,42
GeorgeW Bush,43
Barack Obama,44
Donald Trump,45
Joseph Biden,46
- 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:
[["Name","Index"],["Bill Clinton",42],["GeorgeW Bush",43],["Barack Obama",44],["Donald Trump",45],["Joseph Biden",46]]
Footnotes​
-
See "parseBody()" in the HonoJS documentation. ↩