Skip to main content

JavaScript Programming Language: Overview, Node.js Installation with apt and Node Version Manager (NVM), Node Package Manager (npm) Commands, Node.js Examples, Node.js Express Webserver Example with Jest Integration Test and Container Deployment

2673 words·
JavaScript Node.js Node Package Manager (npm) Node Version Manager (NVM) YARN Multistage Dockerfile Jest Integration Test Webserver
Table of Contents
GitHub Repository Available

JavaScript Overview
#

Java Script Language
#

  • Programming language primarily used for creating interactive content in web browsers

  • Originally designed to run only in web browsers.


Use Cases:

  • Client-side development, for example validating user input on a form.

Node.js
#

  • Runtime environment that allows JavaScript to run outside of a browser, typically on a server.

  • Uses the V8 JavaScript engine (same engine used in Google Chrome) to execute JavaScript code on the server.


Use Cases:

  • Building web servers and APIs.

  • Server-side develpment / scripting.

  • Developing backend services.

  • Running JavaScript tools like npm (Node Package Manager).


Node Package Manager (npm)
#

  • Package manager, bundled with Node.js.

Yarn Package Manager
#

  • Alternative package manager to npm.

  • Both npm and Yarn use the npm registry to get packages and package information.


Node Version Manager (NVM)
#

  • Open-source version manager for Node.js.

  • Allows to install and manage different versions of Node.js and switch between them.

  • NVM stores the Node versions and associated modules inside your the directory.



JavaScript Setup (Linux Server)
#

Node.js Installation (apt Version)
#

Node.js Installation
#

  • Node.js provides the runtime environment that allows to execute JavaScript code outside of a browser.

  • To use JavaScript on a server, it is not necessary to install Java.

# Install Node.js
sudo apt install -y nodejs

Verify Node.js Installation
#

# Verify installation / list version
node -v

# Shell output:
v18.19.1

Node Package Manager (npm) Installation
#

# Install npm
sudo apt install -y npm
# Verify npm installation
npm -v

# Shell output:
9.2.0



Node.js Installation (NVM Version)
#

Node Version Manager (NVM) Installation
#

# Find latest NVM version
https://github.com/nvm-sh/nvm
# Install NVM
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

# Apply automatically added environment variables
source ~/.bashrc
# Verify installation / check version
nvm --version

# Shell output:
0.40.1

Node.js Installation
#

# List available Node.js versions
nvm ls-remote
# Install Node.js: Latest version
nvm install node

# Shell output:
...
Now using node v23.3.0 (npm v10.9.0)
Creating default alias: default -> node (-> v23.3.0)
# Install Node.js: Specific version
nvm install 19

# Shell output:
Now using node v19.9.0 (npm v9.6.3)

List Installed Node.js Versions
#

# List installed Node.js versions
nvm ls

# Shell output:
->      v19.9.0
        v23.3.0
...

Switch Node.js Version: Current Shell
#

# Use previously installed version 23
nvm use 23

# Shell output:
Now using node v23.3.0 (npm v10.9.0)
# Confirm currently used Node.js version
nvm current

# Shell output:
v23.3.0

Verify Node Package Manager (npm) Installation
#

Node Package Manager (npm) is automatically included when Node.js installed via Node Version Manager (NVM).

# Verify npm installation
npm -v

# Shell output:
10.9.0



npm Commands Examples
#

Create Project Folder
#

# Create a directory for an example application
mkdir ~/example-app && cd ~/example-app

Initialize npm Project
#

# Initialize the new npm project: Enter project information
npm init

# Shell output:
package name: (example-app)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /home/ubuntu/example-app/package.json:

{
  "name": "example-app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": ""
}
# Verify the newly created "package.json" file
ls

# Shell output:
package.json

Install & Uninstall Packages
#

# Install project dependencies: All packages defined in "package.json"
npm install
# Install specific package: For example "Express web application framework"
npm install express

# Install specific package: Define package version
npm install express@4.17.1

# Install specific package: Globally / system wide
npm install -g express

Verify the updated “package.json” file:

# Cat package.json
cat package.json

# Shell output:
{
  "name": "example-app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "express": "^4.21.1"  # Verify the express package
  }
}

Uninstall a Package:

# Uninstall specific package: For example "Express web application framework"
npm uninstall express

Yarn Installation
#

