Mohamed Yahia
Posted on June 17, 2023
Chapter 3, Here we go!! In this chapter, we will learn how to create a server that receives requests from the client and sends back responses, what are ports, and how to do simple routing.
What are we waiting for, LETS DIVE IN!
We talked in the last chapter about how the web works and we learnt that when we enter a url in the browser it send a request to the server, in which the server responds with whatever it is configured to respond with to the client.
WHAT IS A CLIENT??
Here we refer to the web browser which sent the request as the client as we will serve it (or do a service for it like clients in real life) a file which mostly is the website HTML file. so a web client is just something that get served, CAN IT BE ANOTHER SERVER??
Yes, exactly, it doesnt have to be a user browser, may be the server which serves us the HTML file has to request some data from a database which will be hosted (or saved) on a server, then it will have to request the data needed from the database server. In that case, the backend server (server which serves us the HTML file) is a web client.
Simply put a web client is a computer that sends requests and a web server is a computer that sends responses. But for simplicity, computers which its purpose is to servers are referred as servers no matter what their current state are (requesting or responding) and browsers are the clients.
I HAVE HEARD ABOUT HTTP SERVERS. WHAT ARE THOSE??
This is the server that we talked about that sends us back the html file.
WHY IS IT CALLED AN HTTP SERVER??
Well, requests and responses (web communication) is standardized by HTTP Protocol and since that server is configured to handle HTTP Requests/Responses, we call it an HTTP server.
ARE THERE OTHER KINDS OF SERVERS??
Of course, there are FTP Servers which handle requests sent under FTP (File System Protocol). They are used for transferring data (downloading or uploading files from or to the server [see just another computer]), SMTP servers (for transferring emails), database servers and so on.
OK, OK, LETS GET BACK TO HTTP SERVERS. I WANT TO MAKE A WEBSITE DO I HAVE TO BUY A CERTAIN COMPUTER THAT WILL WORK AS A SERVER.
No, No, any computer can work as a server, but yes, if you are serving a website in which many many people are visiting it, you will need a very capable computers to handle that much requests, computers which are very capable and are designed to be used as servers are called servers in the hardware world. We usually rent them too and dont buy them ourselves. Look at the image below.
A row of computer servers in a server rack. Attribution: CSIRO. Licensed under CC-BY-3.0.
As you can see, those are several servers, and they dont have a screen because we dont need a screen, they usually have a capable cpu, much more memory and disk space than ordinary computers.
Now, we dont need a server of this kind to do a simple website just for us to view. We can create a server on our computer and use that as well, for the moment, we will create a server that serves files locally (only our computer can access). CAN I USE MY COMPUTER WHILE IT IS WORKING AS A SERVER.
Of course, just you are running multiple programs at the same time right now, computers can do multiple things at the same time.
OKAY, LET'S CREATE A SERVER.
Yes, using your favourite IDE (may be for you, it is a simple notepad app), create a .js file with any name you like and lets get writing code.
First we will import a core module of nodejs that contains methods (functions) for creating an http server and dealing with requests and responses, and that is the http
module.
const http = require('http')
Then we will use a method on that http object (since http variable will be the a reference to the object that is returned from the require function) called createServer
. createServer
takes a function parameter, this function is run every time a request is sent to the server.
That parameter function takes two parameters, you can specify any names to them but essentially the first param (short for parameter) will be a request object (an object containing key values pairs or information basically about the request that the server received) and nodejs handles that response object automatically so it will be available for us in the request listener/handler function (the function that is run every time a request is sent to the server). I will call the first param req
and that is the convention.
The second param will be a response object also pre-configured by nodejs in which we will add our response. I will name it res
.
const server = http.createServer(requestListener)
function requestListener(req, res) {
// configure our response here
}
Note that when calling functions like
require
we are assigning their return value to a variable. We dont have to do that. we can chain them like this:
const server = require('http').createServer(requestListener)
But we dont do that for readability purposes and specifically for two readability reasons.
- any statements that import modules should be at the top of the file
- chaining here is not a readability problem but if there are multiple chains then the statement will get very long and not very readable
LETS GET BACK.
Alright, now that server (or to be specific that 1-service server) is not doing anything, it is just there, we have to configure it to listen for requests. WHAT DO YOU MEAN BY 1 SERVICE??
Well, typically a real server (which is a computer) will have a firewall, so when we send a request to it, by default it blocks it, on that server you might have multiple services running (web service for providing websites, database service, mail service for sending and receiving mails) and each service has a port number associated with it, you can open ports (holes) in the firewall so that when a request is sent holding remote (server service) port number, the server will know where to direct that request (to the web service here).
When we enter a url, the ip address of the server gets resolved by the dns server and also the port is sent as part of the url in this format 142.251.143.100:443
where the part preceding the :
is the ip address and 443
is the port number. The request object will have a headers object which contains a host
key whose value is that part 142.251.143.100:443
If the port(hole in the firewall) is open, it will get sent to the service with the associated port number. Now, in our case, to do that, we configure the service (or the server since it is a 1-service server) to 'listen' on port 3000
, which means it will get assigned a port number and the firewall will allow requests which hold that port number in the url to pass to the designated service.
To do that, we write
server.listen(3000)
That number is arbitrary we could use any number within a certain range 0-65,535 but ports from 0-1023 are reserved for system processes so to be in the clear, i am using 3000. WHY 65,535??
You are so curious, ok, i will tell you. The port number is of data type integer but only a 16-bit number is allowed, that means there are only 16 digits and each digit is 0 or 1 so two possibilities among 16 digits gives us 65,536 possibilities and since we are counting from 0, the highest number will be 65,535.
Now returning to the topic, with those statements:
const server = http.createServer(requestListener)
function requestListener(req, res) {
// configure our response here
}
server.listen(3000)
if we run this, the js file will keep running since there is an event listener listening for requests and its listening indefinitely. Now in the browser we have to type an address so what will it be?
We will type the ip address and the port number in the same as this 142.251.143.100:443
. But what will it be?
There is a special ip address 127.0.0.1
that refers to the self (same computer we are sending that request from). It is also called the 'loopback' address. So the address will be 127.0.0.1:3000
and this will send a request to the same machine and the request will get directed to the process with the port number 3000 which is the server we created.
Now that server will run the function inside the createServer function, the request object will receive the request aaaaand currently we are not doing anything in the function so a response is never sent back the browser and browser is still waiting so we will get a blank page with a never-ending loading icon.
To progress from this state, we will configure the function to send a response. Fortunately, nodejs gives us a response object as a second parameter so we can work it.
function requestListener(req, res) {
res.setHeader('Content-Type', 'text/html')
res.statusCode = 200
res.write('<html><body>HELLO FROM THE OTHER SIIIIDE</body></html>)
res.end()
}
WHAT IS THAT??
Let me explain,
res.setHeader('Content-Type', 'text/html')
so now in the response, we have to tell the browswer what kind of file are we sending, are we sending a video back, are we sending an html file, what is the type of the content that is sent back.
This will add a key value pair in the headers object that is part of the response object specifing that the content type of the response is html
res.statusCode = 200
this line sets status code to be 200, which in networking is a standard for 'your request has been handled successfuly and we sent you the requested response'
this line is not mandatory because it is set by default to 200 when the requested response is sent to the client
res.write('<html><body>HELLO FROM THE OTHER SIIIIDE</body></html>)
here we are writing the response body, which is the html content that will be sent back to the client
res.end()
then we need to end the response, to let the browser know that that's it and there is no additional response that will be sent. After we end it we cannot res.write() anything else because the response has been sent already and if we did this, we will get an error
WHAT IF WE WANT TO SEND DIFFERENT RESPONSES BASED ON THE URL PATH, GOOGLE.COM/MAPS
RESPONSE DIFFERS FROM GOOGLE.COM
Excellent question, then we would do a simple if statement:
function requestListener(req, res) {
if (req.url === '/') {
res.write('main page')
} else if (req.url === '/maps') {
res.write('maps page')
}
}
req.url
value is the slash /
after google.com
in google.com/
and anything after it, be it maps
or any other path. In google.com/
the path is /
, in google.com/maps
, path or route is /maps
and that will return true when checking for strict equality. What we did is called routing
, because we are sending a response based on the path (or route) in the request.
And that's it for this chapter. Hope you enjoyed it. If you have any questions, please feel free to ask them below. Additionally, I welcome any corrections or valuable information you may have to contribute.
The content of this series draws inspiration from Maximilian Schwarzmüller's Node.js course on Udemy, serving as an index for the topics covered. However, it should be noted that the content reflects my personal interpretation and additional research conducted by me.
Posted on June 17, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.