How to change express router path without changing URL? - express

I am statically serving my site from one directory. I have one dynamic route for setting the URL parameter roomCode. I want all routes to this path to serve the root index page without changing the URL on the client (that way I can still use the roomCode in my JavaScript).
Here is what I currently have:
// direct rooms to the index page
app.use('/room/:roomCode([A-Z]{4})', (_, res) => {
res.sendFile(path.join(__dirname, 'dist/index.html'))
})
// serve from the dist build
app.use(express.static(path.join(__dirname, 'dist')))
Instead of manually sending the dist/index.html file, I would like to simply change the route path to / for the following middleware and let the static server send the file. Something like this:
// direct rooms to the index page
app.use('/room/:roomCode([A-Z]{4})', (_, res, next) => {
req.path = '/'
next()
})
// serve from the dist build
app.use(express.static(path.join(__dirname, 'dist')))
This way, when the static middleware is reached, it believes the path was /, so it will serve the index page at the root.
Is this possible?

To reroute a request, you must change req.originalUrl to the new route, and then send it to the router handler with app._router.handle(req, res, next).
// direct rooms to the index page
app.use('/room/:roomCode([A-Z]{4})', (req, res, next) => {
// this reroutes the request without a redirect
// so that the clients URL doesn't change
req.originalUrl = '/'
app._router.handle(req, res, next)
})
// serve from the dist build
app.use(express.static(path.join(__dirname, 'dist')))
The documentation for req.originalUrl is a little confusing. It says:
This property is much like req.url; however, it retains the original request URL, allowing you to rewrite req.url freely for internal routing purposes.
This sounds like if you change req.url, it will change how it is routed. However, it does not. It does allow you to change it and then manually check it in latter middleware. But the middleware will still be called based on the original URL. Therefore, we need to overwrite the original URL and send it back through the router.

Related

Express is serving files without going through route handlers?

I have the following code:
const app = express();
app.use(express.static(path.resolve('../frontend/dist')));
const server = http.createServer(app);
app.get('/', (req, res) => {
console.log('received request');
res.sendFile(path.resolve('../frontend/dist/index.html'));
});
If I comment out the app.get handler, index.html is served at localhost:3000 anyway, apparently due to the second line. The get handler is not actually executing - received request is never printed in the console.
But without the second line, it can't serve static assets and JS & CSS fail to load. What am I doing wrong here? Why is the static asset config causing the route to be ignored?
express.static() is a generic route handler. It tries to match incoming requests to specific files at a target location in your file system. One of the special features it has is that if it gets a request for / it will look for index.html in the target directory.
You can see the feature described in the doc and, in fact, there's an option { index: false} that can turn the feature off if you don't want it to serve index.html automatically.

Serving dynamic subdomain content from a subdirectory

I have a directory structure on my server like:
site-a/app/index.html
site-a/app/main.js
site-a/app/vendor.js
site-b/app/index.html
site-b/app/main.js
site-b/app/vendor.js
And so on.
I'd like to be able to create a server that can respond to these like:
curl http://site-a/app/
curl http://site-a/app/vendor.js
For the index.html file, I have something like this and it works:
this.app.use((req, res) => {
res.sendFile(path.join(process.env['SOURCE'], req.subdomains[req.subdomains.length - 1], '/app/index.html'));
});
But I can't for the life of me figure out how to get express.static to work in a situation like this.
Bonus Credit: Any request that fails should load /app/index.html. It's an Angular application.
Anyone have any pointers?
Perhaps this is what you are looking for?
app.use('/app', function(req, res, next) {
let host = req.get('host'); // for some reason, subdomains wasn't working on localhost
let site = host.includes('site-b') ? 'site-b' : 'site-a'; // conditional logic because ^^^
let files = path.join(process.env['SOURCE'], site, 'app'); // file path to /app folder for that site
express.static(files)(req, res, function() {
res.sendFile(path.join(files, 'index.html')) // fallback to index.html
})
});
express.static needs to be given the directory, not index.html. It'll automatically use index.html as the root.
I specify next function to express.static that, if it fails to find a file, just sends index.html anyway.
Let me know if you have any questions!
/app/site-a = http://site-a.localhost:port/app
/app/site-b = http://site-b.localhost:port/app

Express: serve static files on subroute

