Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Getting Started With An Express And ES6+ JavaScript Stack

Getting Started With An Express And ES6+ JavaScript Stack

Getting Started With An Express And ES6+ JavaScript Stack

Jamie Corkhill

This article is the second part in a series, with part one located here, which provided basic and (hopefully) intuitive insight into Node.js, ES6+ JavaScript, Callback Functions, Arrow Functions, APIs, the HTTP Protocol, JSON, MongoDB, and more.

In this article, we’ll build upon the skills we attained in the previous one, learning how to implement and deploy a MongoDB Database for storing user booklist information, build an API with Node.js and the Express Web Application framework to expose that database and perform CRUD Operations upon it, and more. Along the way, we’ll discuss ES6 Object Destructuring, ES6 Object Shorthand, the Async/Await syntax, the Spread Operator, and we’ll take a brief look at CORS, the Same Origin Policy, and more.

In a later article, we’ll refactor our codebase as to separate concerns by utilizing three-layer architecture and achieving Inversion of Control via Dependency Injection, we’ll perform JSON Web Token and Firebase Authentication based security and access control, learn how to securely store passwords, and employ AWS Simple Storage Service to store user avatars with Node.js Buffers and Streams — all the while utilizing PostgreSQL for data persistence. Along the way, we will re-write our codebase from the ground up in TypeScript as to examine Classical OOP concepts (such as Polymorphism, Inheritance, Composition, and so on) and even design patterns like Factories and Adapters.

A Word Of Warning

There is a problem with the majority of articles discussing Node.js out there today. Most of them, not all of them, go no further than depicting how to setup Express Routing, integrate Mongoose, and perhaps utilize JSON Web Token Authentication. The problem is that they don’t talk about architecture, or security best practices, or about clean coding principles, or ACID Compliance, Relational Databases, Fifth Normal Form, the CAP Theorem or Transactions. It’s either assumed that you know about all of that coming in, or that you won’t be building projects large or popular enough to warrant that aforementioned knowledge.

There appear to be a few different types of Node developers — among others, some are new to programming in general, and others come from a long history of enterprise development with C# and the .NET Framework or the Java Spring Framework. The majority of articles cater to the former group.

In this article, I’m going to do exactly what I just stated that too many articles are doing, but in a follow up article, we are going to refactor our codebase entirely, permitting me to explain principles such as Dependency Injection, Three-Layer Architecture (Controller/Service/Repository), Data Mapping and Active Record, design patterns, unit, integration, and mutation testing, SOLID Principles, Unit of Work, coding against interfaces, security best practices like HSTS, CSRF, NoSQL and SQL Injection Prevention, and so on. We will also migrate from MongoDB to PostgreSQL, using the simple query builder Knex instead of an ORM — permitting us to build our own data access infrastructure and to get close up and personal with the Structured Query Language, the different types of relations (One-to-One, Many-to-Many, etc.), and more. This article, then, should appeal to beginners, but the next few should cater to more intermediate developers looking to improve their architecture.

In this one, we are only going to worry about persisting book data. We won’t handle user authentication, password hashing, architecture, or anything complex like that. All of that will come in the next and future articles. For now, and very basically, we’ll just build a method by which to permit a client to communicate with our web server via the HTTP Protocol as to save book information in a database.

Note: I’ve intentionally kept it extremely simple and perhaps not all that practical here because this article, in and of itself, is extremely long, for I have taken the liberty of deviating to discuss supplemental topics. Thus, we will progressively improve the quality and complexity of the API over this series, but again, because I’m considering this as one of your first introductions to Express, I’m intentionally keeping things extremely simple.

  1. ES6 Object Destructuring
  2. ES6 Object Shorthand
  3. ES6 Spread Operator (...)
  4. Coming up...

ES6 Object Destructuring

ES6 Object Destructuring, or Destructuring Assignment Syntax, is a method by which to extract or unpack values from arrays or objects into their own variables. We’ll start with object properties and then discuss array elements.

const person = {
    name: 'Richard P. Feynman',
    occupation: 'Theoretical Physicist' 
};

// Log properties:
console.log('Name:', person.name); 
console.log('Occupation:', person.occupation);

Such an operation is quite primitive, but it can be somewhat of a hassle considering we have to keep referencing person.something everywhere. Suppose there were 10 other places throughout our code where we had to do that — it would get quite arduous quite fast. A method of brevity would be to assign these values to their own variables.

const person = {
    name: 'Richard P. Feynman',
    occupation: 'Theoretical Physicist' 
};

const personName = person.name;
const personOccupation = person.occupation;

// Log properties:
console.log('Name:', personName); 
console.log('Occupation:', personOccupation);

Perhaps this looks reasonable, but what if we had 10 other properties nested on the person object as well? That would be many needless lines just to assign values to variables — at which point we’re in danger because if object properties are mutated, our variables won’t reflect that change (remember, only references to the object are immutable with const assignment, not the object’s properties), so basically, we can no longer keep “state” (and I’m using that word loosely) in sync. Pass by reference vs pass by value might come into play here, but I don’t want to stray too far from the scope of this section.

ES6 Object Destructing basically lets us do this:

const person = {
    name: 'Richard P. Feynman',
    occupation: 'Theoretical Physicist' 
};

