W
Wasp-langβ€’2y ago
Gwaggli

Return static files from the server

Is there a possibility to return files from the server app using res.sendFile()? If so, how would you include the assets folder in the output directory and reference it from the code? The functionality i want to achieve is to return an image based on some db calculated data. Its a small set of images, so a whole external image storage seems like overkill as its supposed to be around 6 images.
18 Replies
Vinny (@Wasp)
Vinny (@Wasp)β€’2y ago
import { fileURLToPath } from 'url';

// @ts-ignore
const __filename = fileURLToPath(import.meta.url);

const __dirname = path.dirname(__filename);
console.log('directory-name πŸ‘‰οΈ', __dirname);
import { fileURLToPath } from 'url';

// @ts-ignore
const __filename = fileURLToPath(import.meta.url);

const __dirname = path.dirname(__filename);
console.log('directory-name πŸ‘‰οΈ', __dirname);
Vinny (@Wasp)
Vinny (@Wasp)β€’2y ago
depending on your implementation, you might need to use import.meta.url to get the directory, as wasp outputs compiled files in a different path than your root src but what you're mainly looking for is setting up a custom API that doesn't depend on Entities, nor operations, allowing you to use e.g. res.sendFile() : https://wasp-lang.dev/docs/language/features#apis
Gwaggli
GwaggliOPβ€’2y ago
Thank you for your very quick response! Yes exactly i am trying to setup a custom api like following code:
export const getImage: GetImage = async (req, res, context) => {
res.sendFile('/assets/image.png')
}
export const getImage: GetImage = async (req, res, context) => {
res.sendFile('/assets/image.png')
}
and i have my image stored here:
|- server
|-- assets
|----image.png
|- server
|-- assets
|----image.png
But that entire folder is not copied to the output folder when the project is built, so there is no way, this code could ever work. Or what am i missing?
Vinny (@Wasp)
Vinny (@Wasp)β€’2y ago
is the wasp server running? wasp start?
Gwaggli
GwaggliOPβ€’2y ago
yes πŸ™‚
MEE6
MEE6β€’2y ago
Wohooo @Gwaggli, you just became a Waspeteer level 1!
Vinny (@Wasp)
Vinny (@Wasp)β€’2y ago
ah. try putting the folder in ./src/shared actually, that shouldn't matter. If they are in src/server/assets they will get compiled to the .wasp/out folder and then all you'll have to do is give the relative path. so if your api file is in the src/server folder then just make the path const ASSETS_DIR = './assets';
|- server
|-- api.ts
|-- assets
|----image.png
|- server
|-- api.ts
|-- assets
|----image.png
Gwaggli
GwaggliOPβ€’2y ago
Ah i see now, yes it does get compiled into the output folder inside the ext-src folder. So i got it to work with:
const __filename = fileURLToPath(import.meta.url);

const __dirname = path.dirname(__filename);

const root = path.join(__dirname, '../../../src/ext-src')
const __filename = fileURLToPath(import.meta.url);

const __dirname = path.dirname(__filename);

const root = path.join(__dirname, '../../../src/ext-src')
and then with the relative path ./assets/image.png Seems a little hacky tho, is there no cleaner way? As my code now relies on some wasp-internal file structuring While this code works locally, it doesnt seem to work on the fly.dev server. As there is no src folder on the fly machine but only the dist folder which does not contain the assets folder.
martinsos
martinsosβ€’2y ago
You can do that via api feature that @Vinny (@Wasp) shared, there you can direclty define your Express routes, like the one you described. You will have to figure out the details on your own, but it shouldn't be anything particularly Wasp specific. If you get stuck, ping us. One thing though -> where will you store files? Are they always the same, or are they re-calculated every so and so? The thing is, if you are storing at the file system of where your server is running, then it depends on where you will host the app -> it will be important that they guarantee the file system to be permanent, or at least the part of it where you will store the files. Other options are S3 or something similar, as you mentioned, or even storing the images in the DB if you have only a couple. Finally, they could be living even on the frontend possibly, as static assets. I think it would be useful to understand the lifecycle of these images, for us to be able to maybe help a bit more -> are they completely static and you have them already know, are they created per user, ... . You said they are calculated from the db, but I am not sure what that means -> at which moment, and how often?
Vinny (@Wasp)
Vinny (@Wasp)β€’2y ago
Yeah @martinsos asks good questions, because it seems in this code you’re trying to access this file path from the client perhaps?
Gwaggli
GwaggliOPβ€’2y ago
Thank you for your help! I try to explain in more detail what I want to achieve: I need to have an API (which I set up and is reachable from the outside world) that returns an image for a product based on its score. So I would call let's say server.fly.dev/static-files/:productId and then calculate the score of that product based on some data stored in the DB and depending on the score (0-6) the API would return one of 6 images that all are static and won't change. Here is the code i have for now:
// API

import { GetLabelImageForProduct } from '@wasp/apis/types'
import { fileURLToPath } from 'url';
import path from 'path';

// @ts-ignore
const __filename = fileURLToPath(import.meta.url); // /home/<User>/<Project>/.wasp/out/server/dist/ext-src/api/product.js

const __dirname = path.dirname(__filename); // /home/<User>/<Project>/.wasp/out/server/dist/ext-src/api

const root = path.join(__dirname, '../../../src/ext-src') // /home/<User>/<Project>/.wasp/out/server/src/ext-src

