How to Return File from SvelteKit Endpoint

Viewed 1340

I am trying to serve a PDF file that my SvelteKit app generates and allow a user to download it from an endpoint.

My project structure looks like this:

---------------------
/src/routes/downloads
---------------------
[file].ts
ABC.pdf
XYZ.pdf

My [file].ts endpoint looks like this:

import fs from 'fs'

// ----- GET -----
export async function get({ params }){
  //console.log(params.file) -> ABC

  var pdf = fs.readFileSync('./src/routes/downloads/'+params.file+'.pdf')

  return{
    status:200,
    headers: {
      "Content-type" : "application/pdf",
      "Content-Disposition": "attachment; filename="+params.file+".pdf"
    },
    body: pdf
  }
}

So then when I hit http://localhost:3000/downloads/ABC, the PDF file named ABC.pdf downloads.

But my readFileSync path isn't something that's going to work on production. As far as I know, there is no /src/routes folder.

How do I serve my file from a http://localhost:3000 url? Everything I've tried yields a 404 and it can't find the file. I'm also open to a different way of handling this scenario. This is just my best guess of how to do this in SvelteKit.

3 Answers

The recommended way to do this, for adapter-node, is to place your application data in a new folder under your project's root directory (ie. alongside /src and /static). You can then read files with a relative path: fs.readFile('./my-app-data/foo.txt').

For deployment, you just have to make sure to execute node build from the project root, as this guarantees that you have the same working directory during both development and production.

The static folder works, but it is not meant to carry application data—files in this folder represent new routes that are served directly to users, so this is not desirable if your generated files must be protected in any way. Even if they're meant to be public files, it still blurs what is supposed to be production and source data: should a new deploy overwrite all the files in static? If you're not careful, a naming clash could mean overwriting production data.

As @Stephane suggest, put your files under statics folder. This way you can serve directly through a reverse proxy, like Nginx

You can use import.meta.glob for this.

export async function get({ params }){
  const file = `./${params.file}.pdf`;
  const pdfs = import.meta.glob(('./*.pdf', { as: 'raw' });
  const pdf = pdfs[file];

  return {
    status:200,
    headers: {
      "Content-type" : "application/pdf",
      "Content-Disposition": "attachment; filename="+params.file+".pdf"
    },
    body: pdf
  }
}

The import.meta.glob in combination with the as: 'raw' option will effectively embed the contents of each file in your resulting code. (this is purely server side so no worries about shipping to much to the client)

Note that this of course means that only files present during build can be served this way.

Related