Skip to main content

Node.js - JavaScript in Server Applications

·16 mins
Recommended basics: Articles you should know

To get the full picture of this article, you should know about this topics:

Efficient Dependency Management with NPM

Learn how to efficiently manage dependencies in your projects using NPM. Avoid losing control and maintain oversight with proper documentation and management techniques.

Effortless Website Hosting on a Budget with Namecheap

Discover how to effortlessly host your website on a small budget with Namecheap's shared hosting. Explore the process from selecting a plan to configuring SSL, and learn to upload your site for a seamless online presence.

Applications, no matter if public websites, web-services, internal backends or local applications can be complex and in most cases run dynamically, based on provided information.

Get into dynamic programming with JavaScript:

We’ve discussed JavaScript already, in the context of interactive websites. The discussed use-case was to show a real-time-clock on a website. As you probably recognized, this code will just run, if a users visits this website and as long as he has it open. Once closed, this code will is not executed any longer.

Today we’ll discuss how we can use Node.js to run the code whenever and however long we like on our computer natively, without using a browser.

JavaScript in Websites vs. Node.js: Key Differences for Server Applications #

Node.js is JavaScript executed as a application. Compared to the browser, you as a developer have more technical possibilities. On the other hand there’s no browser where you can render HTML to interact with your users.

When deciding which to pick, I would review the use-cases. Is it something to implement in my website, I’ll go for JavaScript in the website. But if something standalone, I’ll probably go for Node.js.

The compare is way more complex, but on a high level, this should be enough for the moment.

How to Set Up Node.js #

Check out the official how to install Node.js guide.

Real-World Node.js Use Cases: From CLI Apps to Web Servers #

Since we’ve discussed JavaScript already, let’s directly walk through some use-cases to actually see what Node.js can do for you.

If you're unfamiliar with JavaScript, start here:

Hello World in Node.js: Your first CLI application #

No tutorial can work without “Hello world”, right? So here’s the infamous example for “Hello world” in Node.js.

In Node.js we can use console.log to write messages to system prompt / shell:

1
2
3
#!/usr/bin/env node

console.log("Hello, World!");

Hint: The first line (shebang) is optional.

Save this (e.g. as main.js) and run it in a system prompt / shell (e.g. node main.js) and you’ll see the output.

Building a simple calculator in Node.js: A CLI example #

OK that was not too bad, wasn’t it? Now let’s start doing something more application alike. Let’s write our own calculator, that we can use on command line, something like node main.js 1 + 2 or node main.js 456 / 123. This is important so you understand how to work with command line arguments.

Hint: This is just for demo purpose. I’ll skip handling edge cases.

Read more about arrays and objects in JavaScript:

To achieve this, we first need to understand how we can get the defined arguments from within Node.js. This we’ll get as an array via process.argv. We need to keep in mind, that arrays in JavaScript are “0 based”, so in the example above, we would have this array items:

0. "node"
1. "main.js"
2. "1"
3. "+"
4. "2"

Getting the first number can be done with process.argv[2]. Since this would be a string, we can use parseInt() to convert it to an integer.

That is already the hardest part. After we’ve read both numbers and the operation, we can code our supported operations and print out the result.

Three things to keep in mind:

  1. What if not all arguments is given?
  2. What if a unsupported operation is given?
  3. What if one or both numbers are no numbers?

The length of the provided arguments can be checked with process.argv.length. For the operation, we can run through select / case statement to ensure, just implemented operations can be used. And for “no-number inputs” JavaScript has isNaN (isNaN stands for “is not a number”) that can help us.

So let’s put it all together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/usr/bin/env node

// Do we have enough arguments?
if (process.argv.length < 5) {
    console.log("Usage: <NUM> <OPERATOR> <NUM>");
    process.exit(1);
}

// Parse the arguments.
const num1 = parseInt(process.argv[2]);
const operation = process.argv[3];
const num2 = parseInt(process.argv[4]);
let result = 0;

// Do we have the two numbers?
if (isNaN(num1) || isNaN(num2)) {
    console.log("Usage: <NUM> <OPERATOR> <NUM>");
    process.exit(1);
}