// This is new. It’s called Object Destructuring.
const { name, occupation } = person;

// Log properties:
console.log('Name:', name); 
console.log('Occupation:', occupation);

We are not creating a new object/object literal, we are unpacking the name and occupation properties from the original object and putting them into their own variables of the same name. The names we use have to match the property names that we wish to extract.

Again, the syntax const { a, b } = someObject; is specifically saying that we expect some property a and some property b to exist within someObject (i.e, someObject could be { a: 'dataA', b: 'dataB' }, for example) and that we want to place whatever the values are of those keys/properties within const variables of the same name. That’s why the syntax above would provide us with two variables const a = someObject.a and const b = someObject.b .

What that means is that there are two sides to Object Destructuring. The “Template” side and the “Source” side, where the const { a, b } side (the left-hand side) is the template and the someObject side (the right-hand side) is the source side — which makes sense — we are defining a structure or “template” on the left that mirrors the data on “source” side.

Again, just to make this clear, here are a few examples:

// ----- Destructure from Object Variable with const ----- //
const objOne = {
    a: 'dataA', 
    b: 'dataB'
};

// Destructure
const { a, b } = objOne;

console.log(a); // dataA
console.log(b); // dataB

// ----- Destructure from Object Variable with let ----- //
let objTwo = {
    c: 'dataC', 
    d: 'dataD'
};

// Destructure
let { c, d } = objTwo;

console.log(c); // dataC
console.log(d); // dataD

// Destructure from Object Literal with const ----- //
const { e, f } = { e: 'dataE', f: 'dataF' }; // 

In the case of nested properties, mirror the same structure in your destructing assignment:

const person = {
    name:  'Richard P. Feynman',
    occupation: {
        type:  'Theoretical Physicist',
        location: {
            lat:  1,
            lng:  2
        }
    }
};

// Attempt one:
const { name, occupation } = person;

console.log(name); // Richard P. Feynman
console.log(occupation); // The entire `occupation` object.

// Attempt two:
const { occupation: { type, location } } = person;

console.log(type); // Theoretical Physicist
console.log(location) // The entire `location` object.

// Attempt three:
const { occupation: {  location: { lat, lng } } } = person;

console.log(lat); // 1
console.log(lng); // 2

As you can see, the properties you decide to pull off are optional, and to unpack nested properties, simply mirror the structure of the original object (the source) in the template side of your destructuring syntax. If you attempt to destructure a property that does not exist on the original object, that value will be undefined.

We can additionally destructure a variable without first declaring it — assignment without declaration — using the following syntax:

let name, occupation;

const person = {
    name: 'Richard P. Feynman',
    occupation: 'Theoretical Physicist' 
};

;({ name, occupation } = person);

console.log(name); // Richard P. Feynman
console.log(occupation); // Theoretical Physicist

We precede the expression with a semicolon as to ensure we don’t accidentally create an IIFE (Immediately Invoked Function Expression) with a function on a previous line (if one such function exists), and the parentheses around the assignment statement are required as to stop JavaScript from treating your left-hand (template) side as a block.

A very common use case of destructuring exists within function arguments:

const config = {
    baseUrl: '',
    awsBucket: '',
    secret: '' //  {
    fetch(baseUrl).then(() => console.log('Done'));
    console.log(awsBucket); // 
};

performOperation(config);

As you can see, we could have just used the normal destructuring syntax we are now used to inside of the function, like this:

const config = {
    baseUrl: '',
    awsBucket: '',
    secret: '' //  {
    const { baseUrl, awsBucket } = someConfig;
    fetch(baseUrl).then(() => console.log('Done'));
    console.log(awsBucket); // 
};

performOperation(config);

But placing said syntax inside the function signature performs destructuring automatically and saves us a line.

A real-world use case of this is in React Functional Components for props:

import React from 'react';

// Destructure `titleText` and `secondaryText` from `props`.
export default ({ titleText, secondaryText }) => (
    

{titleText}

{secondaryText}

);

As opposed to:

import React from 'react';

export default props => (
    

{props.titleText}

{props.secondaryText}

);

In both cases, we can set default values to the properties as well:

const personOne = {
    name:  'User One',
    password:  'BCrypt Hash'
};

const personTwo = {
    password:  'BCrypt Hash'
};

