Sapper/Svelte: how to fetch a local json file to retrieve data
Asked Answered
A

4

10

In my sapper app, I have some data stored in a json file at src/data/videoslist.json, an array of objects in json format.

I need to retrive the data in my index page to pass it to the component. Here's my code at the top of src/routes/index.svelte

<script context="module">
    export async function preload() {
        const response = await this.fetch('../data/videoslist.json');
        const responseJson = await response.json();
        return {
            videos: responseJson
        }
    }
</script>

I get an error 500

invalid json response body at http://127.0.0.1:3000/data/videoslist.json reason: Unexpected token < in JSON at position 0

The file is pure json, nothing else. The unexpected token < makes me think a 404 is returned instead of the file.

Do you know what I get wrong ? I tried so many paths variations wit ../ ./ or moving the file to the route folder, but nothing works.

PS: I'm still a newbie with js and framework stuff, I may have missed something very basic for someone who knows better :) (like you can't retrieve a local file with fetch).

Afterburner answered 17/5, 2020 at 16:3 Comment(5)
Why do you use fetch if it is a local json file? Wouldn't it be simpler to make a javascript file, export the json and import it the "normal" way?Dubrovnik
Yes, I thought of that, but the content of the json file is generated by fs.writeFileSync, and it overwrites the whole content everytime fresh data is collectedAfterburner
Ok and it is impossible to hardcode something like this in front of it: module.exports = { jsonFile: // the json file with the fs function?Dubrovnik
It uses fetch because it is only a local file on the server, once the code is bundled and send to the client it is no longer 'local'. If you use this approach you will have to re-bundle your app everytime you make a change to the json file.Elastance
Yes, I confirm what Stephane says. I initially tried to read the json file with fs in the <script> block, but it creates issues like preventing import from other libraries. I was confirmed by a sapper contributor that fs should not be used on the client side, only on the node side.Afterburner
M
5

Write the JSON file into the static folder, as static/data/videoslist.json.

Magnify answered 18/5, 2020 at 15:4 Comment(1)
Thanks Rich, I'll give it a try and let you knowAfterburner
D
3

I've noticed that with the following:

<script context="module">
    export async function preload({ params: { id } }) {
       return await (await this.fetch(`/route/${id}/details.json`)).json();
    });
</script>

...the SSR code will attempt to load https://127.0.0.1/route/id/details.json - which fails on my hosting. If I specify a fully-qualified absolute URL (externally accessible), e.g.

this.fetch(`https://hostname.tld/route/${id}/details.json`)

..it works fine. I have a details.json.js server route set up, which is working as expected when the URL is accessed directly via https://hostname.tld/route/id/details.json

Sapper evidently runs the same code on the client after the server 500, so the page still works, but it's somewhat confusing.

I had a similar issue with relative paths in preload scripts due to the hosting provider blocking with lack of referrer (resulting in a 403), which can be mitigated with:

this.fetch(`/route/${id}/details.json`, { referrerPolicy: "origin" })

...however, this doesn't seem to help in this instance.

I can obviously just change the URL to be absolute, but I'd rather not tie this to the host, screw up local dev, and evidently force the SSR code to leave the local network. I guess I'm unclear how server-side fetch works to localhost, so may not be a sapper-specific issue.

N.B. I can utilise "host" from the preload page argument to construct an absolute URL thus (must be http not https):

<script context="module"> 
   export async function preload({ host, params: { id } }) { 
      return await (await this.fetch(`http://{host}/route/${id}/details.json`)).json(); 
   });
</script>

...and it works on the hosting - but fear it isn't intuitive, and not as the documentation suggests.


Derive answered 24/5, 2020 at 13:55 Comment(0)
E
2

In Sapper, when you try to fetch a json file that it actually looks for a route/file with the name videoslist.json.js you have to make that file, and have it return the json. You can find an example of that in the docs here: https://sapper.svelte.dev/docs#Server_routes

Elastance answered 17/5, 2020 at 16:34 Comment(6)
the content of the json file is generated by fs.writeFileSync, it gets entirely overwritten every time new content is written, and I didn't find a way to keep lines of javascript to return the json.Afterburner
you would create a file videoslist.json.js and in that file you write a function that returns the content of videoslist.jsonElastance
So simple and brilliant ! I'll give it a try and let you know. ThanksAfterburner
Hi stephane, I tried your suggestion but i'm not getting it right and I can't figure out how to code properly this solution. Server routes are still very unclear to me as I'm a beginner. I should learn about general node concepts first before trying to code. Thanks for the suggestion, I'll get back to it when I understand better about routing.Afterburner
@StephaneVanraes fs doesn't work when using relative path like, for example: ./_videolist.json, cause Sapper seems not to compile that files and then it won't find it among compiled stuff (it seeks inside __sapper__ folder). The only way to make it work is to use an absolute path, but it's not a very portable solution for deploying etc. Just wondering what should be the best practice to manage data (server side) without using a DB.Mannerless
You should use node's path library and __dirname to get the current path, that is a lot more portableElastance
A
2

Rich's solution works very well.

I just moved the json file into the static folder and the following code works now.

<script context="module">
    export async function preload() {
        const response = await this.fetch('videoslist.json');
        const responseJson = await response.json();
        return {
            videos: responseJson
        }
    }
</script>

This solutions gives direct acces to the file without dealing with server routing and paths.

Afterburner answered 19/5, 2020 at 10:1 Comment(3)
This way the data are publicly accessible and not filtered by the server.Mannerless
Given that the question was asking how to get the data in videoslist.json into the client, that obviously isn't a concern.Magnify
@RichHarris would be very interesting a safer solution, if any.Mannerless

© 2022 - 2024 — McMap. All rights reserved.