Get Download URL After Upload

Using the provided template, I'm having a hard time figuring out a good way to get the download url after uploading a file to s3. I know the template lists all of a user's files with a download button, but the logic there is a little confusing to follow. I have a form that uses the file upload functionality and that works fine, but I need to capture the download url before the form "submits". Could someone give me a hand with this? Lol even chatgpt doesn't handle this so well. For context, I have a form to upload a post. The image for it needs to upload, provide a download link, that I can then store when I createPost(). Thanks in advance. And sorry if it's a noob question.
12 Replies
MEE6
MEE67mo ago
Wohooo @JLegendz, you just became a Waspeteer level 1!
kapa.ai
kapa.ai7mo ago
Hi @JLegendz. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- No problem at all, it's a great question! The logic for getting a download URL after uploading a file to S3 is not directly provided in the template, but it can be inferred from the discussions and code snippets shared in the knowledge sources. Here's a simplified version of the process: 1. When you upload a file, you get a key which is the identifier of the file in S3. This happens in the handleUploadFile function in the front end. Here's the relevant code snippet from Github Issue (open): Ability to serve and upload dynamic binary assets (images, pdfs) > Comments Part 1 (https://github.com/wasp-lang/wasp/issues/494):
const handleUploadFile = async () => {
...
let data = await getUploadFileSignedURL(...);
// key is the identifier of the file in S3
const { uploadUrl, key } = data;
// upload the actual file using the signed URL, newFile here is the file selected in the form
await axios.put(uploadUrl, newFile);
...
}
const handleUploadFile = async () => {
...
let data = await getUploadFileSignedURL(...);
// key is the identifier of the file in S3
const { uploadUrl, key } = data;
// upload the actual file using the signed URL, newFile here is the file selected in the form
await axios.put(uploadUrl, newFile);
...
}
2. After the file is uploaded, you can use this key to generate a download URL. This is done in the getDownloadFileSignedURLFromS3 function. Here's the relevant code snippet from Discord support thread: file download from s3 > Answers Part 1 (https://discord.com/channels/686873244791210014/1207704500966789171):
export const getDownloadFileSignedURLFromS3 = ({ key }: { key: string }) => {
const s3Params = {
Bucket: process.env.AWS_S3_FILES_BUCKET,
Key: key,
Expires: 30, // <----
};
return s3Client.getSignedUrl("getObject", s3Params);
}
export const getDownloadFileSignedURLFromS3 = ({ key }: { key: string }) => {
const s3Params = {
Bucket: process.env.AWS_S3_FILES_BUCKET,
Key: key,
Expires: 30, // <----
};
return s3Client.getSignedUrl("getObject", s3Params);
}
3. You can then store this download URL when you create your post. Please note that the Expires property in the getDownloadFileSignedURLFromS3 function determines how long the download URL is valid. In the current implementation, it's set to 30 seconds, but you can increase this value if you need the URL to be valid for a longer period. I hope this helps! Let me know if you have any other questions. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendzOP7mo ago
@kapa.ai This is my handleUpload function, where in it can I access the key?
const handleUpload = async (e: FormEvent<HTMLFormElement>) => {
try {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const file = formData.get('file-upload') as File;
if (!file || !file.name || !file.type) {
throw new Error('No file selected');
}

const fileType = file.type;
const name = file.name;

const { uploadUrl } = await createFile({ fileType, name });
if (!uploadUrl) {
throw new Error('Failed to get upload URL');
}
const res = await axios.put(uploadUrl, file, {
headers: {
'Content-Type': fileType,
},
});
if (res.status !== 200) {
throw new Error('File upload to S3 failed');
}
} catch (error) {
alert('Error uploading file. Please try again');
console.error('Error uploading file', error);
}
};
const handleUpload = async (e: FormEvent<HTMLFormElement>) => {
try {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const file = formData.get('file-upload') as File;
if (!file || !file.name || !file.type) {
throw new Error('No file selected');
}

const fileType = file.type;
const name = file.name;

const { uploadUrl } = await createFile({ fileType, name });
if (!uploadUrl) {
throw new Error('Failed to get upload URL');
}
const res = await axios.put(uploadUrl, file, {
headers: {
'Content-Type': fileType,
},
});
if (res.status !== 200) {
throw new Error('File upload to S3 failed');
}
} catch (error) {
alert('Error uploading file. Please try again');
console.error('Error uploading file', error);
}
};
kapa.ai
kapa.ai7mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- In the provided handleUpload function, the key is not directly returned when you call createFile. However, if your createFile function is similar to the getUploadFileSignedURL function mentioned in the Github Issue (open): Ability to serve and upload dynamic binary assets (images, pdfs) > Comments Part 1 (https://github.com/wasp-lang/wasp/issues/494), it should return both uploadUrl and key. You can modify the line where you destructure uploadUrl to also get the key:
const { uploadUrl, key } = await createFile({ fileType, name });
const { uploadUrl, key } = await createFile({ fileType, name });
Now, you can use the key in the rest of your function. If createFile does not return a key, you might need to modify it to do so. Please note that this assumes your createFile function is set up to return a key. If it's not, you would need to adjust it accordingly. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendzOP7mo ago
@kapa.ai These download urls expire, how do I make sure I get urls that don't expire?
kapa.ai
kapa.ai7mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- Presigned URLs generated by AWS S3 do have an expiration time, and this is a security feature to prevent unauthorized access if the URL is leaked. The maximum expiration time is 7 days. However, if you want a URL that doesn't expire, you can create a different system where you generate a new URL each time a user requests to download the file. This is discussed in the Discord support thread: file download from s3 > Answers Part 3 (https://discord.com/channels/686873244791210014/1207704500966789171). Here's the suggested approach: 1. Instead of sharing the S3 presigned URL directly, share a link to a route in your Wasp app, for example <https://myapp.com/downloadFile/fileId> where fileId is something that will allow you to know which file they want. 2. Once they come to that route, your logic will, based on fileId, obtain a new download URL from S3 and give them that. This could be done immediately, once they click download, or maybe after you authenticated them and verified they are indeed that user. This way, you have a lot of control over the experience for them, and you should look at the presigned URLs from S3 as just a final step in the process, kind of like telling S3 "ok this person now really wants to download this, let's do it". To implement this in Wasp, you can use the api declaration to create a custom HTTP API endpoint that generates a new download URL each time it's called. You can learn more about this in the Wasp documentation on Custom HTTP API Endpoints (https://wasp-lang.dev/docs/advanced/apis). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendzOP7mo ago
@kapa.ai these are for images uploaded in the app. Should I be taking a different approach?
kapa.ai
kapa.ai7mo ago
Let me check the knowledge base for you. One moment! :books: ---------------------- The approach for handling images is essentially the same as for other file types. You would still use the same process to upload the image to S3 and generate a download URL. However, there are a few additional considerations you might want to keep in mind: 1. File Type: When you're uploading the file, you'll want to specify the correct ContentType. For images, this could be image/png, image/jpeg, etc. This is shown in the code snippet from Github Issue (open): Ability to serve and upload dynamic binary assets (images, pdfs) > Comments Part 3 (https://github.com/wasp-lang/wasp/issues/494):
const upload = await S3.send(
new PutObjectCommand({
Bucket: &quot;&lt;bucket_name&gt;&quot;,
Key: key,
Body: response.data,
// If uploading images for example
ContentType: &quot;image/png&quot;,
})
);
const upload = await S3.send(
new PutObjectCommand({
Bucket: &quot;&lt;bucket_name&gt;&quot;,
Key: key,
Body: response.data,
// If uploading images for example
ContentType: &quot;image/png&quot;,
})
);
2. Displaying Images: If you're displaying these images in your app, you can use the download URL directly in an img tag. However, keep in mind that if the URL expires, the image will no longer be accessible. You might want to implement a system where a new URL is generated each time the image is requested, as discussed in the previous answer. 3. Image Processing: Depending on your use case, you might want to consider additional image processing steps, such as resizing or compressing the images before upload. This can help to improve the performance of your app, especially if you're dealing with large images. Remember, the key is to ensure that the process is secure and provides a good user experience. This includes things like providing feedback to the user during the upload process, handling errors gracefully, and ensuring that images load quickly and correctly. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @JLegendz, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
JLegendz
JLegendzOP7mo ago
I still need to figure out the best way to handle image uploads with s3/wasp. My urls keep expiring for now.
Vinny (@Wasp)
Vinny (@Wasp)7mo ago
If it’s not sensitive data that can be publicly displayed and downloaded by anyone you could change the AWS S3 settings to match. Then you’ll have a persistent download link for the file
JLegendz
JLegendzOP7mo ago
Thanks so much! I'll try that today. I’ve looked into the AWS S3 settings and from what I can tell, it looks like all presigned urls expire. I want to allow users to upload images via s3 but maybe presigned isn’t the best practice for this. I’m coming from a background of using fire base for these kinds of things but I’m loving Wasp so far and want to stay within this “ecosystem”. I’ll keep at it and share my solution here when I find it, but it anyone has any tips I’d love to hear it. Thanks! I was able to follow the steps here to make my bucket of images accessessible to the public: https://repost.aws/knowledge-center/read-access-objects-s3-bucket At some point I think I'll still need to clean things up a bit but it's working for now.
martinsos
martinsos7mo ago
Awesome @JLegendz ! Yeah this is the right approach, this data is obviusly public (the images), so you want to make that bucket public. If it is private, your links do need to have expiry date, and you need to keep producing new links wheneer you need them, that is how it goes. You could do that also in theory, you wouldn't store image link but its url on the bucket and would keep asking server to product new link for you whenever somebody wants to see it, but they can't take that URL with them somewhere and chekc ito ut later as it will indeed be expired
Want results from more Discord servers?
Add your server