https://github.com/lukeautry/tsoa/issues/435 and #44 were both closed but the issue is still here. Is there any way to send files to client using tsoa? Why doesn't @Response decorator exists ?
I'm submitting a ...
I confirm that I
Router to send file buffer to be downloaded by the client.
When using Attempt 1: the file is sent but the file is invalid and cannot be opened.
When using Attempt 2:
TypeError: Cannot read property 'on' of undefined
at ReadStream.Readable.pipe (_stream_readable.js:669:8)
at OrderETicketsController.read (/app/dist/controllers/orderEtickets/controllers.js:22:24)
at processTicksAndRejections (internal/process/task_queues.js:97:5)
error Command failed with signal "SIGINT".
this.setStatus(200);
this.setHeader('Content-Type', 'application/pdf');
this.setHeader('Content-Disposition', `attachment; filename=${fileName}`);
return fs.promises.readFile(filePath);
req.res.download(filePath) // req.res is undefined!
Version of the library: 3.3.0 (latest)
Version of NodeJS: v12.18.1
Ok I finally managed to do it!
The generated routes file already handles streams, you just have to return a valid readable stream.
function returnHandler(response: any, statusCode?: number, data?: any, headers: any = {}) {
Object.keys(headers).forEach((name: string) => {
response.set(name, headers[name]);
});
if (data && typeof data.pipe === 'function' && data.readable && typeof data._read === 'function') {
// Here, data.pipe must be a function
data.pipe(response);
} else if (data || data === false) { // === false allows boolean result
response.status(statusCode || 200).json(data);
} else {
response.status(statusCode || 204).end();
}
}
Controller:
public async download(): Promise<fs.ReadStream> {
const filePath: string = '/path/to/file';
const fileName: string = 'doc.pdf';
const stat: fs.Stats = await fs.promises.stat(filePath);
this.setStatus(200);
this.setHeader('Content-Type', mime.lookup(filePath));
this.setHeader('Content-Length', stat.size.toString());
// Removing this line will cause to not launch the download, just serve the file as it
this.setHeader('Content-Disposition', `attachment; filename=${fileName}`);
return fs.createReadStream(filePath);
}
The solution is more complicated than using res.download but it doesn't even work because type fs.ReadStream cannot be imported by tsoa. So there's an option in routes.ts to use readable stream but it's impossible to return a stream type from the controller. I'd suggest to implement a feature to make file download easier (like it is already with Express).
Generate routes error.
Error: No matching module declarations found for fs.
at new GenerateMetadataError (/.../node_modules/tsoa/dist/metadataGeneration/exceptions.js:22:28)
So the only way I found to fix this is to use Readable interface from stream :
import { Readable } from 'stream';
// ...
public async download(): Promise<Readable> {
// ...
The documentation is wrong but it works as needed.

Why doesn't @Response decorator exists ?
It does.
I assume this affects the Express template only?
We are checking for Readable Streams, seems like we should handle Buffer aswell, the type resolution seems fine, but can't tell from SwUi, please post the relevant spec.
I assume this affects the Express template only?
Yes.
please post the relevant spec
Here's an example of output from tsoa :)
"/download": {
"get": {
"operationId": "downloadFile",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "string",
"format": "byte"
}
}
}
},
},
"description": "Download a file.",
"tags": [
"Download"
],
"security": [],
"parameters": []
}
},
This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days
Most helpful comment
Ok I finally managed to do it!
The generated routes file already handles streams, you just have to return a valid readable stream.
Controller:
The solution is more complicated than using
res.downloadbut it doesn't even work because typefs.ReadStreamcannot be imported by tsoa. So there's an option in routes.ts to use readable stream but it's impossible to return a stream type from the controller. I'd suggest to implement a feature to make file download easier (like it is already with Express).So the only way I found to fix this is to use Readable interface from stream :
The documentation is wrong but it works as needed.