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.
1.
2├── data.txt
3├── package-lock.json
4├── package.json
5└── server.js
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:
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:
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:
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:
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 aGET
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.
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,
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:
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.