top of page
Writer's pictureTandid Alam

Node and The Express Framework

Updated: Dec 19, 2022

A crash course on understanding Node alongside the Express framework


Contents


- Node Architecture

- Node Module System

- Path Module

- OS Module

- File System

- Events Module

- HTTP Server

- NPM (Node Package Manager)

- The Express Framework

- Getting Started with Express

- RESTful Services

- The Environment

- Middleware

- Learning Resources


 

Node Architecture


Node is a runtime environment for executing JavaScript code.


Essentially, Node is a C++ program that embeds Chrome’s v8 engine, the fastest JS engine in the world.

We use Node to build fast and scalable networking applications. It’s a perfect choice for building RESTful services.


Node applications are single-threaded. That means a single thread is used to serve all clients.


Node applications are asynchronous or non-blocking by default. That means when the application involves I/O operations (ex. accessing the file system or the network), the thread doesn’t wait (or block) for the result of the operation. It is released to serve other clients.


This architecture makes Node ideal for building I/O-intensive applications.


You should avoid using Node for CPU-intensive applications, such as a video encoding service. Because while executing these operations, other clients have to wait for the single thread to finish its job and be ready to serve them.


In Node, we don’t have browser environment objects such as window or the document object. Instead, we have other objects that are not available in browsers, such as objects for working with the file system, network, operating system, etc.


 

Node Module System


We don’t have the window object in Node. The global object in Node is “global”.


Unlike browser applications, variables we define are not added to the “global” object.


Every file in a Node application is a module. Node automatically wraps the code in each file with an IIFE (Immediately-invoked Function Expression) to create scope. So, variables and functions defined in one file are only scoped to that file and not visible to other files unless explicitly exported. This is what it looks like under the hood:

(function (exports, require, module, __filename, __dirname) {
    
    require('module')

    //code block goes here

    module.exports = function
})

//All the code in node documents essentially are wrapped up in this node wrapper function, so nothing reaches the global scope.


To export a variable or function from a module, you need to add them to module.exports:

module.exports.sayHello = sayHello; 

To load a module, use the require function. This function returns the module.exports object exported from the target module:


const logger = require('./logger');


Node has a few built-in modules that enable us to work with the file system, path objects, network, operating system, etc.



Path Module


The path module provides utilities for working with file and directory paths.

//app.js

const path = require('path')

let pathObj = path.parse(__filename)
console.log(pathObj)

// Returns {
//     root: '/'
//     dir: '/Users/...'
//     base: 'app.js'
//     ext: '.js'
//     name: 'app'
// }


OS (Operating System)


The OS module provides utilities for getting information on your operating system like total memory or free memory. Check out the documentation for more information.



FS (File System)


The file system helps you work with the files on your app and allows you to read them. Like other modules, there are many utilities so you can check out the documentation for more information.



Events Module


EventEmitter is one of the core classes in Node that allows us to raise (emit) and handle events. Several built-in classes in Node derive from EventEmitter.


To get started you have to call the EventEmitter class when requiring the events module:

const EventEmitter = require('events')

const emitter = new EventEmitter()


An event is a signal that something has happened.


Quite often you use the on() method when listening for events and emit() function when raising a signal.

//Register a listener
emitter.on('messageLogged', function(){
    console.log('Hello')
})

//Raise an event
emitter.emit('messageLogged')


There are many things you can do with EventEmitters, but it's best practice to create a class with the ability to raise events by extending the EventEmitter:

class Logger extends EventEmitter{
    ...
}


Read more documentation to get familiar with this concept if needed.



HTTP Server


The HTTP method can help you make a server for your app. Keep in mind that it has the same functionality as an event emitter so it can call the same functions.

const http = require('http');

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.write('Hello World!');
  res.end();
}).listen(8080);


There's a simpler way of creating a server by using the Express framework which will be visited later on in this post.


 

NPM (Node Package Manager)


Every Node application has a package.json file that includes metadata about the application. This includes the name of the application, its version, dependencies, etc.


