Modern Node.js
Switching from CommonJS to ES Modules in Node.js, and handling server-side redirects.
Module Systems in Node.js
Enabling ES Modules
Up until now, we have used CommonJS for writing scripts and imports, meaning that if we wanted to share code
between files, they would have to all be imported
as scripts in the html files.
However, if we add "type": "module" to our package.json, it enables ES Modules syntax in Node.js.
{
"type": "module",
"dependencies": {
"express": "5.2.1"
}
}
Now we can export from one file to anothers and share code between the two without having to use a html file between them.
CommonJS
CommonJS Exporting
In common JS we use require() and module.exports to export. This is no longer the
recommended
approach, but it was default for Node.js for many years
Exporting:
const cookie = require('./cookie.json');
function getCookie() {
return cookie;
}
module.exports = { getCookie };CommonJS Importing
Importing:
const cookiesUtil =
require('./util/commonjsCookiesUtil.js');
This demonstration of CommonJS was purely intended as a educational purpose. Going forward, we will use ES Modules
ES Modules
ES Modules Exporting
ES Module use export and import key words, which are also supported natively in
browsers,
that means we can use the same syntax on the client and the server. This is already a way cleaner approach, since
we can use
the same syntax all across our codebase.
Exporting:
// Named export, can have multiple per file
export function esModuleCookieFactory() {
return 'On a break';
}
// Default export, only one per file
export default {
esModuleCookieFactory
};ES Modules Importing
Important rule: Always keep the .js extenstion in ES Module import paths, Node.js
does not resolve the extension
automatically.
Importing:
// Named import (curly braces,
// can define more than one)
import { esModuleCookieFactory }
from './util/esModuleCookiesUtil.js';
// Default import (no curly braces,
// for single imports)
import cookiesUtil
from './util/esModuleCookiesUtil.js';
Always use the .js extension in ES Module imports.
Named vs Default Exports
Like we touched upon earlier, it is not possible to have more than one default exports per script, but we can have many named exports.
Named exports are generally preferred, since you always know exactly what you are importing.
Named exports (recommended):
// Export multiple
export function func1() { }
export const value = 42;
// Import specific
import { func1, value }
from './module.js';
Default export:
// One per file
export default function() { }
// Import without braces
import myFunction from './module.js';
Client-Side Module Loading
Traditional Script Loading
Regular scripts share global scope with every other script in the page, meaning variables can be passed from one file to another and accidentally overwrite another. Load order also needs to be managed manually.
<!-- Scripts share global scope -->
<script src="./common.js"></script>
<script src="./cookieFactory.js"></script>Module Script Loading
With type="module", every script has its own scope and can share code. The browser handles load
order automatically:
<!-- ES Module - isolated scope -->
<script type="module"
src="./cookies.js">
</script>Static File Management
The Problem
When the browser renders a page, it will also request the affiliated CSS, JavaScript and image files.
But without explicit permission from the server, the requests will always return a 404, so we need to tell Express which folder it should serve these files from.
Express Static Middleware
express.static() is a built-in Express middleware that serves any file inside the
specified folder automatically. In this case we named it 'public' and that becomes
directly accessible from the browser.
import express from 'express';
const app = express();
// Serve files from 'public' directory
app.use(express.static('public'));Serving HTML Pages
For specific page routes, we use res.sendFile(). This requires an absolute path,
which is not very useful. We can then use path.resolve() to construct a relative path instead.
Path Resolution in ES Modules
In the earlier weeks, we used __dirname which is a CommonJS feature and
will not work in ES modules projects. path.resolve() is the replacement.
import path from 'path';
app.get('/', (req, res) => {
res.sendFile(path.resolve(
'public/frontpage/frontpage.html'
));
});
app.get('/cookieFactory', (req, res) => {
res.sendFile(path.resolve(
'public/cookieFactory/cookieFactory.html'
));
});Project Organization
Client vs Server Separation
When working on full stack projects, clean separation between the client and server code makes
the project easier to maintain and navigate. The public/ folder belongs to the browser,
everything else belongs to the server.
Recommended Folder Structure
project/
├── public/ # client
│ ├── frontpage/
│ │ ├── frontpage.html
│ │ └── frontpage.css
│ └── cookieFactory/
├── util/ # server
│ └── esModuleCookiesUtil.js
├── package.json # server
└── app.js # server
HTTP Redirection Techniques
When to Use Redirects
We can redirect the user from the browser from one page to another. A common usecase for this, is redirecting after a form submission. For example, sending the user to a success page after a short delay.
Client-Side Redirection
JavaScript Timed Redirect
Timed redirect with JavaScript:
setTimeout(() => {
window.location.href = '/success';
}, 2000);HTML Meta Redirection
Or with a meta tag:
<meta http-equiv="refresh"content="3;url=/success">Server-Side Redirection
When the redirect should happen on the server, for example to forward from an old URL
to a new one, we use res.redirect(). The server sends back a 302 response,
and the browser follows it automatically.
app.get('/old-page', (req, res) => {
res.redirect('/new-page');
});