export const getLabelImageForProduct: GetLabelImageForProduct = async (req, res, context) => {
const productId = req.params.productId
const score = calculateScore(productId);
switch (score) {
case 0: return res.sendFile('./assets/label0.png', { root: root });
case 1: return res.sendFile('./assets/label1.png', { root: root });
case 2: return res.sendFile('./assets/label2.png', { root: root });
// and so on...
}

res.sendFile('./assets/label?.png', { root: root })
}
// API

import { GetLabelImageForProduct } from '@wasp/apis/types'
import { fileURLToPath } from 'url';
import path from 'path';

// @ts-ignore
const __filename = fileURLToPath(import.meta.url); // /home/<User>/<Project>/.wasp/out/server/dist/ext-src/api/product.js

const __dirname = path.dirname(__filename); // /home/<User>/<Project>/.wasp/out/server/dist/ext-src/api

const root = path.join(__dirname, '../../../src/ext-src') // /home/<User>/<Project>/.wasp/out/server/src/ext-src

export const getLabelImageForProduct: GetLabelImageForProduct = async (req, res, context) => {
const productId = req.params.productId
const score = calculateScore(productId);
switch (score) {
case 0: return res.sendFile('./assets/label0.png', { root: root });
case 1: return res.sendFile('./assets/label1.png', { root: root });
case 2: return res.sendFile('./assets/label2.png', { root: root });
// and so on...
}

res.sendFile('./assets/label?.png', { root: root })
}
This code works fine while working locally, but having declared my root with ../../../src/ext-src seems horribly wrong and doesn't work once deployed to fly. While the asset folder in question gets copied into /.wasp/out/server/src/ext-src (inside the src folder) it does not get copied anywhere inside the dist (/.wasp/out/server/dist/) folder. (that's why I tried to declare the root to be in src/ext-src ) On the deployed fly machine there is no src folder tho and thus the whole assets folder is missing. So my file structure looks like this:
|- server
|--- api
|-----product.ts
|
|--- assets
|-----image.png
|- server
|--- api
|-----product.ts
|
|--- assets
|-----image.png
The path to the api file inside the dist folder is: .wasp/out/server/dist/ext-src/api/product.js The path to the api file inside the src folder is: .wasp/out/server/src/ext-src/api/product.ts The path to the image file inside the dist folder is: does not exist The path to the image file inside the src folder is: .wasp/out/server/src/ext-src/assets/label.png I feel like bc there is no compile time reference to the file it is not getting bundled and thus copied to the dist folder, could that be?
martinsos
martinsosβ€’2y ago
Yes, your last sentence is very correct! There is no import of those files, nothing to tell bundler they are important, so it ignores them. Ok, so to rehash, you need an API route that respond with one of the 6 images that you know and have prepared. You tried to put them into some "assets" folder in the server, but failed. You could put them on S3 or even in the DB, but that feels like an overkill since you have only 6 images -> I agree with that. I think approach with "assets" folder is reasonable, and we should look into enabling you to do that. I know that on frontend you can actually import those png images in your code, via import. I am not 100% sure if you can do that on backend, I think not so hm, but if you could give that a try that could be interesting. If not, then I think it is on us really to ensure we provide you with a way to provide static assets -> we could make it so that Wasp has a directory "public" or "assets" in server where any files can be put and they will certainly be included in the server when bundled. @miho what do you say about this, any other ideas?
miho
mihoβ€’2y ago
@Gwaggli are the 6 photos secret and that's why you are sending them via backend? If they are not, you could: 1. upload them to some 3rd party storage and just send the link from the backend 2. keep them in your frontend and make a switch case on the frontend `backend says "Score is 5" -> frontend shows the photo for "score 5"
martinsos
martinsosβ€’2y ago
@miho what if they still wanted to share them from the server, via API, and they wanted to store them on the server for that purpose -> is there any easy way to include them in the server code? Kind of like public assets, but on the server side? They can just put it in the code because it seems bundler (actually, typescript compiler I guess?) drops those hm.
miho
mihoβ€’2y ago
Recreating the issue locally helped me understand the issue a bit better. - create a file in server/media/file.txt - Wasp copied over files from server to <serverApp>/src/ext-src - Wasp builds the app -> but only TS and JS files are transpiled and copied -> no media folder or file.txt in the built app - Wasp deploys the app -> it only takes the build artifiacts i.e. not the media folder or file.txt For now, we can't help with this other then what I mentioned above: - host it on S3 (a bit an overkill but it should work) - return names from server, but keep the files in the frontend code (if the file contents are not secret) πŸ’‘ In the future we should introduce a media folder (well known idea in other frameworks) which would be copied over when deploying, maybe even served statically so users can access it via /media/<filename>? @martinsos do we have an issue for this already?
martinsos
martinsosβ€’2y ago
We have this one: I will add this into to it! https://github.com/wasp-lang/wasp/issues/609 Added: https://github.com/wasp-lang/wasp/issues/609#issuecomment-1572189300 . @Gwaggli to summarize: We don't currently have a way to do it by adding those files to server/ source code, but will have in the future. In the meantime solution is either external hosting (S3, some image hosting), DB, or as static files on frontend (if you don't really need them in the api but just on frontend).
Gwaggli
GwaggliOPβ€’2y ago
Thank you very much guys for analyzing! So for now I will probably go for 3rd party solution. But would definitely be nice to have this in the future πŸ™‚ The reason I don't really wanna add it to the frontend itself is that this API would be supposed to be called from external websites to display it directly on their websites.
martinsos
martinsosβ€’2y ago
Sure, I guess that makes sense! Then third party is probably the best option, as services for hosting images usually have fast response times and caches and stuff, and your API can instead just respond with the link to such image. Also, it will result in less load on your server (otherwise your server would need to be sending those images quite often).
Want results from more Discord servers?
Add your server