// Run the calculation:
switch (operation) {
    case '+':
        result = num1 + num2;
        break;

    case '-':
        result = num1 - num2;
        break;

    case '*':
        result = num1 * num2;
        break;

    case '/':
        result = num1 / num2;
        break;

    default:
        console.log("OPERATOR must be one of: +, -, *, /");
        process.exit(1);
}

// Print result:
console.log(result);

Hint: I wrote this pretty straight-forward to focus on Node.js, not on coding patterns.

Building a html web server with Node.js #

We’ve discussed how to use Node.js to write basic command line interface applications, but what about serving a website using Node.js? At a first step, let’s simply hand out static HTML so we can use our browser to “see” our Node.js application.

Do the basics - Get familiar with HTML:

HTML - the hidden power of the WEB

Uncover the essential role of HTML in structuring web content. This post provides a foundational introduction to HTML, highlighting its crucial role in organizing information for browsers. Explore HTML document structure, the significance of head and body sections, and build a step-by-step "About Me" page. Delve into HTML with practical examples, laying the groundwork for further exploration in web development.

We’ve discussed already, how to order a domain and use some public hoster’s infrastructure to host a simple website. If now we want to serve our own website using Node.js, we need to create this on our own. For sure in a very stripped and simple way.

Host HTML on a public domain:

Effortless Website Hosting on a Budget with Namecheap

Discover how to effortlessly host your website on a small budget with Namecheap's shared hosting. Explore the process from selecting a plan to configuring SSL, and learn to upload your site for a seamless online presence.

We’ll not host our Node.js application publicly, but on our local device. We can still access it from our browser using the “domain” localhost (which will resolve to your local device) and to prevent access issues on unix systems, we’ll go with port 8080 (for sure you can change it to your likings).

Do you know about URL structure incl. domain and port already?

I’ll explain the idea behind the code first. You can find the full code below. Feel free to review it at any time.

Node.js comes with a built-in http-server. We can load it using require('http'). For the sake of ease we’ll send our HTML on whatever request that comes in, just for the showcase. Working with requests can be done with createServer() where we’ll have request as first and response as second argument. This function will be called for every incoming request.

For the response we’ll indicate that everything is OK by setting statusCode to 200 and we’ll indicate that our response is HTML by setting the header Content-Type to text/html. Next, we can send our HTML by using end() on the response object, giving the HTML code as first argument.

Finally, after implementing our “routing logic”, we need to listen to incoming requests, so we can respond with our HTML. We can use listen() on the object we received from createServer() and we can specify three arguments:

  1. The port to listen on (we’ll use localhost)
  2. The hostname to listen on (we’ll use 8080)
  3. What code to run after startup (we’ll just log the full URL)

So let’s put it all together:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env node

const http = require('http');

const server_host = 'localhost';
const server_port = 8080;

const html = `
<!DOCTYPE html>
<html>
<head>
    <title>Hello World</title>
</head>
<body>
    <h1>Hello World</h1>
</body>
</html>
`;

const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');
    res.end(html);
});

server.listen(server_port, server_host, () => {
    console.log(`Server running at http://${server_host}:${server_port}/`);
});

Hint: I wrote this pretty straight-forward to focus on Node.js, not on coding patterns.

Save this (e.g. as server.js) and run it in a system prompt / shell (e.g. node server.js) and you’ll see the output:

1
Server running at http://localhost:8080/

You’ll recognise, the program will not stop, it’ll stay running until you stop it (CTRL + C / ^ + C). As long as this program is running, you can use your browser to access the shown URL.

nodejs serving html to browser
Hello world website served by Node.js

Building a web based calculator with Node.js #

In software development it’s about putting together learnings in a way that suits your project goal. We’ve done a calculator on CLI and we’ve done a simple HTML website, why not combine both to have an interactive calculator in HTML? This chapter is important so you understand how to work with query parameters and how to work with placeholders to generate dynamic responses.

Hint: This is just for demo purpose. I’ll skip handling edge cases.

HTML template #

