We dicuess about how the front-end consists of the information sent to a client so that a user can see and interact with a website, but where does the information come from? The answer is web server.
A web server is a process running on a computer that listens for incoming requests for information over the Internet and sends back responses. Each time a user navigates to a website on their browser, the broswer makes a request to the web server of that website. Every website has at least one web server. A large company like Facebook has thousands of powerful computers running web servers in facilities located all around the world which are llistening for requests, but we could also run a simple web server from our own computer.
The specific format of a request (and the resulting response) is called the protocol. You might be familiar with the protocol used to access websites: HTTP. When a visitor navigates to a website on their browser, similarly to how one places an order for takeout, they make an HTTP request for the resources that make up that site.
For the simplest websites, a cllient makes a single request. The web server receives that request and sends the cllient a response containing everything needed to view the website. This does not mean the website is not interactive. As with the individual static assets, a website is static because once those files are received, they do not change or move. A static website might be a good choice for a simple personal website with a short boi and family photos. A user navigating Twitter, however, wants acess to new content as it’s created, which a static webiste could not provide.
A static website is like ordering takeout, but modern web applications are like dining in person at a sit-down restaurant. A restaurant patron might order drinks, different courses, make substitutions, or ask questios of waiter. To accomplish this level of complexity, an equally complex back-end is required.
Storing Data
There are many different databases, but we can divide them into 2 types:
relational databases and non-relational databases (also known as NoSQL databases).
Relational databases store information in tables with columns and rows.(MySQL, PostgreSQL)
Non-relational databases might use other systems such as key-value pairs or a document storage model. (MongoDB, Redis)
In addition to the database itself, the back-end needs a way to programmatically access, change, and analyze the data stored there.
The Node REPL
REPL is an abbreviation for read-eval-print loop. It is a program that loops, or repeatedly cycles, through three different states: a read state where the prgram reads input from a user, the eval state where the program evalluates the user’s input, and the print state where the program prints out its evaluation to a console. Then it loops through these states again.
By default, you indicate the input is ready for eval when you hit enter. If you’d like to type multiple lines and then have them evaluated at once you can type .editor while in the REPL. Once in “editor” mode, you can type CONTROL D when you’re ready for the input to be evaluated. Each session of the REPL has a single shared memory; you can access any variables or functions you define until you exit the REPL.
The Node environment contains a number of Node-specific global elements in addition to those built into the javascript language. Every Node-specific global property sits inside the the Node global object. This object contains a number of useful properties and methods that are available anywhere in the Node environment.
Node Package Manager
NPM, stands for Node Package Manager, is an online collection, or registry, of software. Developers can share code they are written to the registry or downloaded code provided by other developers.
One package we like is nodemon. It’s a powerful tool for development in Node that watches all the files in a project you’re working on, and automatically restarts your application when any of them change.
Event-Driven Architecture
Node is often described as having event-driven architecture. In the traditional imperative programming, we give the computer a series of instructions to execute in predefined order. In contrast, when we write web applications, we often need to write logic to handle situations without knowing exactly when they will occur. For example, when programming a website, we might provide functionality for a click event without knowing when a user will trigger it. When Node was created, it applied this same concept of event-driven principles to the back-end environment.
Node provides an EventEmitter class which we can access by requiring in the events core module:
// Require in the 'events' core module
let events = require('events');
// Create an instance of the EventEmitter class
let myEmitter = new events.EventEmitter();
Each event emitter instance has an .on method which assigns a listener callback function to a named event. The .on() method takes as its first argument the name of the event as a string and, as its second argument, the listener callback function.
Each event emitter instance also has an .emit() method which announces a named event has occurred. The .emit() method takes as its first argument the name of the event as a string and, as its second argument, the data that should be passed into the listener callback function.
Asynchronous JavaScript with Node.js
In server-side development, we often perform time-consuming tasks such as reading files or querying a database. Instead of halting the execution of our code to await these operations or using multiple threads like other back end environments, Node was designed to use an event loop like the one used in browser-based JavaScript execution. The event-loop enables asynchronous actions to be handled in a non-blocking way.
User Input/ Output
Output is any data or feedback that a computer provides (like to a human user), while input is provided to the computer. In the Node environment, the console is the terminal, and the console.log() method is a “thin wrapper” on the .stdout.write() method of the process object. stdout stands for standard output.
In Node, we can also receive input from a user through the terminal using the stdin.on() method on the process object:
process.stdin.on('data', (userInput) => {
let input = userInput.toString()
console.log(input)
});
Here, we were able to use .on() because under the hood process.stdin is an instance of EventEmitter. When a user enters text into the terminal and hits enter, a ‘data’ event will be fired and our anonymous listener callback will be invoked. The userInput we receive is an instance of the Node Buffer class, so we convert it to a string before printing.
Errors
The Node environment has all the standard JS errors such as EvalError, SyntaxError, RangeError, ReferenceError, TypeError, and URIError…
Within our own code, we can generate errors and throw them, and, with synchronous code in Node, we can use error handling techniques such as try… catch statements.
There is important difference - “error-first” approach is for asynchronous calls, try-catch is for synchronous.
Filesystem
const fs = require('fs');
let secretWord = null;
let readDataCallback = (err, data) => {
if (err) {
console.log(`Something went wrong: ${err}`);
} else {
console.log(`Provided file contained: ${data}`);
}
};
//fs.readFile('./fileOne.txt', 'utf-8', readDataCallback);
//fs.readFile('./anotherFile.txt', 'utf-8', readDataCallback);
fs.readFile('./finalFile.txt', 'utf-8', readDataCallback);
Readable Streams
In more realistic scenarios, data is not processed all at once but rather sequentially, piece by piece, in what is known as a stream. Streaming data is often preferable since you do not need enough RAM to process all the data at once nor do you need to have all the data on hand to begin processing it.
One of the simplest uses of streams is reading and writing to files line-by-line. To read files line-by-line, we can use the .createInterface() method from the readline core module. .createInterface() returns an EventEmitter set up to emit ‘line’ events:
const readline = require('readline');
const fs = require('fs');
const myInterface = readline.createInterface({
input: fs.createReadStream('text.txt')
});
myInterface.on('line', (fileLine) => {
console.log(`The line read: ${fileLine}`);
});
Let’s walk through the above code:
We require in the readline and fs core modules.
- We assign to myInterface the returned value from invoking readline.createInterface() with an object containing our designated input.
- We set our input to fs.createReadStream(‘text.txt’) which will create a stream from the text.txt file.
- Next we assign a listener callback to execute when line events are emitted. A ‘line’ event will be emitted after each line from the file is read.
- Our listener callback will log to the console ‘The line read: [fileLine]’, where [fileLine] is the line just read.
Writable Streams
In the previous exercise, we were reading data from a stream, but we can also write to streams! We can create a writeable stream to a file using the fs.createWriteStream() method:
const fs = require('fs')
const fileStream = fs.createWriteStream('output.txt');
fileStream.write('This is the first line!');
fileStream.write('This is the second line!');
fileStream.end();
In the code above, we set the output file as output.txt. Then we .write() lines to the file. Unlike a readable stream, which ends when it has no more data to read, a writable stream could remain open indefinitely. We can indicate the end of a writable stream with the .end() method.
Let’s combine our knowledge of readable and writable streams to create a program which reads from one text file and then writes to another.
Create an HTTP Server
Node was designed with back end development needs as a top priority. One of these needs is the ability to create web servers, computer processes that listen for requests from clients and return responses. A Node core module designed to meet these needs is the http module. This module contains functions which simplify interacting with HTTP and streamline receiving and responding to requests.
The http.createServer() method returns an instance of an http.server. An http.server has a method .listen() which causes the server to “listen” for incoming connections. When we run http.createServer() we pass in a custom callback function (often referred to as the requestListener). This callback function will be triggered once the server is listening and receives a request.
Let’s break down how the requestListener callback function works:
The function expects two arguments: a request object and a response object.
Each time a request to the server is made, Node will invoke the provided requestListener callback function, passing in the request and response objects of the incoming request.
Request and response objects come with a number of properties and methods of their own, and within the requestListener function, we can access information about the request via the request object passed in.
The requestLi