How to Build a Web App with JavaScript

Finally, it is time for us to create our first real web application using JavaScript. First of all, there are two packages we must discuss.

The file system module

Let's start with the file system (fs) module. This package is built into Node.js, so you don't need to install anything. Instead, create a new server.js file for the code and a data.txt file for JavaScript to read and write.

text
1.
2├── data.txt
3├── package-lock.json
4├── package.json
5└── server.js
javascript
1import { readFile } from "fs";
2
3// Specify the file we want to read as well as the charset encoding format
4readFile("data.txt", "utf8", (error, text) => {
5  if (error) throw error;
6
7  console.log(text);
8});

We can also write to the file like this:

javascript
1import { writeFile } from "fs";
2
3writeFile("data.txt", "Hello, World? Hello, World!", (error) => {
4  if (error) {
5    console.log(`${error}`);
6  } else {
7    console.log("File written.");
8  }
9});

In this case, it is not necessary to specify the encoding format. If writeFile is given a string, it will simply assume the default format, which is UTF-8.

We are revisiting the fs module because for this project, we are going to use a text file as a database, as we will demonstrate later.

The HTTP module

Another very important module we need to talk about is http. It allows us to create an HTTP server using JavaScript. For example:

javascript
1import { createServer } from "http";
2
3let server = createServer((request, response) => {
4  response.writeHead(200, { "Content-Type": "text/html" });
5
6  response.write(`<h1>Hello, World!</h1>`);
7
8  response.end();
9});
10
11server.listen(8000); // Configure the HTTP server to listen on port 8000
12console.log("Listening! (port 8000)");

The variables request and response each represent an object storing the incoming and outgoing data. For instance, you can access the url property of the request by using request.url.

Start this server by running the following command:

bash
1node server.js

Open your browser and go to http://localhost:8000/. You should see a Hello, World! message.

This example is fairly basic, but in reality, the backend servers are usually more complex. So next, let's try something more challenging.

We are going to create a web app that asks for your name, and once you submit your name, it will be stored in a txt file, which acts as a database. When you visit the web page again, it will greet you with your name.

Creating a web app

To get started, our server.js needs a new structure:

javascript
1import { createServer } from "http";
2import { writeFile, readFile } from "fs";
3
4let server = createServer((request, response) => {
5  if (request.method === "GET") {
6    . . .
7  } else if (request.method === "POST") {
8    . . .
9  }
10});
11
12server.listen(8000);
13
14console.log("Listening! (port 8000)");

Here, we need to consider two different scenarios:

  • When you visit http://localhost:8000/, the browser will send a GET request to the backend requesting an HTML page, which contains a web form.
  • When you submit the form, that will send a POST request to the backend.

Under the request object, there is a method property that tells you what request method is used. Our server must deal with GET and POST requests differently.

Dealing with the GET request

When dealing with the GET request, you need to try to read the name from the data.txt file first.

javascript
1if (request.method === "GET") {
2  readFile("data.txt", "utf8", (error, name) => {
3    if (error) throw error;
4
5    response.writeHead(200, { "Content-Type": "text/html" });
6    response.write(`
7      <h2>Hello, ${name}</h2>
8      <p>What is your name?</p>
9      <form method="POST" action="/">
10        <p>Name: <input type="text" name="name"></p>
11        <p><button type="submit">Submit</button></p>
12      </form>
13    `);
14    response.end();
15  });
16} else if (request.method === "POST") {
17  . . .
18}

An error will be thrown if something goes wrong while reading the file. If not, a 200 OK response will be returned. The read data will be assigned to the variable name and be used to print the webpage (line 7).

Dealing with the POST request

As for the POST request,

javascript
1if (request.method === "GET") {
2  . . .
3} else if (request.method === "POST") {
4  let data;
5
6  request.on("data", function (chunk) {
7    data = chunk.toString();
8  });
9
10  request.on("end", function () {
11    let name = data.split("=")[1];
12
13    writeFile("data.txt", name, function (error) {
14      if (error) throw error;
15
16      response.writeHead(302, { Location: "/" });
17      response.end();
18    });
19  });
20}

This is when the backend receives a request to add new data, and we need to consider two different cases:

While the data is being transferred (request.on("data", . . .)), notice that there is a chunk variable inside the callback function.

This variable contains the actual data that is transferred, which is a Buffer. We will discuss more about it later. For now, you only need to know that you can convert it into a string using the toString() method, which you should already be familiar with.

The converted string is then assigned to the variable data, which has a higher scope, so it can be accessed later.

After the data has been transferred and received (request.on("end", . . .)), we get the name from the variable data. Recall that data has a key/value format, key=value, and the split() method splits the string according to the given character, and the result will be returned as an array. And the retrieved name will be stored in the data.txt file.

Finally, start the server with the following command:

bash
1node server.js

Open the browser, go to http://localhost:8000/, and submit a new name. When you refresh the page or restart the server, your name should persist.