In our previous Node.js hello world HTML website, we’ve seen how to respond a static HTML document for every request. If we now want to calculate something and show the result, we can’t always show the same, it must be dynamic, based on what should be calculated.

Do you know HTML?

HTML - the hidden power of the WEB

Uncover the essential role of HTML in structuring web content. This post provides a foundational introduction to HTML, highlighting its crucial role in organizing information for browsers. Explore HTML document structure, the significance of head and body sections, and build a step-by-step "About Me" page. Delve into HTML with practical examples, laying the groundwork for further exploration in web development.

For this demonstration, I’ll work with a placeholder. So the HTML is static, but for every request we’ll replace one part with the result. This way, every request gets his proper HTML document as a response.

We need a form, so the user can put in two numbers and select one calculation method. Finally, as said, we need our placeholder, that we can replace.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Calculator</title>
</head>
<body>
<h1>Calculator</h1>

<form action="." method="get">
    <input type="number" name="number1" placeholder="Number 1"/>

    <select name="operator">
        <option value="+">+</option>
        <option value="-">-</option>
        <option value="*">*</option>
        <option value="/">/</option>
    </select>

    <input type="number" name="number2" placeholder="Number 2"/>

    <button type="submit">=</button>
</form>

{{result}}
</body>
</html>

As you can see, our placeholder is {{result}}. This text will not be shown to the user, since we’ll replace it.

Calculation logic #

We’ve done this part in our Node.js CLI calculator already: Calculation based on user inputs. Now, let’s “extract” the logic into it’s own function, so we can use it when generating our response.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function calculate (number1, operator, number2) {
    switch (operator) {
        case '+':
            return number1 + number2;

        case '-':
            return number1 - number2;

        case '*':
            return number1 * number2;

        case '/':
            return number1 / number2;
    }
}

Hint: This is just for demo purpose. I’ve skipped handling edge cases.

Handling requests #

On each request, we need to know if the inputs are provided or not. Based on this decision, we either run the calculation logic and replace the placeholder with the result or we replace it with nothing (empty string).

flowchart TD Start["request"] --> CheckValues{"inputs provided?"} CheckValues -- "request with inputs" --> ResponseWithResult["replace placeholder with result"] CheckValues -- "request without inputs" --> ResponseWithoutResult["remove placeholder"]

Now it’s time to modify createServer so that the calculator can be used.

Our HTML form will send inputs as query parameters so we need to check if they do exist. If so, we run the logic and use the result. Node.js comes with a built-in URL parser. We can load it using require('url').

Do you know about URL query parameters?

To replace our placeholder we can use the replace function that JavaScript provides.

Since JavaScript is untyped, we need to ensure that our numbers are interpret as such. We can do this by using parseInt. On the other hand, we should ensure that our calculation result is understood as a string. We can do this by using toString.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');

    const parsedUrl = url.parse(req.url, true);
    const query = parsedUrl.query;

    if (query.number1 && query.operator && query.number2) {
        let calculationResult = calculate(
            parseInt(query.number1),
            query.operator,
            parseInt(query.number2)
        );
        res.end(
            html.replace('{{result}}', calculationResult.toString())
        );
        return;
    }

    res.end(
        html.replace('{{result}}', '')
    );
});

Full implementation #

Here’s the full code of this example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#!/usr/bin/env node

const http = require('http');
const url = require('url');

const server_host = 'localhost';
const server_port = 8080;

const html = `
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Calculator</title>
</head>
<body>
    <h1>Calculator</h1>
    
    <form action="." method="get">
        <input type="number" name="number1" placeholder="Number 1" />
        
        <select name="operator">
            <option value="+">+</option>
            <option value="-">-</option>
            <option value="*">*</option>
            <option value="/">/</option>
        </select>
        
        <input type="number" name="number2" placeholder="Number 2" />
        
        <button type="submit">=</button>
    </form>
    
    {{result}}
</body>
</html>
`;

function calculate (number1, operator, number2) {
    switch (operator) {
        case '+':
            return number1 + number2;

        case '-':
            return number1 - number2;

        case '*':
            return number1 * number2;

        case '/':
            return number1 / number2;
    }
}