# Install Yarn
sudo npm install -g yarn

# Shell output:
added 1 package in 795ms
  • -g Install package globally, make it vailable anywhere on the system, not just within a project
# Verify installation
yarn -v

# Shell output:
1.22.22



Node.js Example Application: HelloWorld
#

# Create a Node.js example application
vi HelloWorld.js
// Outputs text to the console
console.log("Hi there, from Node.js");
# Run the Node.js application
node HelloWorld.js

# Shell output:
Hi there, from Node.js



Node.js Example Application: Simple Webserver
#

Create Project Folder
#

# Create a directory for an example application
mkdir ~/example-webserver && cd ~/example-webserver

Webserver Application
#

Example 1: Simple Text Response
#

Example 1: Simple text response on any requested url path

  • example-webserver.js
// Import the 'http' module to create an HTTP server
import http from 'http';

// Define the port, using environment variable or default to 8080
const port = process.env.PORT || 8080;

// Create the HTTP server and define its behavior
const server = http.createServer((req, res) => {
  // Send a simple text response to any incoming request
  res.end('Hi there.\n');
});

// Start the server and listen on the specified port
server.listen(port, () => {
  // Log a message to indicate the server is running
  console.log(`Server listening on port ${port}`);
});

Example 2: Diverse Responses
#

Example 2: Different responses regarding the requested URL path

  • example-webserver.js
// Import the 'http' module to create an HTTP server
import http from 'http';

// Define the port, using environment variable or default to 8080
const port = process.env.PORT || 8080;


// Create the HTTP server and define its behavior
const server = http.createServer(function (req, res) {
  // Send a simple text response
  if (req.url === '/') return respondText(req, res)
  // Send a json response
  if (req.url === '/json') return respondJson(req, res)
  // Send a 404 response
  respondNotFound(req, res)
})


// Start the server and listen on the specified port
server.listen(port, () => {
  // Log a message to indicate the server is running
  console.log(`Server listening on port ${port}`);
});


// Send a simple text response
function respondText (req, res) {
  res.setHeader('Content-Type', 'text/plain')
  res.end('Hi there.\n')
}
// Send a json response
function respondJson (req, res) {
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify({ text: 'Hi there.', numbers: [1, 2, 3] }))
}
// Send a 404 response
function respondNotFound (req, res) {
  res.writeHead(404, { 'Content-Type': 'text/plain' })
  res.end('Not Found\n')
}

Example 3: Stream Static Content
#

Example 3: The following example streams static content from a directory named public.

The file and folderstructure looks like this:

example-webserver
├── example-webserver.js
└── public
    └── index.html

  • example-webserver.js
// Import the 'http' module to create an HTTP server
import http from 'http';
// Import the filesystem module
import fs from 'fs'; // Import the filesystem module
// Import the querystring module
import querystring from 'querystring'
// Import utilities to simulate __dirname in ES modules
import { fileURLToPath } from 'url';
import path from 'path';


// Simulate __dirname in ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);


// Define the port, using environment variable or default to 8080
const port = process.env.PORT || 8080;


// Create the HTTP server and define its behavior
const server = http.createServer(function (req, res) {
  // Send a simple text response
  if (req.url === '/') return respondText(req, res)
  // Send a json response
  if (req.url === '/json') return respondJson(req, res)
  // Stream static files
  if (req.url.match(/^\/static/)) return respondStatic(req, res)
  // Send a 404 response
  respondNotFound(req, res)
})


// Start the server and listen on the specified port
server.listen(port, () => {
  // Log a message to indicate the server is running
  console.log(`Server listening on port ${port}`);
});


// Send a simple text response
function respondText (req, res) {
  res.setHeader('Content-Type', 'text/plain')
  res.end('Hi there.\n')
}
// Send a json response
function respondJson (req, res) {
  res.setHeader('Content-Type', 'application/json')
  res.end(JSON.stringify({ text: 'Hi there.', numbers: [1, 2, 3] }))
}
// Stream static content from "public" directory
function respondStatic (req, res) {
  // Construct the file path
  const filename = `${__dirname}/public${req.url.split('/static')[1]}`
  // Stream the file
  fs.createReadStream(filename)
    .on('error', () => respondNotFound(req, res))
    .pipe(res)
}
// Send a 404 response
function respondNotFound (req, res) {
  res.writeHead(404, { 'Content-Type': 'text/plain' })
  res.end('Not Found\n')
}
  • public/index.html