I'm trying to get routing work using Express and create-react-app.
My goal is to address the user to the homepage of the application when the URL is / and to the login page when the URL matches /login.
In my server.js I have two routes defined:
var mainRoutes = require("./routes/mainRoutes");
var apiRoutes = require("./routes/apiRoutes");
[...]
app.use("/", mainRoutes);
app.use("/api", apiRoutes);
While apiRoutes contains all the api routing definitions, mainRoutes is responsible for the main navigation (at least this was the idea):
var express = require("express");
var path = require("path");
let router = express.Router();
router.route("/").get((req, res, next) => {
res.sendFile("index.html", { root: "./client/build/" });
});
router.route("/login").get((req, res, next) => {
res.send("This is the login page");
});
module.exports = router;
Somewhere I read about serving the static asset generated by the building process of create-react-app so I added:
// Priority serve any static files.
app.use(express.static(path.join(__dirname, "client/build")));
// All remaining requests return the React app, so it can handle routing.
app.get("*", function(req, res) {
res.sendFile(path.join(__dirname + "/client/build/index.html"));
});
Adding these lines, I successfully see my index.html but I can't visit both /login and /apisubroutes since it redirect me on the main page (index.html) each time.
It's like I need to serve the static files on my subroute mainRoutes but I don't have an idea on how to do that.
How can I make this work?
app.get('*') would match every single route that you have.
You should do something like this:
var mainRoutes = require("./routes/mainRoutes");
var apiRoutes = require("./routes/apiRoutes");
[...]
app.use(express.static(path.join(__dirname, "client/build")));
app.use("/", mainRoutes);
app.use("/api", apiRoutes);
// If the app reaches this point, it means that
// the path did not match any of the ones above
app.use(function(req, res, next){
// redirect the user to where we serve the index.html
res.redirect('/');
});
create-react-app I believe handles routing different, you cannot hook up the browser's route to the route you want to serve because you're running a single page application", unless you do universal routing with server and the js bundle

How to redirect to prefixed path in Expressjs Router

I am deploying an app in a specific path say under /path.
// login form
router.get('/login', function (req, res) {
})
router.post('/login', function(req, res) {
if (success) {
res.redirect('/') // <<--- does not direct to /path
}
})
// install router under path
app.use('/path', router)
So I can navigate to http://.../path/login
and after logged in, I want to redirect the user to the / under /path.
I don't want to hardcode /path and only use relative path since router has already been mounted under path.
Is it possible? How to do that?
If you want to redirect a user to the mount path (in this case, /path) without hardcoding it, you can use req.baseUrl:
res.redirect(req.baseUrl + '/');
The + '/' is optional for nested routers, but it's necessary for the application-level router because it has an empty req.baseUrl. Alternatively (if you don't want to add a trailing slash), you can use this (if you also want it to work in the application-level router):
res.redirect(req.baseUrl || '/');

What is the difference between get/post/... and use

All:
I am new to Express 4 router.
When I tried some login/signup example, I got one question about the .use and .get/.post function:
I saw sometimes it uses:
var express = require('express');
var router = express.Router();
router.get('/hello', function(req, res, next) {
res.send("Welcome");
});
and in main app, we use it like:
app.use("/", router);
While some other time, it uses:
var express = require('express');
var router = express.Router();
//here the router uses .use() function rather than .get/.post
router.use('/hello', function(req, res, next) {
res.send("Welcome");
});
and in main app, we use it like:
app.use("/", router);
So I am wondering what is the difference between them, does the .use() just a general name for all of get/post/put/... together?
I find this post: Difference between app.use and app.get in express.js
But still not feel easy to understand this....
Thanks
In addition to what Jonathan Lonowski said in the posted link, it might help to not compare use to get and post, but to compare it to all because both all and use work regardless of the HTTP verb used while that's obviously not true for get. Everything I'm about to say applies if you replace "all" with "get", it'll just narrow that handler down to a specific HTTP verb.
So, what's the difference between all and use?
app.all will handle incoming requests at the specified URL path regardless of the HTTP verb, just as app.use does. However, how it compares the requested URL to the handler is different. For example:
var r = express.Router();
r.use('/foo', function (...) { ... }); // Route A
r.all('/bar', function (...) { ... }); // Route B
If you make a request to /foo/123 Route A will be run.
If you make a request, however, to /bar/123 Route B will NOT be run.
This is because with HTTP verbs express compares the full path, but with 'use' it only cares about the beginning of the url. Because the URL /foo/123 begins with /foo Route A will run, but because /bar/123 does not match the FULL URL, Route B will not be. Note: You could make .all behave in the same way: r.all('/bar/*', ...), but use is easier and more appropriate for this.
So, what you would tend to mount with one vs the other is different. For example:
var app = express();
var router1 = express.Router();
var router2 = express.Router();
router2.all('*', function (req, res) { ... }); // Must specify a path!
router1.use('/secondary-routes', router2); // Can't do this with all.
app.use(router1); // Look Ma, no path!
Here I've used all to handle a request coming in, where I've used use to mount an entire router. Also, note that the usage of router.METHOD functions require a URL string as the first parameter, while use does not.
At the end of the day, if you:
Want all requests that come in under a given path (or even every request) to use the specified middleware, or
Want to mount an entire sub router/application, or
Want to include a plugin into your application
... Then use is probably what you want.
If you:
Are handling a specific request at a specific URL path (i.e. probably not doing a * match in the URL)
Generally won't be calling next and will instead actually be handling the request
... Then an HTTP verb method (like get, post or all) is probably what you want.
.use is used in 2 cases, middlewares and "modular mountable route handlers".
In your example
router.use('/hello', function(req, res, next) {
res.send("Welcome");
});
This means that any requests sent to /hello will be terminated with "Welcome" and the actual .get attached to /hello will not be called.
So, in short, call use when you need to apply some general middlewares or want to do modular architecture with routers. use can be "used" as request handlers, but you shouldn't because it is not designed for that purpose