Features Since ECMAScript 2015(ES6)
Registered members can download the FREE Get Started App. This is the project I used to compose articles about setting up VS Code and developing Node with Express and the Embedded JavaScript (EJS) view engine.
The ES6 standard is widely supported but too much documentation does not implement key features which improve the syntax and performance for server-side applications. This article describes standards which all new Express applications should implement.
When I decided to learn Express, I found a lot of articles and AI suggestions which did not implement ES6 standards. ES6 or ECMAScript 2015 changed the landscape of JavaScript with the ability to create promises for asynchronous programming. I am familiar with ASP.NET Core and C# web applications and SQL Server databases. I developed this Express application with KenHaggerty.Com as a model. Registered members can download the FREE Get Started PostgreSQL app and Get Started MySQL app which implement this getting started with Express EJS tutorial.
Let's Talk About Express
Development Environment Setup
Features Since ECMAScript 2015(ES6)
Express EJS Template Engine
Express EJS Views, Layouts, and Partials
Express Error Handling
Express Routers and Controllers
Express Environment Variables
MySQL and Services
PostgreSQL and Services
EmailSender With Nodemailer
ES6 or ECMAScript 2015 changed the landscape of what has come to be known as JavaScript to make it fit for today’s programming. It added new elements like block-lexical environment (let and const), module, template string, the ability to create promises for asynchronous programming, and the new data structures: Map and Set. They made it much more powerful as well as let developers create cleaner, more maintainable, and more efficient code in JavaScript. See What is ES6 & Its Features You Should Know
Let's review some of the major advancements of ECMAScript 2015 (ES6).
Scoped Variable Declarations
ES6 introduced const and let variable declarations which are block-scoped, meaning they are only accessible within the block of code (defined by curly braces {}) where they are declared. Variables declared with let can be re-assigned new values after their initial declaration. Variables declared with const cannot be re-assigned after their initial declaration. They are intended for values that should remain constant throughout their lifetime.
let updatableString = 'This is updatable.'; updatableString = 'This is updated.'; const constantString = 'This is not updatable.'; constantString = 'Update attempt.'; // throws TypeError: Assignment to constant variable.
ECMAScript Modules (ESM)
ESM is an upgrade to CommonJS modularity CommonJS uses require() for importing modules and module.exports or exports for exporting. ESM uses import statements for importing and export statements (named or default) for exporting. ESM modules are designed for static analysis, allowing for optimizations like tree-shaking and potentially faster loading. ESM compatibility is declared with "type": "module" in the project's package.json file.
CommonJS
const express = require('express');
const app = express();
ESM
import express from 'express'; const app = express();
Promises and Async Programming
Asynchronous programming is a fundamental concept in JavaScript, especially when dealing with operations like API requests, file reading, or database queries. While Promises were introduced in ES6, async/await arrived later in ES2017, providing a more elegant and readable syntax for working with asynchronous operations in JavaScript.
UserAccountController
UserAccountController.PostLogin = async (req, res, next) => {
const { username, password } = req.body;
try {
const usercanidate = await UserService.findByUsername(username);
...
} catch (err) {
next(err);
}
}
UserService
UserService.findByUsername = async (username) => {
const [rows] = await ConnectionPool.query('SELECT * FROM users WHERE username = ?', [username]);
return rows[0];
};
Arrow Functions
Arrow Functions allow a shorter syntax for function expressions. You can skip the function keyword, the return keyword, and the curly brackets.
Before Arrow
const myFunction = function(a, b) {return a * b}
After Arrow
const myFunction = (a, b) => a * b;
You can only omit the return keyword and the curly brackets if the function is a single statement. Because of this, it might be a good habit to always keep them.
// This will not work
const myFunction = (a, b) => { a * b } ;
// This will not work
const myFunction = (a, b) => return a * b ;
// Only this will work
const myFunction = (a, b) => { return a * b };
Template Literals
Template literals are defined using backticks (`) instead of quotes. They allow multiline support, easy interpolation of expressions, and the ability to create custom string processing functions through tagged templates.
Template literals allow you to create multiline strings without needing to use escape characters like \n.
const multilineString = `This is a multiline string.`; console.log(multilineString);
You use the ${expression} syntax to insert variables or JavaScript expressions, and their evaluated values will be automatically included in\ the resulting string.
const name = "Alice";
const age = 30;
const greeting = `Hello, my name is ${name} and I am ${age} years old.`;
console.log(greeting); // Output: Hello, my name is Alice and I am 30 years old.
const price = 10;
const taxRate = 0.05;
const total = `Total: $${(price * (1 + taxRate)).toFixed(2)}`;
console.log(total); // Output: Total: $10.50
Template literals offer a more readable alternative to concatenating strings using the + operator, especially when dealing with multiple variables or expressions.
// Traditional concatenation
const traditional = "My name is " + name + " and I am " + age + " years old.";
// Template literal
const template = `My name is ${name} and I am ${age} years old.`;
Tagged Templates: This advanced feature allows you to parse template literals with a function. The tag function receives the static parts of the string and the interpolated values as arguments, enabling custom processing and manipulation of the template literal's content before it's returned.
function highlight(strings, ...values) {
let str = '';
strings.forEach((string, i) => {
str += string;
if (values[i]) {
str += `<strong>${values[i]}</strong>`;
}
});
return str;
}
const user = "Bob";
const message = highlight`Welcome, ${user}!`;
console.log(message); // Output: Welcome, <strong>Bob</strong>!
Destructuring Objects
Destructuring allows developers to extract values from objects or arrays with ease.
const user = { name: "John", age: 30 };
const { name, age } = user;
console.log(name, age); // Output: John, 30
Enhanced Object Literals
Enhanced object literals simplify object creation and allow for shorthand properties.
const age = 25;
const user = { name: "Alice", age, greet() { return "Hello"; } };
console.log(user.greet()); // Output: Hello
Set and Map Data Structures
Choosing one data structure among these two depends on the usage and how we want to access the data. If we want the data to be identified by a key then we should opt for maps but if we want unique values to be stored then we should use a set.
Set
A collection of values that can be accessed without a specific key. The elements are unique and the addition of duplicates is not allowed. Sets are mostly used to remove duplicates from any other data structure
// Array to Set
const myArr = [1, 2, 2, 3];
const mySet = new Set(myArr); // mySet will be {1, 2, 3}
// Set to Array
const newArr = Array.from(mySet); // newArr will be [1, 2, 3]
Map
Maps are used to store data in key-value pairs where keys are used to uniquely identify an element and values contain the data associated with it.
// Create a new Map
const myMap = new Map();
// 1. Setting key-value pairs with .set()
myMap.set('name', 'Alice');
myMap.set(1, 'One');
myMap.set(true, 'Boolean Key');
console.log(myMap);
// Output: Map(3) { 'name' => 'Alice', 1 => 'One', true => 'Boolean Key' }
// 2. Getting values by key with .get()
console.log(myMap.get('name')); // Output: Alice
console.log(myMap.get(1)); // Output: One
console.log(myMap.get('nonExistentKey')); // Output: undefined
// 3. Checking if a key exists with .has()
console.log(myMap.has('name')); // Output: true
console.log(myMap.has('age')); // Output: false
// 4. Deleting a key-value pair with .delete()
myMap.delete(true);
console.log(myMap.has(true)); // Output: false
console.log(myMap);
// Output: Map(2) { 'name' => 'Alice', 1 => 'One' }
// 5. Getting the number of key-value pairs with .size
console.log(myMap.size); // Output: 2
// 6. Clearing all key-value pairs with .clear()
myMap.clear();
console.log(myMap.size); // Output: 0
console.log(myMap); // Output: Map(0) {}