setting up a lot of routes in node.js - javascript

I've created a webpage to use it locally as a way to save information about random topics and college stuff. I have a ton of routes like the ones shown below:
//app.js - using node and express
app.get('/page1', function(req, res){
res.render('page1');
});
app.get('/page2', function(req, res){
res.sendFile('views/page2.html', { root: __dirname });
});
Each one of these routes has a .ejs file or a .html file, and they are all quite small.
I don't think I've made any major mistakes, but I gotta a feeling I'm not using "best practices", or at least could be doing some differently.
Is there anything wrong with using a lot a routes like this? Should change something to optimize the rendering of my pages?
As I said before, I'm using .ejs on most of the pages, and most of them have the same header.ejs and footer.ejs. Every time I change pages that have the same header/footer, do they get loaded again, or since they are using the same header/footer files the server only requests the content in between?
what's the difference between using res.render and res.send?
Should I be using a different syntax: const express = require('express'); & const app = express(); instead of var express = require('express'); & var app = express();
``` or this
app.get('/page1', (req, res) => {
res.render('page1');
});
```
Instead of the first block of code above.
Thanks! :D

Is there anything wrong with using a lot a routes like this? Should change something to optimize the rendering of my pages?
Nothing technically wrong with it. Many plain res.sendFile() routes can probably be replaced with a single express.static() middleware statement to simplify your code.
Lots of res.render() routes that don't pass any customized data to EJS can also probably be replaced by a single middleware that handles either a whole directory of template files (and their corresponding routes) or a list of files. That would be a lot more DRY than spelling out each route separately.
As I said before, I'm using .ejs on most of the pages, and most of them have the same header.ejs and footer.ejs. Every time I change pages that have the same header/footer, do they get loaded again, or since they are using the same header/footer files the server only requests the content in between?
EJS caches templates in memory (unless you disable caching) so the header and footer templates won't get loaded over and over again from disk.
what's the difference between using res.render and res.send?
These are fully covered in the documentation. In a nutshell, res.render() supports your template engine and local data to feed to the template engine to allow it to add data to the template. res.send() just sends the raw content you give it.
Should I be using a different syntax: const express = require('express'); & const app = express(); instead of var express = require('express'); & var app = express();
It is considered a good practice to use const whenever the variable you are declaring should get its initial value and not be assigned to again. In addition to some coding safety, this also can sometimes allow the interpreter to do more optimizations when using the variable (since its value can't be changed).

Related

Is there a way to serve files to browser and data to rest requests using the same endpoint?

I've followed this 7 hour tutorial on creating a blog almost to. There is one step that I skipped, because I did not want to use the separate client/server hosting that the tutorial suggests. Instead, I have a single heroku server that serves the client out of the public server:
const app = express();
app.use(express.static('public'))
app.use('/posts', postRoutes);
app.use('/user', userRoutes)
You can see that the app also serves some rest requests to the /posts and /user paths. However, the tutorial also led me to add these paths into the url client-side.
For example, you can access my app at (https://blog-tutorial-888.herokuapp.com), but you will be immediately "redirected" to (https://blog-tutorial-888.herokuapp.com/posts).
I say "redirected" because on the client side, it appears that you are at that site, and the purpose is so that you can do things like navigate to the next page, which will be at (https://blog-tutorial-888.herokuapp.com/posts?page=2).
But if you were to actually go to these links, or refresh the page, you will be hit with the result of the rest request, which is a giant block of text (and this is obviously because I have app.use('/posts', postRoutes)).
Is there a way to get around this somehow? Somehow serve both the html and the rest request at this endpoint?
To have a single server that severs the front and data trough a REST API, you would need to differentiate the paths, for example by adding an /api to urls for getting data, like so:
app.use('/api/posts', postRoutes);
app.use('/api/user', userRoutes);
And then below all your /api handlers add following lines so that for every other request you send that HTML that would load React bundle:
app.get("/*", (req, res) => {
// Make sure it's the correct path to the build folder
res.sendFile(path.join(__dirname, "../client/build/index.html"));
});
Of course don't forgot to add /api in your front end query urls as well, not the routes setup:
fetch("/api/posts")
// or
axios.get("/api/posts")

How do I setInterval an Express-app?

Been googling for a bit, trying out stuff and coming up with all sorts of weird solutions to my problem without really getting anywhere. My question is, how would I setInterval my express.js-application to let it run once every 30s?
The reasoning for this is that I have a bunch of variables that change every ~30s and I need to re-render the page in order for these to show up. The "solutions" that I have come up with hits the EADDRINUSE error since I am trying to create a new server on the same port as the old one, so I understand that there is something that needs to be done here to avoid that, but what?
If anyone wants to know what I have tried, I've tried process.exit (and let PM2 restart my app), setInterval, purposely crashed my app to let PM2 restart and even begun rewriting everything to let web sockets run back and forth between client and server, but I feel like this is over-complicating (maybe) the issue, as a standard setInterval would solve it all, if I just understood how.
Below is my express-script:
var express = require('express');
var app = express();
var ejs = require('ejs');
var config = require('./config');
app.set('view engine', 'html');
app.engine('html', ejs.renderFile);
app.get("/", function(req, res){
app.use(express.static(__dirname + '/views'));
async(function(variables) {
res.render('index',
{
// stuff to render
});
});
function async(callback) {
// several variables...
callback(variables);
}
});
app.listen(config.port, "0.0.0.0");
Things you want to google for are: comet, long-pulling, web sockets, event sourcing.
Basically this is how I would work on this task:
Have a global state in your web server, simple setInterval to update it and setInterval to constantly do AJAX call to the web page.
Use web sockets and instead of setInterval(AJAX) push new state to the client.
Second approach is faster and preferable, but may require fallback if you need to support older browsers.
Are you sure this is the correct approach? Doesn't seem the correct one to me. I wouldn't do it.
You have to set the refreshing function on the client using some JS script(to auto refresh the page or update the values using AJAX/WS) or the equivalent meta tag.

expressJS router planning - how to set up an effective router

I realize that this may be a fairly simple question but bear with me. I am really new to node/express.
My directory structure looks like:
auth
index.html
pub
index.html
login.html
the idea here is that the resources in pub are publicly available but the resources in auth are only available after a user is authenticated.
However, at this point, I am just trying to get these pages to come back properly from the server. Ideally, my routing engine would be able to serve these pages up based on some parameter. So:
site.com -> pub/index.html
site.com/login/ -> pub/login.html
site.com/dashboard/ -> auth/index.html
I tried something like this:
router.get('/dashboard/', function(req, res, next) {
res.sendFile(__dirname + "/src/auth/index.html");
});
router.get('/login/', function(req, res, next) {
res.sendFile(__dirname + "/src/pub/login.html");
});
router.get('*', function(req, res, next) {
res.sendFile(__dirname + "/src/pub/index.html");
});
However, the problem I quickly found was that these pages are requesting resources relative to their own position in the directory structure and all requests were being returned the default index.html. So, for example if I type site.com in the browser index.html loads. Here is part of index.html:
<script src="js/jquery.min.js"></script>
naturally then, the browser makes another request for /js/jquery.min.js which the router can't find so it responds with index.html again.
How do I design a routing engine that is smart enough to find the correct view based on the url and then understand that it needs to serve all requests from that page relative to that pages position in the directory structure? Or is there another standard way of handling this kind of problem?
To complicate matters, the auth/index.html is an angular page. So, once it loads it will be requesting all kinds of html pages relative to its position in the directory structure (depending on routes and included templates etc.)
Thanks in advance.
Those are a lot of questions but I think I can at least get you pointed in the right direction :)
However, at this point, I am just trying to get these pages to come back properly from the server.
To do this with express, you can use express.static to designate a public directory whose assets get made available to web requests. For example, if you had a directory sturcture like this:
public/
templates/
index.html
stylesheets/
js/
jquery.min.js
In express, you would do this:
app.use(express.static(__dirname + '/public'));
in order to expose those files as static assets, relative to the public dir, eg http://yourserver.com/templates/index.html
To complicate matters, the auth/index.html is an angular page. So, once it loads it will be requesting all kinds of html pages relative to its position in the directory structure
I think part of your confusion here is knowing the difference between client side routing and server side routing in an AngularJS/node.js app.
AngularJS is a framework for building single page apps (SPA). What this means is your browser requests one HTML file at the start (eg an index.html served from the route '/' on your server) to get things started, which loads some bootstraping javascript. From then on, client side javascript and AJAX calls will handle all of the rest to facilitate rendering additional HTML, user interaction, and navigation to other parts of your app. The URL in the browser will change, but you'll notice that no further page reloads will take place as you navigate. This is the client side routing that you can use AngularJS to build. If you've looked at the AngularJS tutorial, step 7 goes over how this works.
Your server side routes are typically not involved in this page navigation. Instead, your server should provide an API for the AngularJS client side will mae AJAX calls to for creating, reading, updating, deleting (CRUD) application data. For login for example, you could have a server side /api/login route that doesn't return an HTML page, but rather accepts a username and password via a POST request, establishes some session state, and then returns the result to be dealt with on the client side.
In addition to the AngularJS tutorials, I would invite you to take a look at mean.js for an end to end example of what a node.js + angularJS app looks like.

AngularJS - Best Way to Handle State on Manually Entered URLs

I currently have a set-up based on the meanjs stack boilerplate where I can have users logged in this state of being 'logged-in' stays as I navigate the URLs of the site. This is due to holding the user object in a Service which becomes globally available.
However this only works if I navigate from my base root, i.e. from '/' and by navigation only within my app.
If I manually enter a URL such as '/page1' it loses the global user object, however if I go to my root homepage and navigate to '/page1' via the site. Then it's fine, it sees the global user object in the Service object.
So I guess this happens due to the full page refresh which loses the global value where is navigating via the site does not do a refresh so you keep all your variables.
Some things to note:
I have enabled HTML5Mode, using prefix of '!'.
I use UI-Router
I use a tag with '/'
I have a re-write rule on express that after loading all my routes, I have one last route that takes all '/*' to and sends back the root index.html file, as that is where the angularjs stuff is.
I'm just wondering what people generally do here? Do they revert the standard cookies and local storage solutions? I'm fairly new to angular so I am guessing there are libraries out there for this.
I just would like to know what the recommended way to deal with this or what the majority do, just so I am aligned in the right way and angular way I suppose.
Update:
If I manually navigate to another URL on my site via the address bar, I lose my user state, however if I manually go back to my root via the address bar, my user state is seen again, so it is not simply about loosing state on window refresh. So it seems it is related to code running on root URL.
I have an express re-write that manually entered URLs (due to HTML5 Location Mode) should return the index.html first as it contains the AngularJs files and then the UI-Route takes over and routes it properly.
So I would have expected that any code on the root would have executed anyway, so it should be similar to navigating via the site or typing in the address bar. I must be missing something about Angular that has this effect.
Update 2
Right so more investigation lead me to this:
<script type="text/javascript">
var user = {{ user | json | safe }};
</script>
Which is a server side code for index.html, I guess this is not run when refreshing the page to a new page via a manual URL.
Using the hash bang mode, it works, which is because with hash bang mode, even I type a URL in the browser, it does not cause a refresh, where as using HTML5 Mode, it does refresh. So right now the solution I can think of is using sessionStorage.
Unless there better alternatives?
Update 3:
It seems the best way to handle this when using HTML5Mode is that you just have to have a re-write on the express server and few other things.
I think you have it right, but you may want to look at all the routes that your app may need and just consider some basic structure (api, user, session, partials etc). It just seems like one of those issues where it's as complicated as you want to let it become.
As far as the best practice you can follow the angular-fullstack-generator or the meanio project.
What you are doing looks closest to the mean.io mostly because they also use the ui-router, although they seem to have kept the hashbang and it looks like of more of an SEO friendly with some independant SPA page(s) capability.
You can probably install it and find the code before I explained it here so -
npm install -g meanio
mean init name
cd [name] && npm install
The angular-fullstack looks like this which is a good example of a more typical routing:
// Server API Routes
app.route('/api/awesomeThings')
.get(api.awesomeThings);
app.route('/api/users')
.post(users.create)
.put(users.changePassword);
app.route('/api/users/me')
.get(users.me);
app.route('/api/users/:id')
.get(users.show);
app.route('/api/session')
.post(session.login)
.delete(session.logout);
// All undefined api routes should return a 404
app.route('/api/*')
.get(function(req, res) {
res.send(404);
});
// All other routes to use Angular routing in app/scripts/app.js
app.route('/partials/*')
.get(index.partials);
app.route('/*')
.get( middleware.setUserCookie, index.index);
The partials are then found with some regex for simplicity and delivered without rendering like:
var path = require('path');
exports.partials = function(req, res) {
var stripped = req.url.split('.')[0];
var requestedView = path.join('./', stripped);
res.render(requestedView, function(err, html) {
if(err) {
console.log("Error rendering partial '" + requestedView + "'\n", err);
res.status(404);
res.send(404);
} else {
res.send(html);
}
});
};
And the index is rendered:
exports.index = function(req, res) {
res.render('index');
};
In the end I did have quite a bit of trouble but managed to get it to work by doing few things that can be broken down in to steps, which apply to those who are using HTML5Mode.
1) After enabling HTML5Mode in Angular, set a re-write on your server so that it sends back your index.html that contains the Angular src js files. Note, this re-write should be at the end after your static files and normal server routes (e.g. after your REST API routes).
2) Make sure that angular routes are not the same as your server routes. So if you have a front-end state /user/account, then do not have a server route /user/account otherwise it will not get called, change your server-side route to something like /api/v1/server/route.
3) For all anchor tags in your front-end that are meant to trigger a direct call to the server without having to go through Angular state/route, make sure you add a 'target=_self'.

