How can I allow a user to download a file from a link in express? - node.js

I have a function that allows you to create a folder in the directory where the server is located, and upload files there
And I'm trying to access them via a direct link, like this
http://localhost:5000/attachments/1618413024408--1.jpg
Because, the file is located here
But why can't I access it?
Cannot GET /attachments/1618413024408--1.jpg

Express itself provides an easy to use implementation for this express.static(root, [options]).
Simply add this add the right position in your code:
app.use('/attachments', express.static(path.join(__dirname, 'attachments')))
Make sure that path.join(__dirname, 'attachments') points to the right directory (with a simple console.log) otherwise just adjust it.
Documentation: https://expressjs.com/en/starter/static-files.html

try to use express function sendFile
https://expressjs.com/en/api.html#res.sendFile
something like this
app.use('/attachments/:filename', (req, res) => {
const myIms = <path to directory>
const filePath = myIms + '/attachments/' + req.filename
res.sendFile(filePath, {
headers: {
'Content-Type': 'image/jpg'
}
})
})
or express download function https://expressjs.com/en/api.html#res.download

Related

Unable to use res.send and res.download in Node/Express due to headers already being set

I am new to Node, and I am trying to make it so that when I go to 'localhost:1337/download/open' it renders a webpage, as well as download a file.. I understand that you can only set a header once (that is the error I am getting), but what is the easiest way to both render html AND download a file? Code below:
const express = require('express');
const app = express();
app.get('/download/open', function (req, res) {
let file = `${__dirname}/downloads/Open Tasks.csv`;
res.download(file);
res.send("words");
})
app.listen(1337, function (err) {
if (err) {
console.log(err)
return
}
console.log(`App running. listening on: http://localhost:1337`);
});
Error:
Error: Can't set headers after they are sent.
Thank you in advance.
I was able to figure out what I was trying to do. Instead of trying to render a whole new page AND download a file, I needed to dedicate a route to just a download through the use of an <a></a> tag.
For instance, if I have a webpage at 'http://localhost:1337' that has a link on it like:
Download Open Tasks
Download Open Tasks
Then in node.js I have a route for 'download/open' like so:
app.get('/download/open', function (req, res) {
let file = `${__dirname}/downloads/Open Tasks.csv`;
res.download(file);
})
It will not open a new page (like I thought it needed to) it will just download the file.
IMO, I would suggest you should do the following to achieve your goal:
render the HTML result for "GET http://localhost:1337/download/open"
In the HTML file /download/open, put AJAX block to invoke download file operation
(Download a file by jQuery.Ajax)
$(document).ready(function(){
//code to invoke download file....
});

Best way to handle dynamic routes with its own logic in ExpressJS?

I've been tasked with something at work that's beyond my current skills so any help is appreciated.
I'm building an admin where you can add "games". Each game needs to have it's own front-end, routes, and logic.
Kinda like,
mainsite.com/game/game1
mainsite.com/game/game2
mainsite.com/game/game3
At the moment I'm just creating a directory based on the game name.
var dir = "./games/" + req.body.gameId;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
In turn I can pull the .ejs file via:
/* GET dynamic game page. */
router.get("/game/:game", function(req, res, next) {
res.render("../games/"+req.params.game+"/index", { title: "Express" });
});
But I am confused on how it can have it's own logic, routes, connecting to database, front-end, stylesheets inside it's own folder.
There must be a better way to achieve this right?
Cheers
Yes! In Express, you can call app.use() inside a route. You will be able to define a public folder to contain the CSS, JS, and assets that are specific to each route. Just call app.use(express.static('route/to/assets')) inside the route.
app.get('/game/:game', (req, res) => {
app.use(express.static(req.params.game + '/public'))
res.render('../games/' + req.params.game + "/index", { title: "Express" })
})
Seems strange, but perfectly allowed.

Serve a file in a browser from a runtime directory using nodejs

