Intro to Node.js (Part 2)
Tags: node, javascript, server, webdev
Published: 2020-12-28 04:20:38

Here is part 2 to what I've been learning about Node.js through [The Net Ninja](https://www.thenetninja.co.uk/)!
<br>

# <center>Creating a Node Server</center>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ajfiuvd143ge6stn6ymi.jpg)

If you have a basic understanding of how computers and the internet work, you know that it's a cycle of a client/browser making requests for pieces of information and receiving it from a local server/web server.  With Node, we have the ability to create our own server!

We first need to require one of Node's core modules, the HTTP module, to gain access to its built-in `createServer()` method.  This function accepts two parameters: a `request` object and a `response` object.  

The `request` object has a property of `url`, which we can later use to differentiate what type of response would be appropriate for each request. 

The `response` object has a function `writeHead()` that is used to specify what we are actually sending back as a response to the client.  This function accepts two parameters: the HTTP status code (i.e., 200 - Accepted, 404 - Not Found, 500 - Server Error, etc.) and the Content-Type to tell the browser what media type we are sending back (i.e., plain text, HTML, JSON).  Finally, we will complete the response process with the `end()` function, signaling to the server that all of the response headers and body have been sent.

To see the response in the browser, we need to listen for the port, and pass in the IP address as well.  For our purposes, we'll use the commonly used port 3000, and the local IP address (127.0.0.1). 

In the following code, we are simply creating a Node server that responds with plain text when we make the URL request through the browser.

**app.js**
```javascript
// First, we'll require the HTTP module and save it to a variable
const http = require('http');

// Next we'll call the createServer() function, which will also be saved to a variable, and then call the writeHead() function to respond with a 200 HTTP status code and plain text
const server = http.createServer(function(request, response) {
  response.writeHead(200, {'Content-Type': 'text/plain'});
  response.end('Here is the plain text you requested');
});

// Let's listen to the port so we can view our response in the browser
server.listen(3000, '127.0.0.1');
// Let's also confirm that we are indeed listening to the port by logging a message in the console
console.log('You are currently listening to port 3000');
```

Now we can run Node in the terminal:

```
$ node app
```

and we should see the message in the terminal confirming that we are listening to the port.  Go to the browser and enter the IP address and port (`127.0.0.1:3000` or `localhost:3000`), and we should now see our plain text message.  It looks like our Node server is up and running properly!

You may notice that we will continue receiving the same response no matter what we enter following the port number in the URL.  This is because we have not yet dealt with routes to differentiate between requests (we'll get to that shortly). 
<br>

# <center>The Node Stream</center>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zv61lqgboxf3hr470vhh.jpg)

Let's say we wanted to watch The Umbrella Academy through Netflix (seriously, you should check it out if you haven't yet).  Our user experience wouldn't be too great if we had to wait for an entire episode to load before being able to even begin watching it.  That is why *streaming* is so amazing when it comes to requests and responses that deal with a ton of data!

When we stream a show, movie, music, etc., we are getting bits of data at a time that is being sent from one end to another.  These bits of data fill what's called a buffer, where pieces of data are temporarily stored.  Once the buffer is full, it gets sent off to be consumed. 

In this way, instead of having to wait who-knows-for-how-long to consume data, it can be fed to us pieces at a time!  Bottom line, we get better performance. 

Node provides us with some cool tools to create our own data streams! 

## <center>Readable Stream</center>

Let's say we want to create a stream to read a large piece of data.  First, we'll need to require the File System core module to access its built-in function `createReadStream()`.  This function accepts the name of the file we are trying to read, and we'll also pass in the character encoding UTF-8 (if we don't add the character encoding, the response will just be the buffer, instead of the actual contents of the file we are trying to read).