Pass server-side data into Angular

i'm currently trying to display the name of the logged-in user in my MEAN app. I'm using the EJS templating engine and I'm able to get the username showing by putting <%- user.username %> in my markup. The problem with this is that I don't really want to my mixing angular and embedded scripts in the same files, I'd like to pass the server-side data into Angular. I've tried ng-init but I'm not having any success at all with it.
I will assume you are using ExpressJS 4 it will be a little different in version 3, however the approach will be the same for both versions:
Node/Sever side
var express = require('express');
var bodyParser = require('body-parser');
var schema = require("./schemas");
var app = express();
var router = express.Router();
router.get('api/account', function(req, res){
res.json({ "login": req.session.user.login }); /*access the session account not sure if this matches your login location you will adapt it to your own*/
});
Angular/Client Side
You then invoke from your controller or service the url api/account with a GET request to receive the JSON like:
$http.get("/api/account").then(function(data){ console.log(data); });
then you could do something like:
$scope.login = data.login;
and in the html
{{login}}
From what I understand of Angular, it discourages server-side setting of templated values in HTML code. Consider setting up a JSON service that provides the data, and fetching the data over JSON. There is an example at the end of the Angular Tutorial that may make this clearer.
I do the exact same thing and mix EJS to embed things like my server info (to initiate a socket connection from the client). If you wanted to keep this purely client-side, you would need to have AngularJS fetch the server-side data using a service / factory - and then affect any views you have depending on said data.
ng-init only applies to things which live inside the "Angular" scope.

Categories