--- title: Sheets on Fire with HonoJS sidebar_label: HonoJS pagination_prev: demos/net/network/index pagination_next: demos/net/email/index --- import current from '/version.js'; import CodeBlock from '@theme/CodeBlock'; [HonoJS](https://hono.dev/) is a lightweight server-side framework. [SheetJS](https://sheetjs.com) 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"](#complete-example) section includes a complete server. :::note Tested Deployments 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](/docs/getting-started/installation/bun) can be imported from HonoJS server scripts. ### Reading Data The HonoJS body parser[^1] processes files in POST requests. The body parser returns an object that can be indexed by field name: ```js /* /import route */ app.post('/import', async(c) => { /* parse body */ const body = await c.req.parseBody(); /* get a file uploaded in the `upload` field */ // highlight-next-line const file = body["upload"]; /* `file` is a `File` object */ // ... }); ``` :::caution pass 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 `[]`: ```js /* parse body */ const body = await c.req.parseBody(); /* get all files uploaded in the `upload` field */ // highlight-next-line 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` method[^2]. 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` method[^3], and respond with text: ```js 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 arrays[^5], 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`: ```js 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: ```bash bun create hono sheetjs-hono --template bun --install --pm bun cd sheetjs-hono ``` 2) Install the [SheetJS BunJS module](/docs/getting-started/installation/bun): {`\ bun i xlsx@https://sheet.lol/balls/xlsx-${current}.tgz`} 3) Save the following script to `src/index.ts`: ```ts title="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; ``` 4) Run the server: ```bash bun run dev ``` The process will display a URL (typically `http://localhost:3000`): ```text % bun run dev $ bun run --hot src/index.ts // highlight-next-line Started server http://localhost:3000 ``` 5) 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
6) Test imports using https://docs.sheetjs.com/pres.numbers . The commands should be run in a new terminal window: ```bash 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: ```text title="Expected output" Name,Index Bill Clinton,42 GeorgeW Bush,43 Barack Obama,44 Donald Trump,45 Joseph Biden,46 ``` 7) Confirm the state was updated by loading `http://localhost:3000/json` : ```bash curl -LO http://localhost:3000/json ``` The terminal will display the worksheet data in an array of arrays: ```json title="Expected output" [["Name","Index"],["Bill Clinton",42],["GeorgeW Bush",43],["Barack Obama",44],["Donald Trump",45],["Joseph Biden",46]] ``` [^1]: See ["parseBody()"](https://hono.dev/docs/api/request#parsebody) in the HonoJS documentation. [^2]: See [`read` in "Reading Files"](/docs/api/parse-options) [^3]: See [`sheet_to_csv` in "Utilities"](/docs/api/utilities/csv#delimiter-separated-output) [^4]: See [`write` in "Writing Files"](/docs/api/write-options) [^5]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) [^6]: See [`book_new` in "Utilities"](/docs/api/utilities/wb)