Beginner's guide to creating a Node.js server
Lisa Jung
Posted on August 29, 2020
As full stack developers, we use multiple programming languages to build the frontend and backend of our apps. I often find myself mixing the syntax of JavaScript and Ruby as I switch back and forth between frontend and backend.
What is a programmer to do to keep everything straight?!
Node.js solves this exact pain point. It allows JavaScript developers to write both the client-side and server-side code without having to learn a completely different language.
But what exactly is Node.js? When you look up Node.js, you will see it defined as a JavaScript runtime built on Chrome's V8 JavaScript engine(definition from Node.js).
If you only recognized the words JavaScript, Chrome, and engine from that definition and still cannot grasp what Node.js is, you are not alone. I was on the same boat and thought it is about time I find out what Node.js is all about.
So let's get to it!
By the end of this blog, you will be able to:
- define Node.js in your own words
- learn enough basics to create a Node.js server and create routes that handle different http requests.
What is Node.js?
Node.js is a free, open-sourced, cross-platform JavaScript run-time environment that lets developers write command line tools and server-side scripts outside of a browser(excerpt from Node.js).
Don't worry if this definition does not make sense yet. These concepts will be explained in detail in the following sections.
History of Node.js
JavaScript is a programming language originally developed to run only in the browser. It manipulates the DOM and adds interactivity to your website.
Javascript is executed by Javascript engines. Basically, this engine takes JavaScript code and compiles it into machine code that computers can work with more efficiently. There are multiple Javascript engines available. However, Node.js selected the V8 engine developed by Google to run Javascript.
As JavaScript grew more in popularity, major browsers competed to offer users the best performance. More development teams were working hard to offer better support for JavaScript and find ways to make JavaScript run faster. Around that time, Node.js was built on V8 JavaScript engine(excerpt from Node.js) and gained popularity among developers for the following reasons.
Defining characteristics of Node.js
Characteristic #1 With Node.js, you can write server-side code with JavaScript
Like JavaScript, Node.js runs on V8 JavaScript engine. The creators of Node.js took the V8 code base and have added multiple features to it. These features have made it possible for Node.js users to build servers with JavaScript.
With Node.js, you can now build a server that connects to the database to fetch and store data, authenticates user, validates input and handles business logic.
Characteristic #2 Node.js is not limited to the server. You can use Node.js for utility scripts or for building tools.
While Node.js is most commonly used for web development and server-side code, you can do other things with it! Because Node.js is a JavaScript runtime, you can execute any JavaScript code with Node.js.
For example, Node.js has the ability to access the file system so it can read, write and manipulate files. This feature allows you to use Node.js to handle a lot of utility tasks on your computer without exposing files to the public.
Characteristic #3 Node.js uses an event driven code for running your logic. Because of that, JavaScript thread is always free to handle new events and new incoming requests.
Node.js involves a lot of asynchronous code, meaning that it registers callbacks and events to be executed in the future instead of being executed right away. This characteristic is what allows Node.js to run in a non-blocking fashion and it is what makes Node.js apps to be very performant.
Now that we have covered the basic concepts, let's get our hands dirty and build a server with Node.js!
Creating a server and routes with Node.js
This is what we will be building!
We will be creating a very simple server that can handle requests from a browser.
On the browser side, the user will be greeted with a welcome message and will be asked to submit their mood through a form.
The server will receive the user input and it will create a file to store user input.
We will be accomplishing all of these tasks without the help of frameworks like Express. This may be a harder way to learn Node.js but it will help us understand how Node.js actually works under the hood!
After mastering the concepts in this blog, check out my next blog on how to create a Node.js server using Express as a framework. It will give you a greater appreciation for Express as it will accomplish a lot of the work we will be doing in this blog with fewer lines of code!
Prerequisite Download
Download Node.js here. Save it and run the installer.
The code for the server is included in this GitHub repo. Feel free to refer to it if you encounter a bug while you follow along!
Step 1: Create a directory for our server
In the appropriate directory, type the following in your terminal to create a directory for our server.
mkdir All_The_Feels
Get into All_The_Feels directory and open it up in your text editor.
cd All_The_Feels
code .
Step 2: Create server.js and routes.js files within All_The_Feels directory
In your terminal, execute the following command.
touch server.js routes.js
You will see that server.js and routes.js files have been created within your directory.
In the server.js file, we will we will import all the necessary components to set up a server. Server will be set up to listen for client requests.
In the routes.js file, we will build routes to handle various client requests and send an appropriate response to the browser. We will also be writing code here to save user input in a separate file in our server.
We will first focus on server.js. The final version of server.js has been provided for you in the image below. Steps 3-5 will include corresponding lines of code specified in the image so you can easily follow along!
Step 3: Import http module in server.js
There are several core modules available in Node.js. Among these, http core module has the ability to launch a server.
To use the features of http module, we need to import it into server.js by using the require() keyword. In server.js, create a http constant and require http as shown below.
#In server.js(line 1)
const http = require('http')
Now we can use the features of http module!
Step 4: Import routes into server.js and create a server
One of the functionalities of http module is the createServer() method. This method creates a server and accepts a requestListener function that has two parameters: HTTP request(req) and response(res).
However, we will be passing routes here instead as we will be defining requestListener in routes.js. But more on that later!
Create a server by declaring server as a constant and setting it equal to createServer method and passing routes as its argument.
#In server.js(line 5)
const server = http.createServer(routes)
In order for us to pass routes as an argument, we need to import routes.js into server.js. To do this, declare routes as a constant and require routes by providing the file path.
#In server.js(line 3)
const routes = require("./routes")
Lastly, our server needs to listen for incoming requests from the browser. We accomplish that by using the listen() method to create a listener on a specified port. Pass in 3000 as an argument in server.listen() method.
#In server.js(line 7)
server.listen(3000);
Now that we have configured server.js to create a server, let's focus on routes.js. Our goal is to create a requetListener function that takes in client request and server response as arguments. We will build routes to handle various client requests and send an appropriate response to the browser.
The final version of routes.js has been provided for you below to avoid any confusion as you follow along. The following steps will discuss the code line by line!
#in routes.js
const fs = require("fs");
const requestListener = (req, res) => {
const url = req.url;
const method = req.method;
if (url === "/") {
res.write("<html>");
res.write("<head><title>All the Feels</title></head>");
res.write(
'<body><h1>Hey there, welcome to the mood tracker!</h1><p>Enter your mood below and hit send to save your mood.</p><form action = "/mood" method="POST"><input type = "text" name="mood"><button type="submit">Send</button></body>'
);
res.write("</html>");
return res.end();
}
if (url === "/mood" && method === "POST") {
const body = [];
req.on("data", (chunk) => {
body.push(chunk);
});
return req.on("end", () => {
const parsedBody = Buffer.concat(body).toString();
console.log(parsedBody)
const mood = parsedBody.split("=")[1];
fs.writeFile("user_mood.txt", mood, () => {});
return res.end();
});
}
};
module.exports = requestListener;
Step 5: Create a requestListener in routes.js and export routes
In routes.js, copy and paste the following.
# in routes.js
const requestListener = (req, res) => {
console.log(req)
};
module.exports = requestListener;
Let's break this down!
We will start with the last line of code:
module.exports = requestListener;
Earlier in step #4, I mentioned that createServer() method in server.js accepts a requestListener function.
#In server.js(line 5)
const server = http.createServer(routes)
However, we passed routes as an argument instead as we are defining requestListener in routes.js.
We need to export routes file so that routes could be imported into server.js. We do that by using the module.exports keyword.
module.exports = requestListener;
Let's get back to the top of the code!
requestListener is a function that is executed whenever the server receives an incoming request. This function takes in two arguments:
- request: incoming
- response: serverResponse
# in routes.js
const requestListener = (req, res) => {
console.log(req)
};
module.exports = requestListener;
Request and response are abbreviated as req and res as shown in the code sample above. Both request and response are objects that contain a lot of information about the request(req) sent from the browser and the response(res) that server sends to the browser.
In the current code, I have included console.log(req) here to show you what a typical request from a browser looks like. To view the req, fire up the server by running the following command in the terminal.
#in terminal
node server.js
Open up a chrome browser and type in localhost:3000 in the url bar. Nothing should be showing on the page at the moment. Go back to your text editor.
In the terminal, you will see a req object that includes a ton of information as key value pairs.
For the purpose of this tutorial, we will be focusing on keys- url, method, and headers in the request. To view what these look like, replace the code in routes.js with the following.
#in routes.js
const requestListener = (req, res) => {
console.log(req.url, req.method, req.headers)
};
module.exports = requestListener;
With the current set up we have, we have to manually restart our server every time we want to see the results after making changes to our code. There are tools that does this for you but for the purpose of this blog, we will exit the server by hitting control + c on your keyboard and restart the server by typing node server.js in your terminal.
Refresh your browser and go back to your text editor.
You will see the following in your terminal.
The url in the request object is highlighted with a red box. "/" denotes that localhost:3000 is making the request. If the url of the browser was "localhost:3000/moods", "/moods" should be displayed as the url in the request object.
The method of the request is highlighted with a blue box. Since we have not specified method on the browser side, it is going to send a default GET request to our server.
The {} contains the header. It includes information about the host, which browser we used for that request, and what type of request we would accept and etc.
Step 6: Configure a "/" route to display a greeting message and a form that takes user input
Our browser(localhost:3000) is sending a GET request to our server but the browser is not displaying anything because our server is not sending back a response. As we will not be writing front end code in this tutorial, we will send some html code as a response to display in the browser.
If a user is sending a request from a localhost:3000 url, we will send html code that displays a greeting message and a form where the user can submit their mood. We will accomplish this by replacing the code in routes.js with the following code.
# in routes.js
const requestListener = (req, res) => {
const url = req.url;
if (url === "/") {
res.setHeader("Content-Type", 'text/html')
res.write("<html>");
res.write("<head><title>All the Feels</title></head>");
res.write(
'<body><h1>Hey there, welcome to the mood tracker!</h1><p>Enter your mood below and hit send to save your mood.</p><form action = "/mood" method="POST"><input type = "text" name="mood"><button type = "submit">Send</button></body>'
);
res.write("</html>");
return res.end();
}
};
module.exports = requestListener;
Let's go over this line by line!
As the url of the request will determine what response we will send to the client, we need to first grab the url from the req object.
Create a constant called url and set it equal to url in req object.
# in routes.js
const url = req.url;
If the value of url is "/"(meaning localhost:3000), we will send the following html code as a response.
# in routes.js
if (url === "/") {
res.setHeader("Content-Type", 'text/html')
res.write("<html>");
res.write("<head><title>All the Feels</title></head>");
res.write(
'<body><h1>Hey there, welcome to the mood tracker!</h1><p>Enter your mood below and hit submit to save your mood.</p><form action = "/mood" method="POST"><input type = "text" name="mood"><button type = "submit">Send</button></body>'
);
res.write("</html>");
return res.end();
}
res.setHeader() is a method that creates a header for our response. The header lets the browser know what type of content is in our response object. Since we are sending html code, we set our Content-Type to be text/html.
res.write() is a method that allow us to write the data we are going to send in a response. In Node.js, you can write html code exactly like how you would in the frontend. However, you must start every line with res.write and include the html code in parenthesis as shown above.
As you can see, we declare that we are writing html code and set the title of our browser tab to be "All the Feels".
The body tag contains multiple elements so let's break it down.
- h1 tag contains a greeting message(Hey there, welcome to the mood tracker!)
- p tag contains directions for the user(Enter your mood below and hit submit to save your mood.)
- form tag contains action and method attributes. Action attribute specifies where to send the form-data when a form is submitted. We have specified the location to be /mood. Method specifies that we are sending a POST request to the server upon form submission.
- input tag states that the type of user input will be text and input name is mood. -button tag creates a button labeled as "Send" and once it's clicked, it will send the request.
We write res.end() to signify we are finished writing the data in our response.
All right! Let's restart the server by exiting out of the server(control + C) and firing up the server(node server.js).
Go to your browser(localhost:3000), you will see the response displayed on our page!
Open up DevTools by pressing control + Shift + J on your keyboard. Click on the network tab and refresh your browser. Click on localhost under the name column(red arrow).
You will see that our get request got a status code of 200, meaning that the get request succeeded at getting appropriate data from the server(green box).
If you look at response headers(orange box), you will also see the response header we have specified in our response.
Click on response tab(red box). You will see the content of our response we have written in our server!
So far, we have been able to successfully create a route for get request and send our response to the browser. Next step is to save user's input in a separate file in our server!
Step 7: Save user's input in a separate file
Before we delve into the code, we need to get familiar with how Node.js handles data, a concept also known as streams.
Instead of waiting for the entire incoming data to be read into memory, Node.js reads chunks of data piece by piece, processing its content without keeping it all in memory(excerpt from NodeSource).
The chunks of data are further grouped into buffers. Your code can now recognize these buffers and start working with the data.
This is extremely powerful when working large amounts of data(ex.streaming videos) and it increases memory and time efficiency of your app!
Even though our user input is very small, our code will reflect how Node.js processes data.
All right, let's get to the code!
Copy and paste the following code after the previous if statement we have written.
# in routes.js
if (url === "/mood" && method === "POST") {
const body = [];
req.on("data", (chunk) => {
body.push(chunk);
});
return req.on("end", () => {
const parsedBody = Buffer.concat(body).toString();
const mood = parsedBody.split("=")[1];
fs.writeFile("user_mood.txt", mood, () => {});
return res.end();
});
}
Remember our html code for form.
# in routes.js
<form action = "/mood" method="POST"><input type = "text" name="mood">
When a user submits the form, /mood url, post method, along with input type(text) and name(mood) will be sent to the server. Since we will be saving user input only upon form submission, we will write the following if statement.
If the url and method of incoming request are /mood and post respectively, then save the user input in a separate file.
# in routes.js
if (url === "/mood" && method === "POST") {
//rest of the code
}
Instead of waiting until full incoming messages are read into memory, Node.js handles data in chunks. We will accomplish this by writing an event listener that listens for data.
In Node.js, event listeners are initiated by req.on(). The first parameter specifies the name of the event and the second parameter defines the function triggered by an event.
In the code below, we create an array called body as we are getting data from the request body. Then, we create an event listener that listens for incoming data. As soon as chunk of data is detected, it pushes the chunk into the body array.
# in routes.js
const body = [];
req.on("data", (chunk) => {
body.push(chunk);
});
We will now create an end listener. The end listener will fire once it is done parsing the incoming request data.
# in routes.js
return req.on("end", () => {
const parsedBody = Buffer.concat(body).toString();
console.log(parsedBody)
});
We have previously pushed chunks of data in a body array. To interact with these chunks of data, we first need to group the chunks in the body array into a buffer(Buffer.concat(body)).
Buffer now has to be turned into a string(.toString()) so that our code can work with the data! We will set the result equal to parsedBody.
Let's console.log the parsedBody to see what we are working with here.
Quit and start your server and refresh your browser. In the form, type in "Excited" and submit the form.
You will notice that your browser url will change to localhost:3000/moods and display a blank page. This makes sense as we do not have any html code written for /moods url.
Go back to the server terminal, you will see the following in your terminal.
# in terminal
mood=Excited
This means that the form is capturing user input and is sending it our server in request body. But we only want the mood value "Excited" to be saved in our file.
# in routes.js
const mood = parsedBody.split("=")[1];
We can achieve that by splitting parsedBody(mood=Excited) by =. This will yield an array of ["mood", "Excited"]. We can further isolate "Excited" by specifying that we want element at index position of 1 and saving that as a mood constant.
Next, we can create a file to store user input. At the very top of routes.js file, we require fs package and set it to a fs constant.
#In routes.js at the very top of the file
const fs = require("fs");
Right after const mood = parsedBody.split("=")[1], copy and paste the following.
fs.writeFile("user_mood.txt", mood, () => {});
return res.end();
At the very top of route.js, we have imported fs package. This package contains writeFile functionality which allows us to create a file and add whatever information we want to save.
fs.writeFile takes in two arguments. The first argument is the file name, "user_mood.txt". The second argument is what you want to add to the file. We will include our mood variable which contains "Excited" as its value.
Lastly, we use res.end() function to end the response process.
Let's test it out!
Stop the server and fire up the server. Go to your browser and fill out your mood in the form and hit send.
Go back to your server. You will see that a file named user_mood.txt has been created in your server. Go into the file and you will see that Excited has been saved in the file!
There you have it! This blog was full of complex concepts and coding. Major kudos to you for making it to the end.
Now go apply what you have learned and add more routes and features!
Posted on August 29, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.