<!DOCTYPE html>
<html>

<head>
	<title>jklug.work</title>
</head>

<body>
	<h1>Hi there</h1>
  <p>Some HTML content </p>
</body>

</html>

Example 3: Test the Webserver
#

Run the Node.js application

# Run the application
node example-webserver.js

# Shell output:
Server listening on port 8080

Curl the different URL Paths in a new shell:

# Curl the webserver: Simple text response
curl localhost:8080/

# Shell output:
Hi there.
# Curl the webserver: Json response (Use browser for JSON output in a readable format)
curl localhost:8080/json

# Shell output:
{"text":"Hi there.","numbers":[1,2,3]}
# Curl the webserver: Stream static content
curl localhost:8080/static/index.html

# Shell output:
<!DOCTYPE html>
<html>

<head>
        <title>jklug.work</title>
</head>
# Curl the webserver: Stream static content / Error not found
curl localhost:8080/static/djkgnsdg

# Shell output:
Not Found
# Curl the webserver: Not found response
curl localhost:8080/djkgnsdg

# Shell output:
Not Found



Node.js Example Application: Webserver with Express Framework
#

GitHub Repository
#

The code of this Node.js application is available on GitHub:
https://github.com/jueklu/node.js-webserver-with-express-framework

File And Folderstructure
#

The file and folderstructure looks like this:

example-express-webserver
├── .env  # Variable for Node.js webserver port
├── .gitignore
├── node_modules
├── package.json  # Project metadata and dependencies
├── package-lock.json  # Locked dependency versions
├── public  # Static files (HTML, CSS, JS)
│   └── index.html  # Example HTML file
├── src  # Application source code
│   ├── app.js  # Main application
│   ├── routes
│   │   └── index.js  # Define route handlers
│   └── server.js  # Main application entry-point
└── tests
    └── app.test.js  # Integration test

Create Project Folder
#

# Create a directory for the example application
mkdir -p ~/example-express-webserver/{tests,public,src/routes} && cd ~/example-express-webserver

Initialize npm Project
#

# Initialize the new npm project: Skip interactive prompts and use default values
npm init --yes

# Shell output:
{
  "name": "example-express-webserver",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

Install Express Package
#

# Install Express package
npm install express

# Install dotenv package (loads environment variables)
npm install dotenv

# Install Jest and Supertest (for testing)
npm install --save-dev jest supertest

Application Files
#

Webserver Main Block: app.js
#

# Webserver main code block
vi src/app.js
// Import the Express framework for web applications
const express = require('express'); 

// Create an instance of the Express application
const app = express(); 

// Import routes
const routes = require('./routes');

// Import environment variables from .env
require('dotenv').config();

// Define the port, using an environment variable if available, or default to 80
const port = process.env.PORT || 80; 


// Use routes from "src/routes/index.js"
app.use('/', routes);


// Export the app for testing and server setup
module.exports = { app, port };

Webserver Entry-Point: server.js
#

# Starting block for the webserver
vi src/server.js
// Import the Express app instance and port configuration from app.js
const { app, port } = require('./app');

// Start the server
app.listen(port, () => console.log(`Server listening on port ${port}`));

Webserver Routes: index.js
#

# Webserver routes
vi src/routes/index.js
// Import the built-in 'fs' (File System) module to work with files
const fs = require('fs');

// Import the Express framework for web applications
const express = require('express'); 

// Import the 'path' module for handling and manipulating file paths
const path = require('path'); 


// Create a router instance
const router = express.Router();

// Respond with plain text
router.get('/', (req, res) => {
  res.send('Hi there.\n');
});

// Respond with JSON
router.get('/json', (req, res) => {
  res.json({ text: 'Hi there.', numbers: [1, 2, 3] });
});

// Serve static files directly from the 'public' directory
router.use('/static', express.static(path.join(__dirname, '../../public')));

// Respond with custom 404 error for unmatched paths
router.use((req, res) => {
  res.status(404).send('Not found\n');
});


// Export the router so it can be used in other files
module.exports = router;

Env File
#

# Create .env file
vi .env
# Define webserver port
PORT=8080

HTML File
#

# Create an example HTML file
vi public/index.html
<!DOCTYPE html>
<html>

<head>
	<title>jklug.work</title>
</head>

<body>
	<h1>Hi there</h1>
  <p>Some HTML content </p>
</body>

</html>

Add Integration Test
#

# Create a test file
vi tests/app.test.js
const request = require('supertest'); // Import Supertest to simulate HTTP requests
const { app } = require('../src/app'); // Import the app instance

describe('Basic Web Server Tests', () => {
  it('should return "Hi there." for GET /', async () => {
    const response = await request(app).get('/');
    expect(response.status).toBe(200); // Expect a 200 OK status
    expect(response.text).toBe('Hi there.\n'); // Expect the response body
  });

  it('should return JSON for GET /json', async () => {
    const response = await request(app).get('/json');
    expect(response.status).toBe(200); // Expect a 200 OK status
    expect(response.body).toEqual({ text: 'Hi there.', numbers: [1, 2, 3] }); // Expect the JSON response
  });

  it('should return 404 for an unknown route', async () => {
    const response = await request(app).get('/unknown');
    expect(response.status).toBe(404); // Expect a 404 Not Found status
    expect(response.text).toContain('Not found'); // Check the custom 404 message
  });
});

Adopt package.json
#

# Open package.json
vi package.json

Original file:

{
  "name": "example-express-webserver",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "dotenv": "^16.4.5",
    "express": "^4.21.1"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^7.0.0"
  }
}

