Skip to content

Instantly share code, notes, and snippets.

@yano3nora
Last active March 23, 2025 11:44
Show Gist options
  • Save yano3nora/753cb2724c00a9e236a4d7c439673cd2 to your computer and use it in GitHub Desktop.
Save yano3nora/753cb2724c00a9e236a4d7c439673cd2 to your computer and use it in GitHub Desktop.
csv-writer

Overview

https://github.com/ryu1kn/csv-writer

  • node 環境で csv write したいときのやつ
  • nextjs (express) の response と組み合わせて streaming 返却で利用した
$ npm i csv-writer

Examples

API

import type { NextApiRequest, NextApiResponse } from 'next'
import { prisma } from 'libs/prisma'
import { createObjectCsvStringifier } from 'csv-writer'

export default async function Csv (
  req: NextApiRequest,
  res: NextApiResponse,
) {
  try {
    const filename = 'FILENAME'
    const csvStringifier = createObjectCsvStringifier({
      header: [
        { title: 'ID', id: 'id' },
        { title: '名前', id: 'name' },
      ]
    })

    res.setHeader('Content-Type', 'text/csv')
    res.setHeader('Content-Disposition', `attachment; filename="${encodeURI(filename)}.csv"`)
    res.write(new Uint8Array([0xEF, 0xBB, 0xBF])) // UTF8 BOM for Windows
    res.write(csvStringifier.getHeaderString())

    let more = true
    let skip = 0
    const take = 1000

    while (more) {
      const records = await prisma.user.findMany({
        take,
        skip,
        where: { /* */ },
        orderBy: [{ /* */ }],
      })

      if (records.length === 0) {
        more = false
        break
      }

      // streaming
      res.write(csvStringifier.stringifyRecords(records.map(user => ({
        id: user.id,
        name: user.name,
      }))))
      skip += take
    }

    return res.end()
  } catch (e: any) {
    return res.status(500).send(e?.message)
  }
}

Front

import { useMutation } from '@tanstack/react-query'

export const useDownload = () => {
  return useMutation({
    mutationKey: ['download'],
    mutationFn: async ({ start, end }: {
      start: string
      end: string
    }) => {
      const res = await fetch('/api/csv', {
        method: 'POST',
        credentials: 'include',
        headers: { 'Content-Type': 'application/json' }
        body: JSON.stringify({ start, end }),
      })

      if (!res.ok) {
        throw new Error(await res.text() || res.statusText)
      }


      const disposition = res.headers.get('Content-Disposition')
      let filename = 'data.csv' // default

      if (disposition && disposition.indexOf('attachment') !== -1) {
        const filenameRegex = /filename="(.+)"/
        const matches = filenameRegex.exec(disposition)
        if (matches && matches[1]) {
          filename = matches[1]
        }
      }

      const blob = await res.blob()
      const url = window.URL.createObjectURL(blob)
      const dynamicA = document.createElement('a')
      dynamicA.href = url
      dynamicA.download = decodeURI(filename)
      dynamicA.click()
      dynamicA.remove()
      window.URL.revokeObjectURL(url)

      return url
    },
  })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment