With Node.js, Express and Mongoose, I am serving several static files synchronously within multiple sub-directories with the following code (which works fine):
fs.readdirSync(__dirname + "/../../../np-site/themes/default/client/")
.forEach(function (dir) {
app.use(express.static(__dirname +
"/../../../np-site/themes/default/client/" + dir)
);
});
However, part of the url must be dynamic depending on a database entry value:
express.static(__dirname + "/../../../np-site/themes/" + theme + "/client/" + dir)
I have tried several different ways, to no avail. The following is the first attempt I made, which made the most sense to me at the time (the App model is a single object that can only be updated and retrieved):
App.find(function (err, appSettings) {
fs.readdirSync(__dirname + "/../../../np-site/themes/" + appSettings[0].theme +
"/client/").forEach(function (dir) {
app.use(express.static(__dirname + "/../../../np-site/themes/" +
appSettings[0].theme + "/client/" + dir)
);
});
});
This however does not work, and no static files are served as a result.
The object that I need to access is available (discovered via console.log), but something is not right.
The browser console does return an infrequent error message stating the MIME type of the files (CSS) is not correct, but I believe this is because the app cannot find correct directories (removing the method altogether has the same result).
I'm thinking that it has something to do with app.use within a Mongoose method, but I am not certain.
Could someone please shed some light towards this frustrated soul? I feel as though I am on the wrong path.
The problem is that you're adding your middleware asynchronously (because App.find() is most likely performing an asynchronous action), which causes your dynamic middleware to get added (probably) after all of your statically defined middleware and route handlers.
Express executes middleware and route handlers in the order that they are added.
The solution would be to delay adding any other middleware or route handlers until after your fs.readdirSync() (but inside your App.find() callback). A simple way to do this is to simply put your other middleware and route handler additions inside a function that you call after your fs.readdirSync():
var baseThemePath = __dirname + "/../../../np-site/themes/";
App.find(function (err, appSettings) {
var themePath = baseThemePath + appSettings[0].theme + "/client/";
fs.readdirSync(themePath).forEach(function(dir) {
app.use(express.static(themePath + dir));
});
setupStatic();
});
function setupStatic() {
app.use(express.static('public'));
app.get('/', function(req, res) {
res.send('Hello world');
});
// ...
}
Related
This is kind of hard for me to explain but I am trying to shorten my code by creating one link instead of 50 for downloading a form. I will try to explain this a bit better using my code.
I have 5 get requests that do exactly the same thing but download a different file.
router.get('/form1', function (req, res) {
var file = __dirname + '/../public/forms/form1.pdf';
res.download(file);
});
router.get('/form2', function (req, res) {
var file = __dirname + '/../public/forms/form2.pdf';
res.download(file);
});
etc...
and my front-end link are;
FORM 1
FORM 2
etc...
Is there anything I can do to make this a more dynamic? The only way I can think of is something like this;
router.get('/:formName', function (req, res) {
// some how do a "req.params.formName"
var file = __dirname + '/../public/forms/' + req.params.formName + '.pdf';
res.download(file); // Set disposition and send it.
});
But I don't know how I will get the formName or if thats even possible.
Here are some more options to clarify:
Option 1: If you have a folder on the server with a fairly manageable directory structure, simply use express.static to map the physical folder to a virtual one with automatic download:
app.use('/download', express.static(path.join(__dirname, '/public/forms/')))
This will result in any link from the front-end with href='/download/something.pdf' working as long as that file is on the server in the path you mapped (i.e. in /public/forms).
Option 2 (which David E above answered in essence): In your original code, if you wanted to generate a path handler for a link that looks like /download/form1, /download/form2, it's a very minor modification:
router.get('/download/:formNumber', function (req, res) {
var file = __dirname + '/public/forms/' + req.params.formNumber + '.pdf';
res.download(file);
});
Option 3: You want to authenticate access to the files and potentially support multiple, complex URL schemes to a single handler that can lookup the appropriate physical path and send the file:
router.get('/download/:path[forms|images|files]/:formNumber1', fileRequestHandler);
router.get('/public/downloadFolder2/:formNumber2', fileRequestHandler);
router.get('/public/downloadFolder3/:formNumber3', fileRequestHandler);
function fileRequestHandler(req, res) {
// Check authentication here - example below from Passport
if(!req.isAuthenticated())
return res.status(401).send({err: 'Unauthorized'});
// Check which form number is supplied and map to appropriate physical file
if(req.params.formNumber1) {
// in this example, req.params.path can be one of three allowed sample values - forms or images or files
var file = __dirname + '/public/' + req.params.path + '/' + req.params.formNumber + '.pdf';
res.download(file);
} else if(req.params.formNumber2) {
// etc.
}
}
Note: Ezra Chang's point about path validity is important.
This response assumes that your route lives in index.js.
router.get('/form/:formName', (req, res, next) => {
res.download(`${__dirname}/../public/forms/${req.params.formName}.pdf`);
});
FORM 2
Be careful about your path. I don't know whether you can start at a directory, go up a level, then down again.
I'm attempting to validate that my client can post info to it's server. I've set up this 'route' in my Express server.
// server.js this is the server for the PvdEnroll application.
//
var express = require("express"),
http = require("http"),
mongoose = require( "mongoose" ),
app = express();
// static file directory for default routing
app.use(express.static(__dirname + "/client"));
app.use(express.urlencoded());
// create Express-powered HTTP server
http.createServer(app).listen(3000);
console.log("Server listening at http://127.0.0.1:3000/");
// set up a post route in the server
app.post("/selections", function (req, res) {
console.log("data has been posted to the server!");
});
app.post("/selections", function (req, res) {
console.log("Some data has been posted to the server from app.js");
})
The client uses this file:
var main = function () {
"use strict";
$.getJSON("../data/checkBoxesA.json", function(checkBoxTxt) {
checkBoxTxt.forEach(function (data) {
var $checkbox = "<input type ='checkbox' name = "
+ data.label + "id = 0 UNCHECKED/>";
$(".enroll_actions").append($checkbox);
$(".enroll_actions").append(' ' + data.label + "<br/>");
$(".enroll_actions").append(' ' + data.note + "<br/>");
$(".enroll_actions").append(' '+ "<br/>");
});
});
$(".comment-input").on("click", function (event) {
console.log("Hello World!");
// here we'll do a post to our selections route
$.post("selections", {}, function (response) {
console.log("Client says - We posted and the server responded!");
console.log("Response from server :", response);
console.log("STUBB1");
});
});
console.log("STUBB2");
};
$(document).ready(main);
In the Chrome console I'm getting:
POST file:///Users/*******/Projects/r_PE/app/PvdEnroll/client/html/selections net::ERR_FILE_NOT_FOUND
A path is being sought but a tutorial's example (which works!) and is structurally identical to mine(?) uses a name i.e. "selections" to establish a route between client and server.
I'm running the server on my Mac using Virtual Box and Vagrant.
Thanks for any clarification.
On restarting the server the log message is now "POST 127.0.0.1:3000/html/selections 404 (Not Found).
Okay. This is some helpful information!
Basically, your file structure is more or less this (some file names will be different, just look at the general structure):
Node.js code (including main .js file and the module.json)
client: a folder for your static content
html: a folder
index.html: the file you are currently using
Anyway, jQuery.post() is fed a relative path (as apposed to an absolute path). That means that, based on the location of where the code was, it will "guess" the location of the file.
That means that it was trying to find a route at [locahost:port]/html/selections when your server is listening at [localhost:port]/selections! You'll need to have jQuery post to /selections instead of just selections.
For more information, this blog post is helpful (relative paths work the same in Javascript/CSS). The helpful snippet:
Here is all you need to know about relative file paths:
Starting with "/" returns to the root directory and starts there
Starting with "../" moves one directory backwards and starts there
Starting with "../../" moves two directories backwards and starts there (and so on...)
To move forward, just start with the first subdirectory and keep moving forward
I have an express.js app, and I am using drywall in order to manage the user system.
When a user signs up, I want a directory to be generated for that user, and I want that user to be able to upload files to that directory and view those files through his or her account.
I am not entirely sure, but I think that most likely for the directory generation I will have to do that inside views/signup/index.js, and that the user can only upload files to his or her directory if logged in.
However, I'm a bit stuck when it comes to saving and displaying the files. I have little experience with server side code, so implementing actions such as accessing files is slightly beyond my scope.
Thanks in advance to those who help.
So first you should create a folder for each user by using fs.mkdir:
http://nodejs.org/api/fs.html#fs_fs_mkdir_path_mode_callback
Let's say you want to create these folders into your app root / images:
Example:
var fs = require('fs');
fs.mkdir(__dirname + '/images/' + userId, function(err) {
if (err) {
/* log err etc */
} else {
/* folder creation succeeded */
}
});
You should probably use the userId for the folder name (since it's easier than trying to strip out the bad characters from the username itself, and this will also work in the future if the user changes his username).
The second thing you need to do is to allow the user to upload files (but only if he is logged in and into the right folder). It's better to not include the bodyParser middleware for all routes, but instead include the json && urlencoded middleware for all routes (http://www.senchalabs.org/connect/json.html && http://www.senchalabs.org/connect/urlencoded.html) and the multipart middleware only for the upload url ( http://www.senchalabs.org/connect/multipart.html && example: https://github.com/visionmedia/express/blob/master/examples/multipart/index.js ).
An example:
app.post('/images', express.multipart({ uploadDir: '/tmp/uploads' }), function(req, res, next) {
// at this point the file has been saved to the tmp path and we need to move
// it to the user's folder
fs.rename(req.files.image.path, __dirname + '/images/' + req.userId + '/' + req.files.image.name, function(err) {
if (err) return next(err);
res.send('Upload successful');
});
});
Note: in the example above I've taken into consideration that req.userId is populated with the id of the user by an auth middleware.
Showing the images to the user if he has the rights to see them (the auth middleware should be applied for this path as well):
app.get('/images/:user/:file', function(req, res, next) {
var filePath = __dirname + '/images/' + req.userId + '/' + req.params.file;
fs.exists(filePath, function(exists) {
if (!exists) { return res.status(404).send('Not Found'); }
// didn't attach 'error' handler here, but you should do that with streams always
fs.createReadStream(filePath).pipe(res);
});
});
Note: in production you might want to use send instead, that example was just demoing the flow ( https://github.com/visionmedia/send ).
I'm fairly new to Node.js and am having trouble understanding the way to go about loading libraries or files, in runtime.
Apparently, it is a bad idea to load files in runtime using Node.js's native "require" function because it is blocking i/o, and therefore should not be used from within request handlers. So, I'm assuming something like this is to be avoided:
var http = require('http').createServer(function(req, res) {
var file = require('./pages/'+req.url);
res.end();
}).listen(8080);
So then is there a way to require files in runtime, in a non-blocking/asynchronous way?
I don't think it would always be possible to load files in "boot time" rather than runtime because like in the example above, the only way to know what file to load/require is by getting the name through the req.url property.
So that seems like the only option. Unless, all the files in the folder are preloaded and then called upon by name, in the callback (By using fs.readdirSync or something to iterate through all the files in the folder and compare the gotten files' names to the req.url property), but that seems wasteful. "Preloading" all the files in the folder (maybe around 50 files) and then only using 1 of them, doesn't seem like a good idea. Am I wrong?
Either way, I would just like to know if there is a way to require files in runtime in a better, non-blocking/asynchronous way.
Thank you!
The function require() is generally used for caching modules or configuration files before most of your application runs. You can think of using require() somewhat like this:
var file = fs.readFileSync('/path');
// depending on the file type
eval(file);
JSON.parse(file);
The reason it is done this way is so that dependencies are loaded in order. If you want to read a file after initializing the application, you should use a asynchronous read, and respond in the callback like this:
var http = require('http');
http.createServer(function(req, res) {
fs.readFile('./pages/' + req.url, function(err, data) {
res.end(data);
});
}).listen(8080);
If you needed to check if a file existed, then you could use fs.stat() to check the existence of a file, rather than querying the directory.
var http = require('http');
http.createServer(function(req, res) {
var file = './pages/' + req.url;
fs.stat(file, (err, stats) {
if (err || !stats.isFile()) {
res.writeHead(404);
res.send();
return;
}
fs.readFile(file, function(err, data) {
res.writeHead(200);
res.end(data);
});
});
}).listen(8080);
I'm creating a simple testing platform for an app and have the following code setup as my server.js file in the root of my app:
var restify = require('restify'),
nstatic = require('node-static'),
fs = require('fs'),
data = __dirname + '/data.json',
server = restify.createServer();
// Serve static files
var file = new nstatic.Server('');
server.get(/^\/.*/, function(req, res, next) {
file.serve(req, res, next);
});
// Process GET
server.get('/api/:id', function(req, res) {
// NEVER FIRES
});
It serves static files perfectly, however, when I try to make a call to the /api it just hangs and times out. Imagine I'm missing something stupid here, any help would be greatly appreciated.
node-static is calling next with an error, which means it's never yielding to other handlers.
You can move your other handlers above node-static or ignore it's errors by intercepting it's callback.
I made a working version here: http://runnable.com/UWXHRONG7r1zAADe
You may make yourself sure the api get call is caught by moving the second get before the first. The reason is your api calls routes are already matched by the first pattern.