const createUser = ({ name = 'Anonymous', password }) => {
    if (!password) throw  new  Error('InvalidArgumentException');
    
    console.log(name);
    console.log(password);
    
    return {
        id: Math.random().toString(36) // 

As you can see, in the event that name is not present when destructured, we provide it a default value. We can do this with the previous syntax as well:

const { a, b, c = 'Default' } = { a: 'dataA', b: 'dataB' };
console.log(a); // dataA
console.log(b); // dataB
console.log(c); // Default

Arrays can be destructured too:

const myArr = [4, 3];

// Destructuring happens here.
const [valOne, valTwo] = myArr;

console.log(valOne); // 4
console.log(valTwo); // 3

// ----- Destructuring without assignment: ----- //
let a, b;

// Destructuring happens here.
;([a, b] = [10, 2]);

console.log(a + b); // 12

A practical reason for array destructuring occurs with React Hooks. (And there are many other reasons, I’m just using React as an example).

import React, { useState } from "react";

export default () => {
  const [buttonText, setButtonText] = useState("Default");

  return (
    
  );
}

Notice useState is being destructured off the export, and the array functions/values are being destructured off the useState hook. Again, don’t worry if the above doesn’t make sense — you’d have to understand React — and I’m merely using it as an example.

While there is more to ES6 Object Destructuring, I’ll cover one more topic here: Destructuring Renaming, which is useful to prevent scope collisions or variable shadows, etc. Suppose we want to destructure a property called name from an object called person, but there is already a variable by the name of name in scope. We can rename on the fly with a colon:

// JS Destructuring Naming Collision Example:
const name = 'Jamie Corkhill';

const person = {
    name: 'Alan Touring'
};

// Rename `name` from `person` to `personName` after destructuring.
const { name: personName } = person;

console.log(name); // Jamie Corkhill 

Finally, we can set default values with renaming too:

const name = 'Jamie Corkhill';

const person = {
    location: 'New York City, United States'
};

const { name: personName = 'Anonymous', location } = person;

console.log(name); // Jamie Corkhill
console.log(personName); // Anonymous
console.log(location); // New York City, United States

As you can see, in this case, name from person (person.name) will be renamed to personName and set to the default value of Anonymous if non-existent.

And of course, the same can be performed in function signatures:

const personOne = {
    name:  'User One',
    password:  'BCrypt Hash'
};

const personTwo = {
    password:  'BCrypt Hash'
};

const  createUser  = ({  name: personName =  'Anonymous', password }) => {
    if (!password) throw  new  Error('InvalidArgumentException');
    console.log(personName);
    console.log(password);

    return {
        id: Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15),
        name: personName,
        password: password // 

ES6 Object Shorthand

Suppose you have the following factory: (we’ll cover factories later)

const createPersonFactory = (name, location, position) => ({
    name: name,
    location: location,
    position: position
});

One might use this factory to create a person object, as follows. Also, note that the factory is implicitly returning an object, evident by the parentheses around the brackets of the Arrow Function.

const person = createPersonFactory('Jamie', 'Texas', 'Developer');
console.log(person); // { ... }

That’s what we already know from the ES5 Object Literal Syntax. Notice, however, in the factory function, that the value of each property is the same name as the property identifier (key) itself. That is — location: location or name: name. It turned out that that was a pretty common occurrence with JS developers.

With the shorthand syntax from ES6, we may achieve the same result by rewriting the factory as follows:

const createPersonFactory = (name, location, position) => ({
    name,
    location,
    position
});

const person = createPersonFactory('Jamie', 'Texas', 'Developer');
console.log(person);

Producing the output:

{ name: 'Jamie', location: 'Texas', position: 'Developer' }

It’s important to realize that we can only use this shorthand when the object we wish to create is being dynamically created based on variables, where the variable names are the same as the names of the properties to which we want the variables assigned.

This same syntax works with object values:

const createPersonFactory = (name, location, position, extra) => ({
    name,
    location,
    position,
    extra        // 

Producing the output:

{ 
    name: 'Jamie',
    location: 'Texas',
    position: 'Developer',
    extra: { 
        interests: [ 
            'Mathematics',
            'Quantum Mechanics',
            'Spacecraft Launch Systems' 
        ],
        favoriteLanguages: [ 'JavaScript', 'C#' ]
     } 
}

As a final example, this works with object literals as well:

const id = '314159265358979';
const name = 'Archimedes of Syracuse';
const location = 'Syracuse';

const greatMathematician = {
    id,
    name,
    location
};

ES6 Spread Operator (...)

The Spread Operator permits us to do a variety of things, some of which we’ll discuss here.

Firstly, we can spread out properties from one object on to another object:

const myObjOne = { a: 'a', b: 'b' };
const myObjTwo = { ...myObjOne }:

This has the effect of placing all properties on myObjOne onto myObjTwo, such that myObjTwo is now { a: 'a', b: 'b' }. We can use this method to override previous properties. Suppose a user wants to update their account:

const user = {
    name: 'John Doe', 
    email: '[email protected]',
    password: '',
    bio: 'Lorem ipsum'
};

const updates = {
    password: '',
    bio: 'Ipsum lorem',
    email: '[email protected]'
};

const updatedUser = {
    ...user,    // ',   // Updated
     bio: 'Ipsum lorem'
 }
 */

The same can be performed with arrays:

const apollo13Astronauts = ['Jim', 'Jack', 'Fred'];
const apollo11Astronauts = ['Neil', 'Buz', 'Michael'];

const unionOfAstronauts = [...apollo13Astronauts, ...apollo11Astronauts];

console.log(unionOfAstronauts);
// ['Jim', 'Jack', 'Fred', 'Neil', 'Buz, 'Michael'];

Notice here that we created a union of both sets (arrays) by spreading the arrays out into a new array.

There is a lot more to the Rest/Spread Operator, but it is out of scope for this article. It can be used to attain multiple arguments to a function, for example. If you want to learn more, view the MDN Documentation here.

ES6 Async/Await

Async/Await is a syntax to ease the pain of promise chaining.

The await reserved keyword permits you to “await” the settling of a promise, but it may only be used in functions marked with the async keyword. Suppose I have a function that returns a promise. In a new async function, I can await the result of that promise instead of using .then and .catch.

// Returns a promise.
const myFunctionThatReturnsAPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve('Hello'), 3000);
    });
}

const myAsyncFunction = async () => {
    const promiseResolutionResult = await myFunctionThatReturnsAPromise();
    console.log(promiseResolutionResult);
};

// Writes the log statement after three seconds.
myAsyncFunction();

There are a few things to note here. When we use await in an async function, only the resolved value goes into the variable on the left-hand side. If the function rejects, that’s an error that we have to catch, as we’ll see in a moment. Additionally, any function marked async will, by default, return a promise.

Let’s suppose I needed to make two API calls, one with the response from the former. Using promises and promise chaining, you might do it this way:

const makeAPICall = route => new  Promise((resolve, reject) => {
    console.log(route)
    resolve(route);
});

const main = () => {
    makeAPICall('/whatever')
        .then(response => makeAPICall(response + ' second call'))
        .then(response => console.log(response + ' logged'))
        .catch(err => console.error(err))
};

main();

// Result:
/* 
/whatever 
/whatever second call 
/whatever second call logged
*/

What’s happening here is that we first call makeAPICall passing to it /whatever, which gets logged the first time. The promise resolves with that value. Then we call makeAPICall again, passing to it /whatever second call, which gets logged, and again, the promise resolves with that new value. Finally, we take that new value /whatever second call which the promise just resolved with, and log it ourselves in the final log, appending on logged at the end. If this doesn’t make sense, you should look into promise chaining.

Using async/await, we can refactor to the following:

const main = async () => {
    const resultOne = await makeAPICall('/whatever');
    const resultTwo = await makeAPICall(resultOne + ' second call');
    console.log(resultTwo + ' logged');
};

Here is what will happen. The entire function will stop executing at the very first await statement until the promise from the first call to makeAPICall resolves, upon resolution, the resolved value will be placed in resultOne. When that happens, the function will move to the second await statement, again pausing right there for the duration of the promise settling. When the promise resolves, the resolution result will be placed in resultTwo. If the idea about function execution sounds blocking, fear not, it’s still asynchronous, and I’ll discuss why in a minute.

This only depicts the “happy” path. In the event that one of the promises reject, we can catch that with try/catch, for if the promise rejects, an error will be thrown — which will be whatever error the promise rejected with.

const main = async () => {
    try {
        const resultOne = await makeAPICall('/whatever');
        const resultTwo = await makeAPICall(resultOne + ' second call');
        console.log(resultTwo + ' logged');
    } catch (e) {
        console.log(e)
    }
};

As I said earlier, any function declared async will return a promise. So, if you want to call an async function from another function, you can use normal promises, or await if you declare the calling function async. However, if you want to call an async function from top-level code and await its result, then you’d have to use .then and .catch.

For example:

const returnNumberOne = async () => 1;

returnNumberOne().then(value => console.log(value)); // 1

Or, you could use an Immedieately Invoked Function Expression (IIFE):

(async () => {
    const value = await returnNumberOne();
    console.log(value); // 1
})();

When you use await in an async function, the execution of the function will stop at that await statement until the promise settles. However, all other functions are free to proceed with execution, thus no extra CPU resources are allocated nor is the thread ever blocked. I’ll say that again — operations in that specific function at that specific time will stop until the promise settles, but all other functions are free to fire. Consider an HTTP Web Server — on a per-request basis, all functions are free to fire for all users concurrently as requests are made, it’s just that the async/await syntax will provide the illusion that an operation is synchronous and blocking as to make promises easier to work with, but again, everything will remain nice and async.

This isn’t all there is to async/await, but it should help you to grasp the basic principles.

Classical OOP Factories

We are now going to leave the JavaScript world and enter the Java world. There can come a time when the creation process of an object (in this case, an instance of a class — again, Java) is fairly complex or when we want to have different objects produced based upon a series of parameters. An example might be a function that creates different error objects. A factory is a common design pattern in Object-Oriented Programming and is basically a function that creates objects. To explore this, let us move away from JavaScript into the world of Java. This will make sense to developers who come from a Classical OOP (i.e, not prototypal), statically typed language background. If you are not one such developer, feel free to skip this section. This is a small deviation, and so if following along here interrupts your flow of JavaScript, then again, please skip this section.

A common creational pattern, the Factory Pattern permits us to create objects without exposing the required business logic to perform said creation.

Suppose we are writing a program that permits us to visualize primitive shapes in n-dimensions. If we provide a cube, for example, we’d see a 2D cube (a square), a 3D cube (a cube), and a 4D cube (a Tesseract, or Hypercube). Here is how this might be done, trivially, and barring the actual drawing part, in Java.

// Main.java

// Defining an interface for the shape (can be used as a base type)
interface IShape {
    void draw();
}

// Implementing the interface for 2-dimensions:
class TwoDimensions implements IShape {
    @Override
    public void draw() {
        System.out.println("Drawing a shape in 2D.");
    }
}

// Implementing the interface for 3-dimensions:
class ThreeDimensions implements IShape {
    @Override
    public void draw() {
        System.out.println("Drawing a shape in 3D.");
    }
}

// Implementing the interface for 4-dimensions:
class FourDimensions implements IShape {
    @Override
    public void draw() {
        System.out.println("Drawing a shape in 4D.");
    }
}

// Handles object creation
class ShapeFactory {
    // Factory method (notice return type is the base interface)
    public IShape createShape(int dimensions) {
        switch(dimensions) {
            case 2:
                return new TwoDimensions();
            case 3:
                return new ThreeDimensions();
            case 4:
                return new FourDimensions();
            default: 
                throw new IllegalArgumentException("Invalid dimension.");
        }
    }
}

// Main class and entry point.
public class Main {
    public static void main(String[] args) throws Exception {
        ShapeFactory shapeFactory = new ShapeFactory();
        IShape fourDimensions = shapeFactory.createShape(4);
        fourDimensions.draw(); // Drawing a shape in 4D.
    }
}

As you can see, we define an interface that specifies a method for drawing a shape. By having the different classes implement the interface, we can guarantee that all shapes can be drawn (for they all must have an overridable draw method as per the interface definition). Considering this shape is drawn differently depending upon the dimensions within which it’s viewed, we define helper classes that implement the interface as to perform the GPU intensive work of simulating n-dimensional rendering. ShapeFactory does the work of instantiating the correct class — the createShape method is a factory, and like the definition above, it is a method that returns an object of a class. The return type of createShape is the IShape interface because the IShape interface is the base type of all shapes (because they have a draw method).

This Java example is fairly trivial, but you can easily see how useful it becomes in larger applications where the setup to create an object might not be so simple. An example of this would be a video game. Suppose the user has to survive different enemies. Abstract classes and interfaces might be used to define core functions available to all enemies (and methods that can be overridden), perhaps employing the delegation pattern (favor composition over inheritance as the Gang of Four suggested so you don’t get locked into extending a single base class and to make testing/mocking/DI easier). For enemy objects instantiated in different ways, the interface would permit factory object creation while relying on the generic interface type. This would be very relevant if the enemy was created dynamically.

Another example is a builder function. Suppose we utilize the Delegation Pattern to have a class delegate work to other classes that honor an interface. We could place a static build method on the class to have it construct its own instance (assuming you were not using a Dependency Injection Container/Framework). Instead of having to call each setter, you can do this:

public class User {
    private IMessagingService msgService;
    private String name;
    private int age;
    
    public User(String name, int age, IMessagingService msgService) {
        this.name = name;
        this.age = age;
        this.msgService = msgService;
    }
    
    public static User build(String name, int age) {
        return new User(name, age, new SomeMessageService());
    }
}

I’ll be explaining the Delegation Pattern in a later article if you’re not familiar with it — basically, through Composition and in terms of object-modeling, it creates a “has-a” relationship instead of an “is-a” relationship as you’d get with inheritance. If you have a Mammal class and a Dog class, and Dog extends Mammal, then a Dog is-a Mammal. Whereas, if you had a Bark class, and you just passed instances of Bark into the constructor of Dog, then Dog has-a Bark. As you might imagine, this especially makes unit testing easier, for you can inject mocks and assert facts about the mock as long as mock honors the interface contract in the testing environment.

The static “build” factory method above simply creates a new object of User and passes a concrete MessageService in. Notice how this follows from the definition above — not exposing the business logic to create an object of a class, or, in this case, not exposing the creation of the messaging service to the caller of the factory.

Again, this is not necessarily how you would do things in the real world, but it presents the idea of a factory function/method quite well. We might use a Dependency Injection container instead, for example. Now back to JavaScript.

Starting With Express

Express is a Web Application Framework for Node (available via an NPM Module) that permits one to create an HTTP Web Server. It’s important to note that Express is not the only framework to do this (there exists Koa, Fastify, etc.), and that, as seen in the previous article, Node can function without Express as a stand-alone entity. (Express is merely a module that was designed for Node — Node can do many things without it, although Express is popular for Web Servers).

Again, let me make a very important distinction. There is a dichotomy present between Node/JavaScript and Express. Node, the runtime/environment within which you run JavaScript, can do many things — such as permitting you to build React Native apps, desktop apps, command-line tools, etc. — Express is nothing but a lightweight framework that permits you to use Node/JS to build web servers as opposed to dealing with Node’s low-level network and HTTP APIs. You don’t need Express to build a web server.

Before starting this section, if you are not familiar with HTTP and HTTP Requests (GET, POST, etc.), then I encourage you to read the corresponding section of my former article, which is linked above.

Using Express, we’ll set up different routes to which HTTP Requests may be made, as well as the related endpoints (which are callback functions) that will fire when a request is made to that route. Don’t worry if routes and endpoints are currently non-sensical — I’ll be explaining them later.

Unlike other articles, I’ll take the approach of writing the source code as we go, line-by-line, rather than dumping the entire codebase into one snippet and then explaining later. Let’s begin by opening a terminal (I’m using Terminus on top of Git Bash on Windows — which is a nice option for Windows users who want a Bash Shell without setting up the Linux Subsystem), setting up our project’s boilerplate, and opening it in Visual Studio Code.

mkdir server && cd server
touch server.js
npm init -y
npm install express
code .

Inside the server.js file, I’ll begin by requiring express using the require() function.

const express = require('express');

require('express') tells Node to go out and get the Express module we installed earlier, which is currently inside the node_modules folder (for that’s what npm install does — create a node_modules folder and puts modules and their dependencies in there). By convention, and when dealing with Express, we call the variable that holds the return result from require('express') express, although it may be called anything.

This returned result, which we have called express, is actually a function — a function we’ll have to invoke to create our Express app and set up our routes. Again, by convention, we call this appapp being the return result of express() — that is, the return result of calling the function that has the name express as express().

const express = require('express'); 
const app = express();

// Note that the above variable names are the convention, but not required.
// An example such as that below could also be used.

const foo = require('express');
const bar = foo();

// Note also that the node module we installed is called express.

The line const app = express(); simply puts a new Express Application inside of the app variable. It calls a function named express (the return result of require('express')) and stores its return result in a constant named app. If you come from an object-oriented programming background, consider this equivalent to instantiating a new object of a class, where app would be the object and where express() would call the constructor function of the express class. Remember, JavaScript allows us to store functions in variables — functions are first-class citizens. The express variable, then, is nothing more than a mere function. It’s provided to us by the developers of Express.

I apologize in advance if I’m taking a very long time to discuss what is actually very basic, but the above, although primitive, confused me quite a lot when I was first learning back-end development with Node.

Inside the Express source code, which is open-source on GitHub, the variable we called express is a function entitled createApplication, which, when invoked, performs the work necessary to create an Express Application:

A snippet of Express source code:

exports  =  module.exports  = createApplication;

/*
 * Create an express application
 */

// This is the function we are storing in the express variable. (- Jamie)
function createApplication() {

   // This is what I mean by "Express App" (- Jamie)
   var app = function(req, res, next) {

      app.handle(req, res, next);

   };

   mixin(app, EventEmitter.prototype, false);
   mixin(app, proto, false);

   // expose the prototype that will get set on requests

   app.request = Object.create(req, {

      app: { configurable: true, enumerable: true, writable: true, value: app      }

   })

   // expose the prototype that will get set on responses

   app.response = Object.create(res, {

      app: { configurable: true, enumerable: true, writable: true, value: app }

   })

   app.init();

   // See - `app` gets returned. (- Jamie)
   return app;
}

GitHub: https://github.com/expressjs/express/blob/master/lib/express.js

With that short deviation complete, let’s continue setting up Express. Thus far, we have required the module and set up our app variable.

const express = require('express');
const app = express();

From here, we have to tell Express to listen on a port. Any HTTP Requests made to the URL and Port upon which our application is listening will be handled by Express. We do that by calling app.listen(...), passing to it the port and a callback function which gets called when the server starts running:

const PORT = 3000;

app.listen(PORT, () => console.log(`Server is up on port {PORT}.`));

We notate the PORT variable in capital by convention, for it is a constant variable that will never change. You could do that with all variables that you declare const, but that would look messy. It’s up to the developer or development team to decide on notation, so we’ll use the above sparsely. I use const everywhere as a method of “defensive coding” — that is, if I know that a variable is never going to change then I might as well just declare it const. Since I define everything const, I make the distinction between what variables should remain the same on a per-request basis and what variables are true actual global constants.

Here is what we have thus far:

const express = require('express'); 
const app = express(); 

const PORT = 3000;

// We will build our API here.
// ...

// Binding our application to port 3000.
app.listen(PORT, () => {
   console.log(`Server is up on port ${PORT}.`);
});

Let’s test this to see if the server starts running on port 3000.

I’ll open a terminal and navigate to our project’s root directory. I’ll then run node server/server.js. Note that this assumes you have Node already installed on your system (You can check with node -v).

If everything works, you should see the following in the terminal:

Server is up on port 3000.

Go ahead and hit Ctrl + C to bring the server back down.

If this doesn’t work for you, or if you see an error such as EADDRINUSE, then it means you may have a service already running on port 3000. Pick another port number, like 3001, 3002, 5000, 8000, etc. Be aware, lower number ports are reserved and there is an upper bound of 65535.

At this point, it’s worth taking another small deviation as to understand servers and ports in the context of computer networking. We’ll return to Express in a moment. I take this approach, rather than introducing servers and ports first, for the purpose of relevance. That is, it is difficult to learn a concept if you fail to see its applicability. In this way, you are already aware of the use case for ports and servers with Express, so the learning experience will be more pleasurable.

A Brief Look At Servers And Ports

A server is simply a computer or computer program that provides some sort of “functionality” to the clients that talk to it. More generally, it’s a device, usually connected to the Internet, that handles connections in a pre-defined manner. In our case, that “pre-defined manner” will be HTTP or the HyperText Transfer Protocol. Servers that use the HTTP Protocol are called Web Servers.

When building an application, the server is a critical component of the “client-server model”, for it permits the sharing and syncing of data (generally via databases or file systems) across devices. It’s a cross-platform approach, in a way, for the SDKs of platforms against which you may want to code — be they web, mobile, or desktop — all provide methods (APIs) to interact with a server over HTTP or TCP/UDP Sockets. It’s important to make a distinction here — by APIs, I mean programming language constructs to talk to a server, like XMLHttpRequest or the Fetch API in JavaScript, or HttpUrlConnection in Java, or even HttpClient in C#/.NET. This is different from the kind of REST API we’ll be building in this article to perform CRUD Operations on a database.

To talk about ports, it’s important to understand how clients connect to a server. A client requires the IP Address of the server and the Port Number of our specific service on that server. An IP Address, or Internet Protocol Address, is just an address that uniquely identifies a device on a network. Public and private IPs exist, with private addresses commonly used behind a router or Network Address Translator on a local network. You might see private IP Addresses of the form 192.168.XXX.XXX or 10.0.XXX.XXX. When articulating an IP Address, decimals are called “dots”. So 192.168.0.1 (a common router IP Addr.) might be pronounced, “one nine two dot one six eight dot zero dot one”. (By the way, if you’re ever in a hotel and your phone/laptop won’t direct you to the AP captive portal, try typing 192.168.0.1 or 192.168.1.1 or similar directly into Chrome).

For simplicity, and since this is not an article about the complexities of computer networking, assume that an IP Address is equivalent to a house address, allowing you to uniquely identify a house (where a house is analogous to a server, client, or network device) in a neighborhood. One neighborhood is one network. Put together all of the neighborhoods in the United States, and you have the public Internet. (This is a basic view, and there are many more complexities — firewalls, NATs, ISP Tiers (Tier One, Tier Two, and Tier Three), fiber optics and fiber optic backbones, packet switches, hops, hubs, etc., subnet masks, etc., to name just a few — in the real networking world.) The traceroute Unix command can provide more insight into the above, displaying the path (and associated latency) that packets take through a network as a series of “hops”.

A Port Number identifies a specific service running on a server. SSH, or Secure Shell, which permits remote shell access to a device, commonly runs on port 22. FTP or File Transfer Protocol (which might, for example, be used with an FTP Client to transfer static assets to a server) commonly runs on Port 21. We might say, then, that ports are specific rooms inside each house in our analogy above, for rooms in houses are made for different things — a bedroom for sleeping, a kitchen for food preparation, a dining room for consumption of said food, etc., just like ports correspond to programs that perform specific services. For us, Web Servers commonly run on Port 80, although you are free to specify whichever Port Number you wish as long they are not in use by some other service (they can’t collide).

In order to access a website, you need the IP Address of the site. Despite that, we normally access websites via a URL. Behind the scenes, a DNS, or Domain Name Server, converts that URL into an IP Address, allowing the browser to make a GET Request to the server, get the HTML, and render it to the screen. 8.8.8.8 is the address of one of Google’s Public DNS Servers. You might imagine that requiring the resolution of a hostname to an IP Address via a remote DNS Server will take time, and you’d be right. To reduce latency, Operating Systems have a DNS Cache — a temporary database that stores DNS lookup information, thereby reducing the frequency of which said lookups must occur. The DNS Resolver Cache can be viewed on Windows with the ipconfig /displaydns CMD command and purged via the ipconfig /flushdns command.

On a Unix Server, more common lower number ports, like 80, require root level (escalated if you come from a Windows background) privileges. For that reason, we’ll be using port 3000 for our development work, but will allow the server to choose the port number (whatever is available) when we deploy to our production environment.

Finally, note that we can type IP Addresses directly in Google Chrome’s search bar, thus bypassing the DNS Resolution mechanism. Typing 216.58.194.36, for example, will take you to Google.com. In our development environment, when using our own computer as our dev server, we’ll be using localhost and port 3000. An address is formatted as hostname:port, so our server will be up on localhost:3000. Localhost, or 127.0.0.1, is the loopback address, and means the address of “this computer”. It is a hostname, and its IPv4 address resolves to 127.0.0.1. Try pinging localhost on your machine right now. You might get ::1 back — which is the IPv6 loopback address, or 127.0.0.1 back — which is the IPv4 loopback address. IPv4 and IPv6 are two different IP Address formats associated with different standards — some IPv6 addresses can be converted to IPv4 but not all.

Returning To Express

I mentioned HTTP Requests, Verbs, and Status Codes in my previous article, Get Started With Node: An Introduction To APIs, HTTP And ES6+ JavaScript. If you do not have a general understanding of the protocol, feel free to jump to the “HTTP and HTTP Requests” section of that piece.

In order to get a feel for Express, we are simply going to set up our endpoints for the four fundamental operations we’ll be performing on the database — Create, Read, Update, and Delete, known collectively as CRUD.

Remember, we access endpoints by routes in the URL. That is, although the words “route” and “endpoint” are commonly used interchangeably, an endpoint is technically a programming language function (like ES6 Arrow Functions) that performs some server-side operation, while a route is what the endpoint is located behind of. We specify these endpoints as callback functions, which Express will fire when the appropriate request is made from the client to the route behind which the endpoint lives. You can remember the above by realizing that it is endpoints that perform a function and the route is the name that is used to access the endpoints. As we’ll see, the same route can be associated with multiple endpoints by using different HTTP Verbs (similar to method overloading if you come from a classical OOP background with Polymorphism).

Keep in mind, we are following REST (REpresentational State Transfer) Architecture by permitting clients to make requests to our server. This is, after all, a REST or RESTful API. Specific requests made to specific routes will fire specific endpoints which will do specific things. An example of such a “thing” that an endpoint might do is adding new data to a database, removing data, updating data, etc.

Express knows what endpoint to fire because we tell it, explicitly, the request method (GET, POST, etc.) and the route — we define what functions to fire for specific combinations of the above, and the client makes the request, specifying a route and method. To put this more simply, with Node, we’ll tell Express — “Hey, if someone makes a GET Request to this route, then go ahead and fire this function (use this endpoint)”. Things can get more complicated: “Express, if someone makes a GET Request to this route, but they don’t send up a valid Authorization Bearer Token in the header of their request, then please respond with an HTTP 401 Unauthorized. If they do possess a valid Bearer Token, then please send down whatever protected resource they were looking for by firing the endpoint. Thanks very much and have a nice day.” Indeed, it’d be nice if programming languages could be that high level without leaking ambiguity, but it nonetheless demonstrates the basic concepts.

Remember, the endpoint, in a way, lives behind the route. So it’s imperative that the client provides, in the header of the request, what method it wants to use so that Express can figure out what to do. The request will be made to a specific route, which the client will specify (along with the request type) when contacting the server, allowing Express to do what it needs to do and us to do what we need to do when Express fires our callbacks. That’s what it all comes down to.

In the code examples earlier, we called the listen function which was available on app, passing to it a port and callback. app itself, if you remember, is the return result from calling the express variable as a function (that is, express()), and the express variable is what we named the return result from requiring 'express' from our node_modules folder. Just like listen is called on app, we specify HTTP Request Endpoints by calling them on app. Let’s look at GET:

app.get('/my-test-route', () => {
   // ...
});

The first parameter is a string, and it is the route behind which the endpoint will live. The callback function is the endpoint. I’ll say that again: the callback function — the second parameter — is the endpoint that will fire when an HTTP GET Request is made to whatever route we specify as the first argument (/my-test-route in this case).

Now, before we do any more work with Express, we need to know how routes work. The route we specify as a string will be called by making the request to www.domain.com/the-route-we-chose-earlier-as-a-string. In our case, the domain is localhost:3000, which means, in order to fire the callback function above, we have to make a GET Request to localhost:3000/my-test-route. If we used a different string as the first argument above, the URL would have to be different to match what we specified in JavaScript.

When talking about such things, you’ll likely hear of Glob Patterns. We could say that all of our API’s routes are located at the localhost:3000/** Glob Pattern, where ** is a wildcard meaning any directory or sub-directory (note that routes are not directories) to which root is a parent — that is, everything.

Let’s go ahead and add a log statement into that callback function so that altogether we have:

// Getting the module from node_modules.
const express = require('express');

// Creating our Express Application.
const app = express();

// Defining the port we’ll bind to.
const PORT = 3000;

// Defining a new endpoint behind the "/my-test-route" route.
app.get('/my-test-route', () => {
   console.log('A GET Request was made to /my-test-route.');
});

// Binding the server to port 3000.
app.listen(PORT, () => {
   console.log(`Server is up on port ${PORT}.`)
});

We’ll get our server up and running by executing node server/server.js (with Node installed on our system and accessible globally from system environment variables) in the project’s root directory. Like earlier, you should see the message that the server is up in the console. Now that the server is running, open a browser, and visit localhost:3000 in the URL bar.

You should be greeted with an error message that states Cannot GET /. Press Ctrl + Shift + I on Windows in Chrome to view the developer console. In there, you should see that we have a 404 (Resource not found). That makes sense — we have only told the server what to do when someone visits localhost:3000/my-test-route. The browser has nothing to render at localhost:3000 (which is equivalent to localhost:3000/ with a slash).

If you look at the terminal window where the server is running, there should be no new data. Now, visit localhost:3000/my-test-route in your browser’s URL bar. You might see the same error in Chrome’s Console (because the browser is caching the content and still has no HTML to render), but if you view your terminal where the server process is running, you’ll see that the callback function did indeed fire and the log message was indeed logged.

Shut down the server with Ctrl + C.

Now, let’s give the browser something to render when a GET Request is made to that route so we can lose the Cannot GET / message. I’m going to take our app.get() from earlier, and in the callback function, I’m going to add two arguments. Remember, the callback function we are passing in is getting called by Express behind the scenes, and Express can add whatever arguments it wants. It actually adds two (well, technically three, but we’ll see that later), and while they are both extremely important, we don’t care about the first one for now. The second argument is called res, short for response, and I’ll access it by setting undefined as the first parameter:

app.get('/my-test-route', (undefined, res) => {
    console.log('A GET Request was made to /my-test-route.');
});

Again, we can call the res argument whatever we want, but res is convention when dealing with Express. res is actually an object, and upon it exist different methods for sending data back to the client. In this case, I’m going to access the send(...) function available on res to send back HTML which the browser wil



This post first appeared on How Copywriting Can Benefit From User Research —, please read the originial post: here

Share the post

Getting Started With An Express And ES6+ JavaScript Stack

×

Subscribe to How Copywriting Can Benefit From User Research —

Get updates delivered right to your inbox!

Thank you for your subscription

×