| 
									
										
										
										
											2023-10-16 09:12:56 +00:00
										 |  |  | --- | 
					
						
							|  |  |  | title: Sheets in FastifyJS | 
					
						
							|  |  |  | sidebar_label: FastifyJS | 
					
						
							|  |  |  | pagination_prev: demos/net/network | 
					
						
							| 
									
										
										
										
											2023-10-23 01:20:18 +00:00
										 |  |  | pagination_next: demos/net/email/index | 
					
						
							| 
									
										
										
										
											2023-10-16 09:12:56 +00:00
										 |  |  | --- | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import current from '/version.js'; | 
					
						
							|  |  |  | import CodeBlock from '@theme/CodeBlock'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [FastifyJS](https://openjsf.org/projects/) is a NodeJS web framework. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing | 
					
						
							|  |  |  | data from spreadsheets. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This demo uses FastifyJS 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 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | This demo was verified on 2023 October 16 using `fastify@4.24.2` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Integration Details
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The [SheetJS NodeJS module](/docs/getting-started/installation/nodejs) can be | 
					
						
							|  |  |  | imported from scripts that use FastifyJS. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### Exporting Data to Workbooks (GET)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The SheetJS `write` method[^1] with the option `type: "buffer"` generates NodeJS | 
					
						
							|  |  |  | Buffer objects containing the raw file data. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | FastifyJS can directly handle `Buffer` data in `Response#end` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The exported filename can be specified using the `Content-Disposition` header. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The following demo FastifyJS server will respond to GET requests to `/download` | 
					
						
							|  |  |  | with a XLSX spreadsheet. In this example, the SheetJS `aoa_to_sheet` method[^2] | 
					
						
							|  |  |  | generates a sheet object and the `book_new` and `book_append_sheet` helpers[^3] | 
					
						
							|  |  |  | build the workbook object. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | /* GET / returns a workbook */ | 
					
						
							|  |  |  | fastify.get('/', (req, reply) => { | 
					
						
							|  |  |  |   /* make a workbook */ | 
					
						
							|  |  |  |   var wb = XLSX.read("S,h,e,e,t,J,S\n5,4,3,3,7,9,5", {type: "binary"}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* write to Buffer */ | 
					
						
							|  |  |  |   const buf = XLSX.write(wb, {type:"buffer", bookType: "xlsx"}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* set Content-Disposition header and send data */ | 
					
						
							|  |  |  |   // highlight-next-line | 
					
						
							|  |  |  |   reply.header('Content-Disposition', 'attachment; filename="SheetJSFastify.xlsx"').send(buf); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ### Parsing Uploaded Files (POST)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | `@fastify/multipart`, which uses `busbuy` under the hood, must be registered: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | /* load SheetJS Library */ | 
					
						
							|  |  |  | const XLSX = require("xlsx"); | 
					
						
							|  |  |  | /* load fastify and enable body parsing */ | 
					
						
							|  |  |  | const fastify = require('fastify')({logger: true}); | 
					
						
							|  |  |  | // highlight-next-line | 
					
						
							|  |  |  | fastify.register(require('@fastify/multipart'), { attachFieldsToBody: true }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Once registered with the option `attachFieldsToBody`, route handlers can use | 
					
						
							|  |  |  | `req.body` directly. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Each file object in the body has a `toBuffer` method that resolves to a Buffer | 
					
						
							|  |  |  | object. The SheetJS `read` method[^4] can read the Buffer and generate a | 
					
						
							|  |  |  | workbook object[^5]. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The following demo FastifyJS server will respond to POST requests to `/upload`. | 
					
						
							|  |  |  | Assuming the `upload` field of the form data is the file, the SheetJS `read` | 
					
						
							|  |  |  | method will parse the file. CSV rows are generated from the first worksheet | 
					
						
							|  |  |  | using the SheetJS `sheet_to_csv` method[^6]. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | /* POST / reads submitted file and exports to requested format */ | 
					
						
							|  |  |  | fastify.post('/', async(req, reply) => { | 
					
						
							|  |  |  |   /* "file" is the name of the field in the HTML form*/ | 
					
						
							|  |  |  |   const file = req.body.upload; | 
					
						
							|  |  |  |   /* toBuffer returns a promise that resolves to a Buffer */ | 
					
						
							|  |  |  |   // highlight-next-line | 
					
						
							|  |  |  |   const buf = await file.toBuffer(); | 
					
						
							|  |  |  |   /* `XLSX.read` can read the Buffer */ | 
					
						
							|  |  |  |   const wb = XLSX.read(buf); | 
					
						
							|  |  |  |   /* reply with a CSV */ | 
					
						
							|  |  |  |   reply.send(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | :::caution pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Out of the box, Fastify will return an error `FST_ERR_CTP_BODY_TOO_LARGE` when | 
					
						
							|  |  |  | processing large spreadsheets (`statusCode 413`).  This is a Fastify issue. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The default body size limit (including all uploaded files and fields) is 1 MB. | 
					
						
							|  |  |  | It can be increased by setting the `bodyLimit` option during server creation: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js | 
					
						
							|  |  |  | /* increase request body size limit to 5MB = 5 * 1024 * 1024 bytes */ | 
					
						
							|  |  |  | const fastify = require('fastify')({bodyLimit: 5 * 1024 * 1024}); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ::: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ## Complete Example
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 0) Save the following snippet to `SheetJSFastify.js`: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```js title="SheetJSFastify.js" | 
					
						
							|  |  |  | /* load SheetJS Library */ | 
					
						
							|  |  |  | const XLSX = require("xlsx"); | 
					
						
							|  |  |  | /* load fastify and enable body parsing */ | 
					
						
							|  |  |  | const fastify = require('fastify')({logger: true}); | 
					
						
							|  |  |  | fastify.register(require('@fastify/multipart'), { attachFieldsToBody: true }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* GET / returns a workbook */ | 
					
						
							|  |  |  | fastify.get('/', (req, reply) => { | 
					
						
							|  |  |  |   /* make a workbook */ | 
					
						
							|  |  |  |   var wb = XLSX.read("S,h,e,e,t,J,S\n5,4,3,3,7,9,5", {type: "binary"}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* write to Buffer */ | 
					
						
							|  |  |  |   const buf = XLSX.write(wb, {type:"buffer", bookType: "xlsx"}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* set Content-Disposition header and send data */ | 
					
						
							|  |  |  |   reply.header('Content-Disposition', 'attachment; filename="SheetJSFastify.xlsx"').send(buf); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* POST / reads submitted file and exports to requested format */ | 
					
						
							|  |  |  | fastify.post('/', async(req, reply) => { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* "file" is the name of the field in the HTML form*/ | 
					
						
							|  |  |  |   const file = req.body.upload; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* toBuffer returns a promise that resolves to a Buffer */ | 
					
						
							|  |  |  |   const wb = XLSX.read(await file.toBuffer()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /* send back a CSV */ | 
					
						
							|  |  |  |   reply.send(XLSX.utils.sheet_to_csv(wb.Sheets[wb.SheetNames[0]])); | 
					
						
							|  |  |  | }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* start */ | 
					
						
							|  |  |  | fastify.listen({port: process.env.PORT || 3000}, (err, addr) => { if(err) throw err; }); | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 1) Install dependencies: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <CodeBlock language="bash">{`\ | 
					
						
							|  |  |  | npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz fastify@4.24.2 @fastify/multipart@8.0.0`} | 
					
						
							|  |  |  | </CodeBlock> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 2) Start server | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | node SheetJSFastify.js | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 3) Test POST requests using <https://sheetjs.com/pres.numbers>: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```bash | 
					
						
							|  |  |  | curl -LO https://sheetjs.com/pres.numbers | 
					
						
							|  |  |  | curl -X POST -F upload=@pres.numbers http://localhost:3000/ | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The response should show the data in CSV rows. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 4) Test GET requests by opening `http://localhost:3000/` in your browser. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | It should prompt to download `SheetJSFastify.xlsx` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | [^1]: See [`write` in "Writing Files"](/docs/api/write-options) | 
					
						
							|  |  |  | [^2]: See [`aoa_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-arrays-input) | 
					
						
							|  |  |  | [^3]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`. | 
					
						
							|  |  |  | [^4]: See [`readFile` in "Reading Files"](/docs/api/parse-options) | 
					
						
							|  |  |  | [^5]: See ["Workbook Object"](/docs/csf/book) | 
					
						
							|  |  |  | [^6]: See [`sheet_to_csv` in "CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output) |