const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');

    const parsedUrl = url.parse(req.url, true);
    const query = parsedUrl.query;

    if (query.number1 && query.operator && query.number2) {
        let calculationResult = calculate(
            parseInt(query.number1),
            query.operator,
            parseInt(query.number2)
        );
        res.end(
            html.replace('{{result}}', calculationResult.toString())
        );
        return;
    }

    res.end(
        html.replace('{{result}}', '')
    );
});

server.listen(server_port, server_host, () => {
    console.log(`Server running at http://${server_host}:${server_port}/`);
});

Save this (e.g. as server.js) and run it in a system prompt / shell (e.g. node server.js) and you’ll see the output:

1
Server running at http://localhost:8080/

As before, the program will not stop, it’ll stay running until you stop it (CTRL + C / ^ + C). As long as this program is running, you can use your browser to access the shown URL.

nodejs calculator application in browser
Calculation form

nodejs calculator application with inputs filled
Calculation form with inputs

nodejs calculator application showing calculation result
Calculator showing result

Creating a CLI log application with Node.js #

Node.js has way more interesting features than I can show in this article, but there’s definitely one last topic to talk about: Accessing files. Since Node.js is a “normal” application on your computer, you can do this as well. Creating, reading, writing and deleting files in Node.js is fundamental so you can persist data.

In this chapter we’ll write a basic logging application, that we can use on command line, something like node main.js Hello world or node main.js Some important information. Every log message should be written on a new line into a file called messages.log in the current directory and it should start with current date and time.

Node.js provides functions to access the file system. We can load it using require('fs'). Node.js also provides functions to work with path’s. We can load it using require('path').

I’ll explain the idea behind the code first. You can find the full code below. Feel free to review it at any time.

Our log message should contain all arguments passed to the process but ignore the first two (which would be node and script path): We use process.argv to get all arguments, ignore first two by using slice(2) and combine them into one string, seperated by spaces by using join(' '): process.argv.slice(2).join(' ').

To build up the log message file path, we first need to know the current directory. We can load it from process.cwd() (cwd stands for “current working directory”). We now can use path.join to generate the full log message file path: path.join(process.cwd(), 'messages.log').

When it comes to date and time in a technical but still readable way, I like ISO8601. We can get the current date and time formatted this way in by using new Date().toISOString(), which is quiet handy.

Now, to append a file, we can use fs.appendFile. It needs 3 arguments:

  1. The path of the file, that should be appended to
  2. The string that should be appended
  3. A function that is called after it was done. This function has one argument which is the error (if any) that was seen during the update (undefined if no error was seen).

Keep in mind, errors are possible. On the file system you probably see permission issues or the disk simply is full. We’ll keep it simple: If there was no error we don’t do anything. If there was an error, we’ll write the error to the console.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

// Do we have enough arguments?
if (process.argv.length < 3) {
    console.log("Usage: <MESSAGE>");
    process.exit(1);
}

const message = process.argv.slice(2).join(' ');
const logFilePath = path.join(process.cwd(), 'messages.log');
const timestamp = new Date().toISOString();

// Append the message to the log file.
fs.appendFile(logFilePath, `[${timestamp}] ${message}\n`, (err) => {
    if (!err) {
        return;
    }

    // Handle the error.
    console.error("Error writing to file:", err);
    process.exit(1);
});

Save this (e.g. as logger.js) and run it in a system prompt / shell two or three times (e.g. node logger.js test) and you shouldn’t see any output (we just print errors). But now you should see a new file messages.log, and if you show it (e.g. cat messages.log), you should see

1
2
3
[2024-12-22T09:41:13.988Z] test1
[2024-12-22T09:41:14.958Z] test2
[2024-12-22T09:41:15.882Z] test3
Oliver Lippert
Author
Oliver Lippert
I care for details. I think about functions and their meaning. What should they do, what not? I think about variable names. I read commit messages. All this with long-term thinking. It’s easy to write code today, but Projects get harder as longer they run. This drives me in many discussions and code reviews. +15 Years of experience has told me a lot. I reflect that mindest in business discussions as well.