4

New to SvelteKit and working to adapt an endpoint from a Node/Express server to make it more generic so as to be able to take advantage of SvelteKit adapters. The endpoint downloads files stored in a database via node-postgresql.

My functional endpoint in Node/Express looks like this:

import stream from 'stream'
import db from '../utils/db'

export async function download(req, res) {
  const _id = req.params.id
  const sql = "SELECT _id, name, type, data FROM files WHERE _id = $1;"
  const { rows } = await db.query(sql, [_id])
  const file = rows[0]
  const fileContents = Buffer.from(file.data, 'base64')
  const readStream = new stream.PassThrough()
  readStream.end(fileContents)
  res.set('Content-disposition', `attachment; filename=${file.name}`)
  res.set('Content-Type', file.type)
  readStream.pipe(res)
}

Here's what I have for [filenum].json.ts in SvelteKit so far...

import stream from 'stream'
import db from '$lib/db'

export async function get({ params }): Promise<any> {
  const { filenum } = params
  const { rows } = await db.query('SELECT _id, name, type, data FROM files WHERE _id = $1;', [filenum])
  
  if (rows) {
    const file = rows[0]
    const fileContents = Buffer.from(file.data, 'base64')
    const readStream = new stream.PassThrough()
    readStream.end(fileContents)
    let body
    readStream.pipe(body)

    return {
      headers: {
        'Content-disposition': `attachment; filename=${file.name}`,
        'Content-type': file.type
      },
      body
    }
  }
}

What is the correct way to do this with SvelteKit without creating a dependency on Node? Per SvelteKit's Endpoint docs,

We don't interact with the req/res objects you might be familiar with from Node's http module or frameworks like Express, because they're only available on certain platforms. Instead, SvelteKit translates the returned object into whatever's required by the platform you're deploying your app to.

nstuyvesant
  • 1,392
  • 2
  • 18
  • 43
  • 1
    Why do you want to remove the node dependency ? The endpoints run on the server, which for all adapters (except static) will be in a node environment anyway. – Stephane Vanraes May 21 '21 at 20:22
  • SvelteKit's docs on https://kit.svelte.dev/docs#routing-endpoints say, "We don't interact with the req/res objects you might be familiar with from Node's http module or frameworks like Express, because they're only available on certain platforms. Instead, SvelteKit translates the returned object into whatever's required by the platform you're deploying your app to." – nstuyvesant May 21 '21 at 21:14
  • yes, but that doesn't mean you don't have access to other node functionality, just that sveltekit abstracts away the req/res part. – Stephane Vanraes May 21 '21 at 21:23
  • As res is not available in the get method, wondering how to call readStream.pipe(res). – nstuyvesant May 22 '21 at 02:22
  • just pipe it in another temporary variable ? – Stephane Vanraes May 22 '21 at 10:56
  • Just adjust the sample above. Getting "TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined" – nstuyvesant May 22 '21 at 18:13

1 Answers1

2

UPDATE: The bug was fixed in SvelteKit. This is the updated code that works:

// src/routes/api/file/_file.controller.ts
import { query } from '../_db'

type GetFileResponse = (fileNumber: string) => Promise<{
  headers: {
      'Content-Disposition': string
      'Content-Type': string
  }
  body: Uint8Array
  status?: number
} | {
  status: number
  headers?: undefined
  body?: undefined
}>

export const getFile: GetFileResponse = async (fileNumber: string) => {
  const { rows } = await query(`SELECT _id, name, type, data FROM files WHERE _id = $1;`, [fileNumber])
  if (rows) {
    const file = rows[0]
    return {
      headers: {
        'Content-Disposition': `attachment; filename="${file.name}"`,
        'Content-Type': file.type
      },
      body: new Uint8Array(file.data)
    }
  } else return {
    status: 404
  }
}

and

// src/routes/api/file/[filenum].ts
import type { RequestHandler } from '@sveltejs/kit'
import { getFile } from './_file.controller'

export const get: RequestHandler = async ({ params }) => {
  const { filenum } = params
  const fileResponse = await getFile(filenum)
  return fileResponse
}
nstuyvesant
  • 1,392
  • 2
  • 18
  • 43