I worked through roughly the first half of the 100Devs material earlier this year & part of last year & then took a break for a bit. This week, I resumed by watching the 6-hour JavaScript Super Review set between classes 34 & 35. Yesterday, I watched the video for class 35 & I started doing the homework today.
Class 35 is an intro to Node.js, promises & async/await. This post will walk through part of the homework: analyzing a Node file. I edited it to add larger indentations & break up the code blocks for readability. Here’s the code:

Intro
So, what we have here is a Node.js HTTP server. Its built without using Express or any other Node framework to help simplify the code. According to the Node homepage:
Node.js® is a free, open-source, cross-platform JavaScript runtime environment that lets developers create servers, web apps, command line tools and scripts.
What this means is that Node lets us run JavaScript outside of the browser. In this particular case, we’re running it on our computers – so on our desktops or laptops or on a server. Ordinarily, we run JavaScript in our browsers. That’s essentially using it on the front-end. Running it outside of the browser, on a computer, is the basis of using JavaScript on the back-end.
Additionally, during class I learned that some of the “JavaScript” that we’ve been using isn’t really JavaScript. They’re Web APIs that are provided by the browser to provide functionality that JavaScript doesn’t have natively. Some of these included setTimeout(), setInterval(), fetch() and THE DOM itself. Here’s a list:
Just like we have Web APIs, Node provides JavaScript with Node APIs that extend its functionality. Some common ones (that are actually used in the code we have to review) include http, fs, url & querystring. Other common ones are https & process. And just like how we can import & use other libraries in JavaScript in the browser (like React, for example), we can import libraries & use them in Node. In the code that we review, the figlet library is imported.
Ok. Lets look at the code & try to understand what its doing.
Imports
const http = require('http')
const fs = require('fs')
const url = require('url')
const querystring = require('querystring')
const figlet = require('figlet')
Up at the top of the file, we have five import statements. Four of them bring in Node APIs. These are:
- http: this API is used to create HTTP servers & handle incoming requests & send back responses
- fs: this API gives the ability to interact with the local file system to read, write & delete files
- url: this API has methods for reading (parsing) & formatting URLs
- querystring: this API has methods for reading (parsing) query strings, which are the little bits of text that follow a URL & are started with a question mark
The last import statement is for an outside library: figlet. It converts text into ASCII art, like we used to use in the 90s on BBSes.
New versions of Node let us import libraries & other resources using the import statement, just like JavaScript does in the browser. Older versions use the older require keyword to import resources. In our code, require is used.
Creating the server
Okay. So, after the import statements, the first few lines of code create a web server & set up variables to hold URLs & query string parameters from incoming requests.
const server = http.createServer((req, res) => {
const page = url.parse(req.url).pathname
const params = querystring.parse(url.parse(req.url).query)
console.log(page)
At the bottom of the file, the last line of code invokes the server & tells it to listen for incoming requests on a specific port. It looks like this:
server.listen(8000)
When the Node file is running (using node server.js in the command prompt on the computer) it’ll call server (by executing that server.listen(8000) line) & tell it to listen for incoming requests on port 8000.
This loads up the http.createServer() object that we have above which runs the request handler function that we define in it. Every time a request is made to port 8000 on the server, that code will run, look at the request contents & respond with something.
const server = http.createServer((req, res) => {}
This line creates an HTTP server & assigns it to a variable named server. server will hold the server object that Node creates. http is a Node module that creates web servers. createServer is a method in the http module that actually sets up new web server instances.
createServer expects a callback function as its argument. It’ll call this function every time a request is sent to the server on the port its listening to. In this case, (req, res) => {} is the callback function. Its an arrow function & is used to handle requests.
The callback function takes two parameters: req (short for request) and res (short for response).
- req is an object that holds the incoming HTTP request from the client
- res is an object that holds the outgoing response that the server will send back to the client
req can hold the URL that was requested (req.url), the method that was called (GET, POST, etc.), headers and sometimes other info in the form of a request body (usually for POST requests).
res holds headers, response data & a signal to end the response (res.end).
So, when a browser or some other client sends a request to the server, Node will receive the request with server. It’ll take that request & run the (req, res) function & decide what to send back (how to respond). That’s basically the entirety of what an HTTP server does: it gets a request, parses it, sees if it has code to handle that request & sends back a response.
Reading parts of the request
Right after the server is created, the next two lines create variables that hold parts of the request data that’ll be used to figure out what to send back to the client. The first one tell the server what page or route the request came from:
const page = url.parse(req.url).pathname
url is a Node module that reads & formats URLs. .parse() breaks a URL into different pieces & stores them as an object. In the server.js file, I added the following after the page & params variables are declared: console.log(url.parse(req.url)) so that we can see what the object looks like if we run the server, go to localhost:8000 in our browser & enter “leon” in the input & click the button:
{
protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: '?student=leon',
query: 'student=leon',
pathname: '/api',
path: '/api?student=leon',
href: '/api?student=leon'
}
.pathname returns the property called pathname from the object stored in the page variable. In the above case, it would return /api. So, the value of page is /api.
Why do we do this? Because usually the page (pathname) tells us which route the client is requesting, which helps to determine what to send back as a response. We’ll get to that below.
The 2nd line tells the server what data was sent to the route in the form of query string parameters.
const params = querystring.parse(url.parse(req.url).query)
In the above, we’re grabbing the query property from the request object, turning it into an object & saving it in the params variable, just like how we grabbed the pathname property & saved it into the page variable.
This part gets the query:
(url.parse(req.url).query)
It could look like: http://localhost:8000/api?student=leon
req.url has the whole thing. .query pulls only the query property out of the response object. Finally, the querystring module parses the query string & turns it into an object, so:
querystring.parse('student=leon')
Turns into:
{ student: 'leon' }
And that object is what’s saved in the params variable. We can then access the object’s properties like params.student, which would return leon.
So, between those two variables, we know what route/page a client request came in from & what data was sent (in the shape of a JavaScript object).
Routes
On a basic level, a route is a rule that says when a specific URL is visited, respond to it with specific content or take a specific action. Think of it as cause & effect. The cause is a URL was visited. The effect is that something was sent back to the visitor (a page, a cat pic, an error message, etc.).
Routes all have a path, which tells the server what URL was visited. In our code, some of the routes are /, /otherpage & /otherotherpage.
Routes also each have a request handler. This is a block of code that determines what to do when that route is visited. Its like the back-end version of an event handler. With an event handler, you have something like a click event on a button. The “route” is the button & the event is the click, which is then answered (responded to) by a function (the event handler). On the server side, a route is visited (this is like the button being clicked) which fires off the request handler in response. Same poop. Different toilet.
Our Node code uses a bunch of if statements to define routes. For static pages, they look like this:
if (page == '/') {
fs.readFile('index.html', function(err, data) {
res.writeHead(200, {'Content-Type': 'text/html'})
res.write(data)
res.end()
})
}
In the if statement, page is the variable we defined at the top of the code. We check its value. If the value is / we run the code block above, which loads the index.html file from the computer (or server), tells the browser the kind of content it is (text/html), sends the content to the client with res.write(data) & closes out the connection with res.end().
So, if someone visits http://localhost:8000/ the above request handler fires off & sends them the index.html file.
Other static page routes do the same thing, like this one:
else if (page == '/otherpage') {
fs.readFile('otherpage.html', function(err, data) {
res.writeHead(200, {'Content-Type': 'text/html'})
res.write(data)
res.end()
})
}
The above request handler looks to see if the value of page is /otherpage. If so, it then sends back otherpage.html. Rocket surgery.
The above request handlers handle static routes. Sometimes, data is sent along with the pathname in the form of query strings. These are dynamic routes, which can return different responses based on the data (query string parameters) that was sent.
- Static route:
/,/otherpage - Dynamic route:
/api?student=leon
Static routes serve back files. Dynamic routes serve back JSON based on the query parameters.
Dynamic routes
Dynamic routes take both the page/route as well as the data sent along with the route into account when figuring out how to respond to the client.
else if (page == '/api') {
if ('student' in params) {
if (params['student'] == 'leon') {
res.writeHead(200, {'Content-Type': 'application/json'})
const objToJson = {
name: "leon",
status: "Boss Man",
currentOccupation: "Baller"
}
res.end(JSON.stringify(objToJson))
} else {
res.writeHead(200, {'Content-Type': 'application/json'})
const objToJson = {
name: "unknown",
status: "unknown",
currentOccupation: "unknown"
}
res.end(JSON.stringify(objToJson))
}
}
}
In the above code, we can see that there’s a route that responds to the /api URL. It gets that URL from the page variable defined at the beginning of the code. However, it doesn’t send back a static page. If the /api URL is hit, it then checks params for a value. That value is sent from the client along with the URL in the form of a query string parameter. Its used to send back a more customized response based on the data sent.
In our case, we can see that if params has a JSON key called student with a value of leon, it will send back a specific response. If it gets any other data than leon, it sends back a different response.
The request would look like: /api?student=leon. It causes the server to send back a response of:
{
"name": "leon",
"status": "Boss Man",
"currentOccupation": "Baller"
}
Other student query strings would get this response instead:
{
"name": "unknown",
"status": "unknown",
"currentOccupation": "unknown"
}
That’s the basics of responding to dynamic routes. They can get more complicated with things like secured routes or reading something from a database before sending back a response or communicating with another API – like to send payment information, but under all of those complexities, the pattern is still the same – get data in some way from the associated route & use it to form a response to send back.
Fallback handler
Finally the last request handler we have is the one that responds to requests for anything other than the specific routes defined in the other request handlers. Its essentially an error handler. It sends back a message to let the visitor know that whatever they’re looking for isn’t available on the server or wasn’t requested in the right way.
In this case, it also uses an outside library called figlet to format the response into ASCII art instead of merely ending back an error message or directing the browser to a page.
else {
figlet('404!!', function(err, data) {
if (err) {
console.log('Something went wrong...')
console.dir(err)
return
}
res.write(data)
res.end()
})
}
We can see that the above catch-all request handler sends back an error message which gets logged to the console & also sends back the data that was used in the request, along with ending the request.
All of these routes make up the bulk of the server code. The other key pieces are the imports, setting up the server & then invoking it. This is all done with Node, which is fairly declarative, causing us to have to write the instructions for each route very granularly. Libraries like Express abstract some of this away, much like how React includes methods to perform common tasks. Node gives us more control & Express builds on Node with its own methods to give us convenience, but its still Node under the hood.