`createReadStream()` inherits functionalities from the EventEmitter module (this is one of the core modules I went over in the [last blog post](https://dev.to/maasak/intro-to-node-js-part-1-3a2c)), so we can listen for the `data` event to actually see how the chunks of data are coming in by logging it into the console.  

**app.js**
```javascript
// We will first require the FS core module
const fs = require('fs');

// Now we can call the createReadStream() function and store it in a variable
const newReadStream = fs.createReadStream(__dirname + '/readFile.txt', 'utf8');

// To see how the chunks of data are coming in, we'll listen to the data event and log each chunk in the console
newReadStream.on('data', function(chunk) {
  console.log('Here is a chunk of data:');
  console.log(chunk);
});
```

## <center>Writable Stream</center>

A stream for writing data in Node is very similar to a stream for reading data.  After requiring the File System module, we have access to another intuitively-named method, `createWriteStream()`, which accepts a parameter for the location and name of the file where we will be writing data to. 

If we combine `createReadStream()` with `createWriteStream()`, it would look something like this: 

**app.js**
```javascript
// You know the drill - gotta require the module first
const fs = require('fs');

// Let's take the same createReadStream() we made earlier
const newReadStream = fs.createReadStream(__dirname + '/readFile.txt', 'utf8');

// This time, instead of logging in the console the chunks of data we are reading, we will utilize the createWriteStream() method to write the chunks of data we are reading into a new file
const newWriteStream = fs.createWriteStream(__dirname + '/writeFile.txt');

newReadStream.on('data', function(chunk){
  newWriteStream.write(chunk);
});
```

Once we run `node` in the terminal, we should see a new text file (`writeFile.txt`) in our current directory, and inside that file we'll now see the data from the `readFile.txt` file. 
<br>

## <center>Pipes</center>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/uvpo40g6we8s261udvpz.jpg)

It is very common to read data from a read stream and to write data to a write stream.  Instead of having to do all this manually every time, Node has a nice shortcut that takes care of this job for us, in the form of a `pipe`. 

A `pipe` simply let's us skip the steps of having to listen for data events and manually writing data to the write stream by doing all that on its own. 

We could revise our previous code above to be shorter with the help of a `pipe`:

**app.js**
```javascript
const fs = require('fs');

const newReadStream = fs.createReadStream(__dirname + '/readFile.txt', 'utf8');
const newWriteStream = fs.createWriteStream(__dirname + '/writeFile.txt');

// Instead of listening for the data event and manually writing the data to the write stream, we can replace it with the pipe() function
myReadStream.pipe(myWriteStream);
```

The above code will function the same way as the previous code we had, but this looks much cleaner!
<br>

# <center>Putting it Together</center>

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0213hp02q39i1mi2mv28.jpg)

Now let's put together everything we've learned here so far to send data to a user through the browser.

In the following example, there are various route requests.  We have one for the root/home route, an about page, an API (made-up), and for anything else an 404 error page.  Let's pretend that these HTML files have already been created separately.

We'll have conditional statements to check the requests's `url` property, and send responses accordingly.  

**app.js**
```javascript
const http = require('http');
const fs = require('fs');

const server = http.createServer(function(request, response) {
  if (request.url === '/home' || request.url === '/') {
    response.writeHead(200, {'Content-Type': 'text/html'});
    fs.createReadStream(__dirname + '/index.html').pipe(response);
  } else if (request.url === '/about') {
    response.writeHead(200, {'Content-Type': 'text/html'});
    fs.createReadStream(__dirname + '/about.html').pipe(response);
  } else if (request.url === '/api/umbrellaacademy') {
    response.writeHead(200, {'Content-Type': 'application/json'});
    response.end(JSON.stringify(badasses);
  } else {
    response.writeHead(404, {'Content-Type': 'text/html'});
    fs.createReadStream(__dirname + '/404.html').pipe(response);
  }
});

server.listen(3000, '127.0.0.1');
console.log('You are currently listening to port 3000')
```

Aaaaaaand that's about it for now.  Feel free to comment below, I'd love to hear your thoughts! 

'Til next time~~~! 

## Helpful Links

[The Net Ninja's Node.js Tutorial Videos](https://www.youtube.com/watch?v=w-7RQ46RgxU&list=PL4cUxeGkcC9gcy9lrvMJ75z9maRw4byYp)

[Node.js Documentation](https://nodejs.org/api/http.html#http_response_end_data_encoding_callback)