Add the Jest test and start script:

{
  "name": "example-express-webserver",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "start": "node src/server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "dotenv": "^16.4.5",
    "express": "^4.21.1"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^7.0.0"
  }
}

Gitignore
#

Exclude the node_modules directory in version control.

# Create a .gitignore file
vi .gitignore
# .gitignore
node_modules/

Run the Just Integration Test
#

# Run the Just test
npm test

# Shell output:

> example-express-webserver@1.0.0 test
> jest

 PASS  tests/app.test.js
  Basic Web Server Tests
    ✓ should return "Hi there." for GET / (9 ms)
    ✓ should return JSON for GET /json (1 ms)
    ✓ should return 404 for an unknown route (1 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.227 s, estimated 1 s
Ran all test suites.

Run the application
#

# Run the application
node src/server.js

# Stop the application
Strg + c

Deploy Application via Container
#

Dockerfile
#

# Create a Dockerfile
vi Dockerfile
### Stage 1: Build

# Use the Node.js base image / Define as build stage
FROM node:23-slim AS build

# Set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json for dependency installation
COPY package*.json ./

# Install only production dependencies
RUN npm install --production

# Copy the rest of the application's source code
COPY . .



### Stage 2: Runtime

# Use the Alpine based Node.js image
FROM node:23-alpine3.20 AS runtime

# Create system user "appuser" / no PW
RUN adduser -S -D -H -h /app appuser

# Switch to "appuser"
USER appuser

# Set the working directory inside the container
WORKDIR /app

# Copy the application
COPY --from=build /app /app

# Expose the port your application listens on
EXPOSE 8080

# Run the application
ENTRYPOINT ["npm", "start"]

Build Docker Image
#

# Build the Docker image
docker build -t example-express-webserver .

Verify Docker Image
#

# List images
docker images

# Shell output:
REPOSITORY                  TAG       IMAGE ID       CREATED         SIZE
example-express-webserver   latest    3235c2482777   2 seconds ago   186MB

Run Docker Container
#

# Run the Docker container
docker run -d -p 8080:8080 example-express-webserver

Test the Application
#

Curl the different URL Paths in a new shell:

# Curl the webserver: Simple text response
curl localhost:8080/

# Shell output:
Hi there.
# Curl the webserver: Json response (Use browser for JSON output in a readable format)
curl localhost:8080/json

# Shell output:
{"text":"Hi there.","numbers":[1,2,3]}
# Curl the webserver: Stream static content
curl localhost:8080/static/index.html

# Shell output:
<!DOCTYPE html>
<html>

<head>
        <title>jklug.work</title>
</head>
# Curl the webserver: Stream static content / Error not found
curl localhost:8080/static/djkgnsdg

# Shell output:
Not Found
# Curl the webserver: Not found response
curl localhost:8080/djkgnsdg

# Shell output:
Not Found



Links #

# Official Documentation
https://nodejs.org/api/index.html
jueklu/node.js-webserver-with-express-framework

JavaScript
0
0