Unable to download a sent file with fastify - node.js

Edit
No solutions worked for me, so I ended up doing the following:
// in handler
return reply.sendFile('my_script.sql', 'scripts');
// on the client
const { data } = await axios({
url: '/api/generate',
method: 'GET',
responseType: 'blob'
})
const url = window.URL.createObjectURL(new Blob([content]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'my_script.sql');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
This works like a charm
Original post:
I have a simple endpoint as follows:
fastify.get('/generate', async (request, reply) => {
console.log('endpoint hit'); // this line is printed
const filePath = path.join(__dirname, '..', '..', 'scripts', 'my_script.sql');
reply.sendFile(filePath, 'scripts');
})
This just sends the contents of the sql file in text form like so:
I know that express has a res.download method, but I couldn't find an analog with fastify.
What am I doing wrong here? How can I download this file?

You can use fastify-static or by sending the right headers:
fastify.get('/', (request, reply) => {
const filePath = require('path').join(
__dirname,
'../../scripts/my_script.sql'
)
const stream = require('fs').createReadStream(filePath)
reply.header(
'Content-Disposition',
'attachment; filename=foo.sql'
reply.send(stream).type('application/sql').code(200)
})

Related

How to copy data from one stream to another in Node JS

I want to copy data from one stream to another in Java i do in below way
ByteStreams.copy( inputStream, outputStream );
In Node JS i am trying to find out how to do that
// Making an ajax call to get the Video file
const getVideo = async (req, res) => {
try {
axios.get('Video URL')
.then(function (videoResponse) {
res.setHeader("Content-Type", "video/mp4");
// copy the inputStram of videoResponse
//to the output stram of res
// copy the videoResponse to res
})
} catch (error) {
console.log(error);
}
};
Can anyone suggest how to do that, Thank you for your help
You need to set the responseType from axios to 'stream', then you can pipe data from the input stream to the response stream using .pipe().
// Making an ajax call to get the video file
const getVideo = async (req, res) => {
try {
axios({
method: 'get',
url: 'Video URL',
responseType: 'stream'
}).then((videoResponse) => {
res.setHeader("Content-Type", "video/mp4");
videoResponse.data.pipe(res);
});
} catch (err) {
console.log(err);
}
}
For more information about the .pipe() function, please read the Node.js docs.
For more information about axios request configuration options, please read the axios docs.
The most simple example for reading and writing to File System would be:
const fs = require('fs')
const input = fs.createReadStream('input_file')
const output = fs.createWriteStream('output_file')
input.pipe(output)
Check the File System docs for Node.js.
The input and output stream can be any ReadStream and WriteStream, like an HTTP response or S3 for example.
From the Axios Github README you have the example which looks very similar to what you are trying to do (please use the original one, I had to change the URL here)
// GET request for remote image in node.js
axios({
method: 'get',
url: 'https://lh3.googleusercontent.com/iXm....',
responseType: 'stream'
})
.then(function (response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
The example below streams video outpu. I am assuming you need something similar. Can you try something like this and based on this example modify your code
const express = require('express')
const fs = require('fs')
const path = require('path')
const app = express()
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname + '/index.html'))
})
app.get('/video', function(req, res) {
const path = 'assets/sample.mp4' //This can be replaced by axios.get('Video URL')
const stat = fs.statSync(path)
const fileSize = stat.size
const range = req.headers.range
if (range) {
const parts = range.replace(/bytes=/, "").split("-")
const start = parseInt(parts[0], 10)
const end = parts[1]
? parseInt(parts[1], 10)
: fileSize-1
const chunksize = (end-start)+1
const file = fs.createReadStream(path, {start, end})
const head = {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4',
}
res.writeHead(206, head)
file.pipe(res)
} else {
const head = {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
}
res.writeHead(200, head)
fs.createReadStream(path).pipe(res)
}
})
app.listen(3000, function () {
console.log('App is running on port 3000')
In the above code:
At the top I have included needed NPM packages. After that we have a get method served on route '/' for serving the html file. Then there is a get method with route '/video' which is being called from html file. In this method at first the filesize is detected with statSync method of fs. after that with stream video is downloaded to client in chunks. With every new request from client the value of start and end is changing to get the next chunk of video. 206 is set in response header to send only newly made stream(chunk of video).
Credit - Example source

Nodejs fs.createReadStream can't read Firebase Storage download URL "ENOENT: no such file or directory"

The documentation for fs.createReadStream(path) says that it can read a URL:
path <string> | <Buffer> | <URL>
The documentation shows quotes around the path:
fs.createReadStream('/dev/input/event0');
But when I put in this Firebase Storage download URL
fs.createReadStream('https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Users%2FbcmrZDO0X5N6kB38MqhUJZ11OzA3%2Faudio-file.flac?alt=media&token=871b9401-c6af-4c38-aaf3-889bb5952d0e'),
I get the error message ENOENT: no such file or directory. You can click on the URL and hear that it works:
https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Users%2FbcmrZDO0X5N6kB38MqhUJZ11OzA3%2Faudio-file.flac?alt=media&token=871b9401-c6af-4c38-aaf3-889bb5952d0e
I also tried the Google Storage URI:
fs.createReadStream('gs://languagetwo-cd94d.appspot.com/Users/bcmrZDO0X5N6kB38MqhUJZ11OzA3/audio-file.flac'),
That didn't work. Do I have to use https.request to make an HTTP GET request?
Here's my full code, copied from the IBM Cloud Speech-to-Text API docs:
const fs = require('fs');
const SpeechToTextV1 = require('ibm-watson/speech-to-text/v1');
const { IamAuthenticator } = require('ibm-watson/auth');
const speechToText = new SpeechToTextV1({
authenticator: new IamAuthenticator({
apikey: 'my-api-key',
}),
url: 'https://api.us-south.speech-to-text.watson.cloud.ibm.com/instances/01010101',
});
var params = {
audio: fs.createReadStream('https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Users%2FbcmrZDO0X5N6kB38MqhUJZ11OzA3%2Faudio-file.flac?alt=media&token=871b9401-c6af-4c38-aaf3-889bb5952d0e'),
contentType: 'audio/flac',
wordAlternativesThreshold: 0.9,
keywords: ['colorado', 'tornado', 'tornadoes'],
keywordsThreshold: 0.5,
};
speechToText.recognize(params)
.then(results => {
console.log(JSON.stringify(results, null, 2));
})
.catch(err => {
console.log('error:', err);
});
To get the file, you have to open up a write stream, then pipe the output of the http library (axios in this example) to that stream.
This would serve you better:
const Fs = require('fs')
const Path = require('path')
const Axios = require('axios')
async function downloadImage () {
const url = 'https://unsplash.com/photos/AaEQmoufHLk/download?force=true'
const path = Path.resolve(__dirname, 'images', 'code.jpg')
const writer = Fs.createWriteStream(path)
const response = await Axios({
url,
method: 'GET',
responseType: 'stream'
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', resolve)
writer.on('error', reject)
})
}
source
If you see here, it clearly mentions that it only supports file:// protocol. There is no support for http/https.
I wouldn't suggest using request or request-promise as they have been deprecated. I would recommend using some modern libraries like got, axios etc.
Another question I asked answers this question as well. Axios not only gets the file but also streams it, eliminating the fs.createReadStream.

Downloading files using express server from another site result in full html source instead of the actuall file

I am trying to download a file on the express server filesystem trough axios but the problem is that I get the site source html instead of the actual file. But if I visit the site the file downloads fine.
Here is the express route I'm using.
Router.route('/:file(*)').get(function (req, res) {
var file = req.params.file;
async function downloadImage () {
const url = 'https://www.hikingproject.com/trail/gpx/7022698'
const path = Path.resolve(__dirname, '../images', 'code.gpx')
const writer = Fs.createWriteStream(path)
const response = await Axios({
url,
method: 'GET',
responseType: 'stream'
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', resolve)
writer.on('error', reject)
})
}
downloadImage()
});
What I'm trying to do is get some geodata using the gpx files and transform it to a geojson data.

Sending DataForm containing a pdf file from a node.js file

I am trying to send a FormData containing a pdf file from a node.js script to a server. The request fail with a status code 400 in response. In the server side, it seems that he do not consider the data as a pdf file, or maybe that that the whole data is undefined.
This is what my node.js script does : (I've tried other ways to send the request but without success)
import axios from "axios"
import fs from "fs-extra"
import FormData from 'form-data'
const formData = new FormData()
const diplomaBuffer = await fs.readFile(`../data/fake/foo`+i+`.pdf`)
formData.append("diploma", diplomaBuffer)
formData.append("diplomaId", 1)
axios({
method: 'post',
processData: false,
contentType: 'multipart/form-data',
cache: false,
url: 'http://localhost:8080/certificates',
data: formData,
config: { headers: formData.getHeaders() }
})
// const response = await axios
// .post(`http://localhost:8080/certificates`, {
// body: formData
// })
// const response = await axios
// .post(`http://localhost:8080/certificates`
// , formData
// , {headers: formData.getHeaders()})
This is the function called in the server side :
app.post('/certificates', async (req, res) => {
const files = req.files
if(!files || !files.diploma || !files.diplomaId) {
res.status(400).send(new Error("No file provided"))
}
if(files.diploma.mimetype !== "application/pdf") {
res.status(400).send(new Error("diploma is not a pdf"))
}
// operations made with the received pdf (not relevant for the question)
const certificateId = uniqid()
const outputDiploma = `data/diplomas/${certificateId}.pdf`
await Promise.all([
fs.writeFile(outputDiploma, files.diploma.data),
])
await pdf.sign(outputDiploma, `${sha("sha256").update(certificateId + diplomaSecret).digest("hex")} : ${date()}`)
const diplomaBuffer = await fs.readFile(outputDiploma)
const certificate_hash = verify_pdf.hashBuffer(diplomaBuffer)
const certificateRegistry = await app.bizNetworkConnection.getAssetRegistry("consortium.supchain.assets.Certificate")
const certificate = app.factory.newResource("consortium.supchain.assets", "Certificate", certificateId)
certificate.diploma = app.factory.newRelationship("consortium.supchain.assets", "Diploma", req.body.diplomaId)
certificate.hashInfo = certificate_hash
await app.registry.certificate.add(certificate)
res.status(200).send("ok")
})

Problems uploading image file with multer (React and Node.js)

I've spent hours trying to find the solution for something which should be quite simple: uploading a file to the server from the client. I am using React.js on the frontend, Express on the backend, and multer for the image uploads.
When I try to upload a file, nothing happens. An uploads/ directory is created, but no file goes there. req.file and req.files are undefined. req.body.file is empty. The form data exists before it is sent.
If I set the Content-Type header to "multipart/form-data" I get a boundary error from multer.
Input
<input
onChange={this.sendFile}
name="avatar"
placeholder="Choose avatar"
type="file"
/>
sendFile
sendFile = e => {
const data = new FormData();
const file = e.target.files[0];
data.append("file", file);
this.props.sendFile(data);
};
Redux action
export default file => async dispatch => {
const res = await axios.post("/api/upload/", { file });
};
Express
const multer = require("multer");
const upload = multer({ dest: "uploads/" });
router.post("/upload/", upload.single("avatar"), (req, res) => {
return res.sendStatus(200);
});
I tried to reproduce it and made it work with this method:
sendFile = e => {
const data = new FormData();
const file = e.target.files[0];
data.append("avatar", file); // <-- use "avatar" instead of "file" here
axios({
method: 'post',
url: 'http://localhost:9000/api/upload',
data: data,
config: { headers: { 'Content-Type': 'multipart/form-data' } }
});
};
Try to set the content-type header to multipart/form-data in the axios request and send the full FormData object as the second parameter.
Like this:
const config = {
headers: {
'content-type': 'multipart/form-data'
}
};
axios.post('/api/upload/', file, headers);`

Resources