I have a small @tus/node-server with the @tus/s3-store to upload files to a Tigris bucket.
I’ve followed the documentation to specify a custom path on the storage based on the userId.
The files are properly being uploaded to the right location in my bucket.
I’m using Uppy (React) with the Tus uploader.
How can I build a URL for the uploaded file and read that in the upload-success event on the client? This event gets an uploadURL which is the URL of the specific Tus server request, not the actual file URL on the destination (the Tigris bucket).
import { Server } from '@tus/server'
import { S3Store } from '@tus/s3-store'
import { consola } from "consola";
import { nanoid } from "nanoid"
import invariant from 'tiny-invariant';
const host = process.env.HOST || '127.0.0.1'
const port = process.env.PORT || 8080
invariant(process.env.S3_BUCKET, "S3_BUCKET is required")
invariant(process.env.S3_ACCESS_KEY_ID, "S3_ACCESS_KEY_ID is required")
invariant(process.env.S3_SECRET_ACCESS_KEY, "S3_SECRET_ACCESS_KEY is required")
const S3_ENDPOINT = process.env.S3_ENDPOINT || "https://fly.storage.tigris.dev"
const S3_BUCKET = process.env.S3_BUCKET
const server = new Server({
path: '/files',
datastore: new S3Store({
s3ClientConfig: {
endpoint: S3_ENDPOINT,
region: "auto",
bucket: S3_BUCKET,
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
}
}
}),
namingFunction(req, metadata) {
invariant(metadata?.filename, "filename is required")
invariant(metadata?.userId, "userId is required")
const id = nanoid()
const userId = metadata.userId;
const filename = metadata.filename;
const extension = filename.split('.').pop();
return `${userId}/${id}.${extension}`
},
generateUrl(req, { proto, host, path, id }) {
id = Buffer.from(id, 'utf-8').toString('base64url')
return `${proto}://${host}${path}/${id}`
},
getFileIdFromRequest(req, lastPath) {
// lastPath is everything after the last `/`
// If your custom URL is different, this might be undefined
// and you need to extract the ID yourself
return Buffer.from(lastPath ?? "", 'base64url').toString('utf-8')
},
onUploadFinish: async (req, res, upload) => {
consola.info(`Upload finished: ${upload.id}`)
// Is this where I can build the file url? If so how?
// I want to be able to build the file URL like:
// const url = `${S3_ENDPOINT}/${S3_BUCKET}/${pathToFile}`
// And get that in the `upload-success` (or any similar event) on Uppy.js
return res
}
})
consola.info(`Server is running on ${host}:${port}`)
server.listen({ host, port })
Is my use case really that uncommon? I mean, you upload a file, then get a result, if it succeeded you get some information about the result. Among that information, it would make a lot of sense to have an URL to the file.
Otherwise, I have to build that logic on the client and it doesn’t belong there. The responsible of knowing how the uploaded file is handled, where is it stored and so it’s the server. Why can’t it just return a URL back to the client after the upload is completed…?
You can use onUploadFinish and return status 200 (otherwise body is not allowed) and the url in the body. Or don’t change the status code and use a header. Then you can get it somehow in onAfterResponse for @uppy/tus.
Do you see it returned in the network tab? If so, can you access it in with onAfterResponse for @uppy/tus? If you can’t access it be do see it please create an uppy issue. If you don’t see it in the network tab create an issue for tus-node-server.
Ok, so the thing is that the server seems to be skipping/ignoring already uploaded files, but it’s returning a 200 empty response back to the client.
How can I disable this cache or ignore thing? I want the server to process each file upload independently as a new file, even though the file uploaded might be the same one over and over again.
It’s not the server ignoring files, but the client. It recognizes that it uploaded the same file and has to ability to resume the previous upload which only consists of a single request to check if the server still has the file.
If you don’t want this behavior, configure the client to not resume completed uploads.
Ok, but how exactly does the “single request to check if the server still has the file” works? Is there some event I can listen to?
If the server still has the file, how can I modify the response back to the client to include a URL to the file just like I do in the onUploadFinish event? I’m simply trying to have a consistent response to the client in both cases: “new file uploaded” and “file was previously uploaded, returning existing URL”.
Tus-js-client sends a HEAD request (see Resumable upload protocol 1.0.x | tus.io) to check the upload’s status. On the server-side, there are no events so far to modify the server’s response.
That’s currently not possible with tus-node-server. @Merlijn and I have been brainstorming about how this situation can be improved using metadata. The server could attach metadata once the upload is finished, which the client can then retrieve when either the upload is finished or the client notices that the upload is already done.