We initialize node in our application through the terminal by using npm init which creates our package.json files for us.

npm init --yes //--yes gives default answers to the questions asked


We use NPM to download and install 3rd-party packages from the NPM registry:


All the installed packages and their dependencies are stored under node_modules folders. This folder should be excluded from the source control. To do this, we have to make sure to use a .gitignore file.


All the installed packages will show up under dependencies in the package.json file and to add packages only to the development environment we use --save-dev so that the packages can show up in the development dependencies section.


Installing the packages also creates a package.lock.json file which stores our package data as well.


Node packages follow semantic versioning: major.minor.patch

"mongoose": "^5.13.6" //An example of semantic versioning in mongoose.

"Notice how there's a caret symbol '^'. This means your machine will restore any new packages as long as its within the major version. The tilde '~' means the machine will restore any new packages within the major.minor version. Be careful because this can break code."



Useful NPM commands are:


  • Install a package:

npm i <packageName>

  • Install a specific version of a package:

npm i <packageName>@<version>

  • Install a package as a development dependency:

npm i <packageName> —save-dev

  • Uninstall a package:

npm un <packageName>

  • List installed packages:

npm list --depth=0

  • View outdated packages:

npm outdated

  • Update packages:

npm update 

To install/uninstall packages globally, use -g flag.



 

The Express Framework


Getting Started with Express


Express is a simple, minimalistic and lightweight framework for building web servers.

// Build a web server
const express = require('express');
const app = express()


Restful Services


REST (Representational State Transfer) defines a set of conventions for creating HTTP services:


  • POST : to create a resource

  • PUT : to update it

  • GET : to read it

  • DELETE : to delete it


Creating a course:


app.post('/api/courses', (req, res) => {      
    // Create the course and return the course object     
    res.send(course); 
});


Getting all courses:


app.get('/api/courses', (req, res) => {       
    // To read query string parameters (?sortBy=name)      
    const sortBy = req.query.sortBy;      

    // Return the courses  
    res.send(courses);
});


Creating a single course:


app.get('/api/courses/:id', (req, res) => {      
    const courseId = req.params.id;  // params.id is located in :id    
    // Lookup the course.    
    // If not found, return 404      
    res.status(404).send('Course not found.');     

    // Else, return the course object     
    res.send(course); 
});


Updating a course:


app.put('/api/courses/:id', (req, res) => {      
    // If course not found, return 404, otherwise update it     
    // and return the updated object. 
});


Deleting a course:


app.delete('/api/courses/:id', (req, res) => {      
    // If course not found, return 404, otherwise delete it     
    // and return the deleted object. 
});


The Environment


If we want to see our app we give it a port that we can access and receive data from:

// Listen on port 3000
app.listen(3000, () => console.log('Listening...'))


We can use environment variables to store various settings for an application. To read an environment variable, we use process.env.

// Reading the port from an environment variable 
const port = process.env.PORT || 3000;
app.listen(port);


We use the nodemon package to watch for changes in files and automatically restart the node process. You typically run a file using node, but this doesn't automatically detect changes.

node index.js //Need to run this every time changes are made
nodemon index.js //Run this once and changes will be made automatically


We can detect the environment in which our Node application is running (development, production, etc) using process.env.NODE_ENV and app.get(‘env’).

The config package gives us an elegant way to store configuration settings for our applications.



Middleware


A middleware function is a function that takes a request object and either terminates the request/response cycle or passes control to another middleware function.


Express has a few built-in middleware functions:

  • json(): to parse the body of requests with a JSON payload

  • urlencoded(): to parse the body of requests with URL-encoded payload

  • static(): to serve static files


You can create custom middleware for cross-cutting concerns, such as logging, authentication, etc:

// Custom middleware (applied on all routes) 
app.use(function(req, res, next)) {     
    // ...     
    next(); 
}


You can apply middleware to specific routes

// Custom middleware (applied on all routes with /api/admin) 
app.use('/api/admin',function(req, res, next)) {     
    // ...     
    next(); 
}


Learning Resources:




Comments


bottom of page