Creating browser-facing applications with Node.JS gets tedious. Express.JS is a JavaScript framework dedicated to single-page and multi-page applications hosted in the Node.JS environment.
It provides a thin layer of fundamental web app features that won't obscure the Node.JS features you're already familiar with, so you can be sure that your finished app will be up to scratch performance-wise. And it's also great for creating robust APIs, thanks to a stack of HTTP utility methods and middleware ready to use.
If you want a less complex option, you can create a site without the coding with a website builder. Either way, make sure you get your web hosting service right. Want to get started? Here's what you need to know about Express.JS.
01. Generate a workable structure
Express.JS prides itself in being 'unopinionated' – that is, the framework allows the developer to mix and match in terms of architectures, templating and markup engines. Sadly, with great power comes great responsibility.
The Express developer team seeks to soften the blow by introducing a project generator (if you're designing alongside a team, deploy the best cloud storage to keep things cohesive). It comes to your workstation in the form of an NPM package, and will aid our experiments with the following framework:
tamhan@tamhan-thinkpad:~/Desktop/
Stuff/2018Aug/FutureExpressJS/
workspace$ sudo npm install
express-generator -g
The generator also contains dozens of project options – the figure accompanying this step shows the full help output. For simplicity's sake, we will limit ourselves to a project based on the default settings. Kick off its generation process with:
tamhan@tamhan-thinkpad:~/Desktop/
Stuff/2018Aug/FutureExpressJS/
workspace$ express futuretest
Warning: the default view engine will not be Jade in future releases.
Get the Creative Bloq Newsletter
Daily design news, reviews, how-tos and more, as picked by the editors.
When done, the current working directory contains a new folder called 'futuretest'. It is home to our test project and must be configured using NPM's package download command. At the time of writing, the generator includes the Jade view generator – the project plans to change this in the near future, obligating you to pass in a parameter selecting the view engine intended. Alternatively, request the use of Pug – it is the official successor of the Jade engine:
cd futuretest/
npm install
02. Understand the application structure
Now that the project generator has done its thing, let us open App.js in an editor of choice. Its – much abridged – structure presents itself as following:
var indexRouter = require('./
routes/index');
var usersRouter = require('./
routes/users');
var app = express();
// view engine setup
app.set('views', path.join(__
dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({
extended: false }));
app.use(cookieParser());
app.use(express.static(path.
join(__dirname, 'public')));
Express.JS is highly modular. App.js serves as an entry point, where one or more 'use()' functions allow the adding of components intended to handle various requests. Invocations of 'set()' let you adjust parameters in the engine – one of which is the installation of the Jade view engine mentioned in the previous step.
The actual emission of web content takes place in the router classes. For brevity's sake, let's limit ourselves to Index.js:
var express = require('express');
var router = express.Router();
router.get('/', function(req, res,
next) {
res.render('index', { title:
'Express' });
});
module.exports = router;
'get()' is provided with a matcher string and an event handler which gets invoked whenever a corresponding event occurs. In our case, the render method of the chosen template engine is told to return content to the browser of the user who logged in.
03. Run the web page
At this point, we are ready to take the website for a spin for the first time. Return to the terminal containing the Express.JS installation, and call on NPM start with the debug flag set:
DEBUG=myapp:* npm start
When done, enter http://localhost:3000/ into a browser of choice to look at the scaffolding created by the project generator. When done, press ctrl+C to close the window and return control to the command line interpreter – keep in mind that this also closes the debugging web server.
04. It's all about routing and endpoints
For the sake of simplicity, let us agree that a web application is usually made up of a sequence of entry points. Express.JS handles these via the router class – think of it as a repository of methods that get called upon in response to an incoming request.
Adding a new endpoint to an application is accomplished by adding a new worker into the queue. Our auto-generated example creates two router types, each of which is raised using the 'require' method:
var indexRouter = require('./
routes/
index');
var usersRouter = require('./
routes/
users');
In the next step, 'app.use' registers the routers and connects them to the request strings. Our code furthermore adds an error handler that gets invoked if a non-existing URL is entered into the system:
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use(function(req, res, next)
{
next(createError(404));
});
05. Create a new route
Open Users.js, and modify its code as below:
router.get('/user1', function(req,
res,
next) {
res.send('Future says Hello
1');
});
router.get('/', function(req, res,
next) {
res.send('respond with a
resource');
});
Adding new routes to Express.JS is a mechanical process. Grab the router object of choice, and invoke the method corresponding to the HTTP verb you seek to handle. Next, pass in a string that will be added to the 'offset' registered with 'app.use'. From that moment onward, both http://localhost:3000/users/user1 and http://localhost:3000/users/ return a valid response.
Keep in mind that Express.JS is not limited to handling 'GET' resources. 'post()', 'put()' and 'delete()' handle the traditional four HTTP requests, with dozens of additional verb methods catering to more unusual needs. Finally, the 'req' object provides access to the request header – put it to good use when parsing parameters or client information.
06. Advanced matching
Adding routes by hand gets tedious as program complexity grows. Express.JS caters for this problem by introducing both wildcard and regular expression support. For example, look at the following declaration which uses a regular expression to match against various strings containing the character sequence dog.
app.get(/.*dog$/, function (req,
res) {
... })
07. Abnormal routing
While handling the four HTTP requests ought to be enough for anyone (hat tip to Bill Gates), Express.JS can also work with additional protocols. Express-WS is an especially tasty candidate for this section – it extends Express.JS's reach to include WebSocket communications.
Once the plugin is added to the main Express.JS project, enabling it is accomplished via a 'require' call. It returns a helper object containing all but one method – call it to establish a connection between the router and the plugin:
var expressWs = require('express-
ws')
app);
After that, a new method called 'ws()' can be invoked to add new routes based on WebSocket technology:
app.ws('/', function(ws, req) {
ws.on('message', function(msg)
{
console.log(msg);
});
console.log('socket', req.
testing); });
Their prototype differs from normal routes due to the presence of the 'ws' object – it provides access to the underlying WebSocket instance connected to the client responsible for the connection.
08. Integrate databases and templating engines
Being based on Node.JS means that the rich plugin ecosystem is at your command when working on web-based applications. For example, accessing SQL and NoSQL databases – usually an extraordinarily tedious task – can be handled using plugins provided by the database vendors. The actual deployment is as easy as installing the needed NPM module – if your code is to access a Redis database, simply add the following:
var redis = require('redis')
var client = redis.createClient()
client.set('stringKey', 'aVal',
redis.print)
. . .
Of course, in-memory SQLite is also supported:
var sqlite3 = require('sqlite3').
verbose()
var db = new sqlite3.
Database(':memory:')
db.serialize(function () {
db.run('CREATE TABLE lorem
(info TEXT)')
Keep in mind that the Node.JS integration is not limited to database plugins. Adventurous developers could go as far as to include products like Tessel, thereby creating web applications which can also interact with Internet of Things devices.
09. Templating in style
One area where simple and real programs differ is the creation of views. While a small example project usually uses hand-crafted strings, assembling large swaths of HTML with a string of connected things is highly annoying.
Template engines provide a neat workaround. They permit the creation of predefined schema files, which can be populated programmatically in execution.
In the case of our example program, the views lay in .jade files. Opening index reveals the following structure:
extends layout
block content
h1= title
p Welcome to #{title}
Expressions enclosed in curly brackets act as template fields whose values are to be replaced at runtime. Index.js invokes render with a parameter object, leading to the rendering of the start page shown in the figure accompanying this step:
router.get('/', function(req, res,
next) {
res.render('index', { title:
'Express' });
});
Most templating engines can also parse arrays when provided with an item template. In this case, every line of the array is displayed with one instance of the DOM model – similarities to the list display model found in Android are purely coincidental. Express.JS is not limited to the predefined templating engines. Should you feel like rolling out your own for some reason, simply follow the steps outlined here – in principle, you have to override all but one function.
10. Handle static content
Express.JS applications tend to contain CSS files and pictures. Serving these via the Render function is inefficient – a smarter way would involve sending them on their merry way with a traditional HTTP request. This can be achieved via the 'express.static()' function, which can mark entire folders for export:
app.use(express.static('public'))
app.use(express.static('files'))
11. Modify the event flow
Finally, allow us to mention the term middleware shortly. In Express.JS parlance, middleware is a set of one or more components which integrate themselves into the flowchart shown opposite. They can, then, be used to modify requests as they pass through the routing system – when implemented correctly, limitless functionality can be achieved.
Furthermore, some ready components can be found here – visit this site before embarking on a large-scale development project.
12. How to host an Express.JS app
Testing Express.JS-based applications is easy. Problems occur once you want the page to become accessible to third parties – due to it being generated by the Node.JS environment, there is no way to get a static image fit for FTP deployment to web hosting services.
In theory, there is nothing against using a Raspberry Pi, an OrangePi, a dedicated server or a virtual machine rented from a cloud service or a web host provider that offers virtual hosting. However, renting a full virtual machine can burden you with the responsibilities of keeping the execution environment and operating system up to date.
If this task is not to your taste, a Platform-as-a-Service provider can be a more attractive (albeit, in most cases, pretty pricey) choice.
Many developers consider Heroku, with its pricing shown in the figure accompanying this boxout, to be the gold standard for all things that are related to Node.JS hosting.
This, however, is a bit unfair in truth – Amazon's Elastic Beanstalk, Google's Cloud Platform and Microsoft's Azure all provide similar support for remote execution of Node.JS-based payloads. In all of these systems, the main issue is handling – while Azure is known for its slow deployments, other providers burden developers with difficult-to-use back-end services of extremely complex configuration systems.
Furthermore, the supported version of the Node.JS environment differs from provider to provider. Of course, we don't have enough space to cover the topic in depth. Visit Mozilla's deployment tutorial and Express.JS' performance and reliability and security best practices pages for some of the issues involved. Make sure to look at the provider's documentation to glean more best practices.
13. Future-proof your applications
Express.JS's development cycle is far from smooth: the developers are well-known for frequent API changes requiring rewrites of client code. The switch from 3.x to 4.x was especially painful, which is why the impending release of 5.x might leave quite a few of you feeling uncomfortable.
While Express.JS 5.0 brings along a few breaking changes, their impact is more limited. First of all, a set of already-deprecated functions is removed for real – if code still uses them, upgrading to 5.x requires maintenance.
Designers of view engines need to check 'res.render():' rank growth in regards to view renderers, which has led to some synchronous implementations slipping through. Version 5 of the framework enhances performance by enforcing asynchronous rendering.
In addition to that, a set of sundry improvements and changes documented here sees the return of some extinct features from previous versions – furthermore, some long-standing bugs will be fixed in the new release.
Finally, be aware that you can already try the new version. Simply create a copy of your source code, grab a terminal and enter the following command to download an archive's worth of barely tested bleeding-edge JavaScript. Be safe.
$ npm install express@>=5.0.0-
alpha.1 --save
This article was originally published in issue 279 of creative web design magazine Web Designer. Buy issue 279 here or subscribe to Web Designer here.
Related articles:
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
Tam Hanna is a software consultant who specialises in the management of carrier and device manufacturer relationships, mobile application distribution and development, design and prototyping of process computers and sensors. He was a regular contributor to Web Designer magazine in previous years, and now occupies his time as the owner of Tamoggemon Software and Computer Software.