In our application we store our reports in a user defined folders. User can add their own folders during runtime. Iam showing the history of those files in a web page. on clicking the file name i should show the file from the folder. How can i show the files from a non public directory.Since its given during runtime i havent added them as static dir to the express server.
One idea we tried was to use node-static-server and create a file server with the folder and serve the file. for each file we create this. it works fine but i get an error saying "port already in use". is there any better idea to do this? is this the right approach?
You can do this in NodeJS using a express.static:
const FS = require('fs')
const express = require('express')
const bp = require('body-parser')
const app = express()
function fileTest(req, res, next){
if (/\.|\/|\\/.test(req.params.file))
return res.sendStatus(400)
return next();
}
app.get(
'/static/:file',
fileTest,
function(req, res, next){
req.url = req.url.replace('/static','')
next()
},
express.static(
'./static',
{
fallthrough: false
}
)
)
app.post(
'/static/:file',
fileTest,
bp.text(),
function (req, res) {
FS.writeFile(
'./static/'+req.params.file,
req.body,
function (err) {
if(err)
return res.sendStatus(500)
return res.sendStatus(200)
}
)
}
)
app.listen(
1337
)
This is a simple example showing a server that will:
[POST]
Take a text body and load it into memory( pitfall: large bodies in memory )
Based on the URL, save it as a file in the static folder
[GET]
Search for a file
If found return file
The good news is that you can make the file and then request the file without restarting the server. Bad news is that this is a VERY SLOW server( comparatively to other options ).
As with all examples no good practices were followed, so be sure to adapt it to your needs.
Things to think about as you adopt it:
How do I allow people to save files to other folders?
How do I disallow people from saving files to other folders I don't want them to?
PROPER AUTHORIZATION

sending multiple files down the pipe

we're using express 4 and right now I have something like this:
var express = require('express'),
router = express.Router();
router.get('/local_modules/*', function (req, res, next) {
var moduleName = req.url.match(/local_modules\/(.*?)\//).pop(1)
res.sendFile(filePath + '.js');
}
and I wanna do something more like:
router.get('/local_modules/*', function (req, res, next) {
var moduleDir = req.url.match(/local_modules\/(.*?)\//).pop(1)
fs.readdir(moduleDir, function(err, files) {
files.forEach(function(f) {
res.sendFile(path.join(moduleDir, f));
})
}
}
But that doesn't work. How can I serve multiple files with express? Note: not just all files in a directory (like in the example) - which probably can be done with app.use; express.static , but specific set of files (e.g. I may need to get the list of files from bower.json)
There is no way to send multiple files like that in a single response, unless you use your own special formatting (standard multipart or otherwise) and then parse that on the client side (e.g. via XHR).
Probably the easiest workaround would be to archive (zip, 7zip, tarball, etc.) the files and then serve that archive instead. This assumes however that you want the user to download it and not use the files in the browser (unless you have a zip, etc. parser in the browser and use XHR).

Expressjs file download - not downloading

I can't seem to get my "download file" feature working using Expressjs.
//DOWNLOAD FILE
router.get('/snippets/download', function (req, res) {
res.attachment("untitled.js");
res.send("here is some javascript");
});
If I access this route in my browser the file downloads to my computer but not if I use an Angularjs request to the route.
Am I missing something?
You can use res.download. Refer documentation here: http://expressjs.com/4x/api.html
Eg:
//DOWNLOAD FILE
router.post('/snippets/download', function (req, res) {
res.download(req.body.filename, req.body.text);
});
See if this helps.
The res.download() method need a file's full path ( which could be different in windows and linux with different seperator ).
And the 2nd param of res.download(localName, downloadPromptName ) should be able to modify the filename that user see (different from the file in your server's directory), but it seems that does not work in my environment.
So I recommend you to use res.sendFile(fullNameInServer ,options) where you can modify the downloaded filename in options.
var root = getDownloadRoot(req);
var options = {
root: getDownloadRoot(req),
headers: {
"content": "text/html;charset=utf-8",
"Content-Type": "application/octet-stream",
"Expires":"0",
"Cache-Control": "must-revalidate, post-check=0, pre-check=0",
"content-disposition": "attachment;filename=" + urlencode(downloadFilename)
}
};
res.sendFile( tempFileName ,options);
urlencode should be required to encode filename then you can use filename other than english.
before call download file , you need to write the file physically in a temp folder,
the getDownloadRoot() method give you the temp folder location in runtime which does not vary when you change the path to run the app.
here is the function getDownloadRoot()
function getDownloadRoot(req){
var path = require('path');
var sep = path.sep;
var parentPath = path.dirname(req.settings.views);
var ret = parentPath.concat(sep + tempFileFolder);
return ret;
}
For now I have no way other than using app.setting (that app is declared in app.js) to get the application folder during runtime. So I made a litte 'middleware' to transport the value with req object as following.
In app.js:
app.use(function(req, res, next) {
req.settings = app.settings;
next();
});
tempFileFolder is a folder that you can name it yourself.
sep is the folder seperator ( \ in windows and / in linux )
Also you need to watch the folder permission settings when running in linux.
This combination works perfectly in my environment (with angularjs)

Resources