Built on Chrome's V8 JavaScript engine, Node.js is an asynchronous event-driven JavaScript runtime, or an environment, that allows us to execute JavaScript code outside of the browser.
A “runtime” converts code written in a high-level, human-readable, programming language and compiles it down to code the computer can execute.
A Node.js app is run in a single process, without creating a new thread for every request.
REPL
is a program that loops through 3 different states:
read
state where the program reads input from a user,eval
state where the program evaluates the user’s input, andprint
state where the program prints out its evaluation to a console.
Then it loop
s through these states again.
When you install Node, it comes with a built-in JavaScript REPL. You can access the REPL by typing the command node
(with nothing after it) into the terminal and hitting enter
.
In computer science, a process is the instance of a computer program that is being executed.
Node has a global process object with useful methods and information about the current process.
The process.env
property is an object which stores and controls information about the environment in which the process is currently running.
The process.memoryUsage()
returns information on the CPU demands of the current process. It returns a property that looks similar to this:
{ rss: 26247168,
heapTotal: 5767168,
heapUsed: 3573032,
external: 8772 }
process.memoryUsage().heapUsed
will return a number representing how many bytes of memory the current process is using.
The process.argv
property holds an array of command line values provided when the current process was initiated:
- The first element in the array is the absolute path to Node, which ran the process.
- The second element in the array is the path to the file that’s running.
- The following elements will be any command line arguments provided when the process was initiated.
- Command line arguments are separated from one another with spaces.
node myProgram.js testing several features
console.log(process.argv[3]); // Prints 'several'
Heap
can mean different things in different contexts: a heap can refer to a specific data structure, but it can also refer to the a block of computer memory.
A module is a collection of code located in a file. Instead of having an entire program located in a single file, code is organized into separate files and combined through requiring them where needed using the require()
function.
Modularity is essential for creating scalable programs which incorporate libraries and frameworks and separate the program’s concerns into manageable chunks.
Core modules are required by passing a string with the name of the module into the require()
function:
let events = require('events')
The require()
function will first check to see if its argument is a core module, if not, it will move on to different attempts to locate it.
Unlike when we require core modules which are required in with the name of the module as a string, local modules are required by passing in the path to the module.
let Dog = require('./dog')
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.
let newUserListener = (data) => {
console.log(`We have a new user: ${data}.`);
};
// Assign the newUserListener function as the listener callback for 'new user' events
myEmitter.on('new user', newUserListener)
// Emit a 'new user' event
myEmitter.emit('new user', 'Lily Pad') //newUserListener will be invoked with 'Lily Pad'
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.
process.stdout.write("I'm thinking of a number from 1 through 10. What do you think it is? \n(Write \"quit\" to give up.)\n\nIs the number ... ");
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.
The Node environment has all the standard JavaScript errors such as EvalError
, SyntaxError
, RangeError
, ReferenceError
, TypeError
, and URIError
as well as the JavaScript Error
class for creating new error instances.
Many asynchronous Node APIs use error-first callback functions: callback functions which have an error as the first expected argument and the data as the second argument. If the asynchronous task results in an error, it will be passed in as the first argument to the callback function. If no error was thrown, the first argument will be undefined
.
const errorFirstCallback = (err, data) => {
if (err) {
console.log(`There WAS an error: ${err}`);
} else {
// err was falsy
console.log(`There was NO error. Event data: ${data}`);
}
}
The Node fs
core module is an API for interacting with the file system. It was modeled after the POSIX standard for interacting with the filesystem.
Each method available through the fs
module has a synchronous version and an asynchronous version. One method available on the fs
core module is the .readFile()
method which reads data from a provided file:
const fs = require('fs');
let readDataCallback = (err, data) => {
if (err) {
console.log(`Something went wrong: ${err}`);
} else {
console.log(`Provided file contained: ${data}`);
}
};
fs.readFile('./file.txt', 'utf-8', readDataCallback);
- We required in the
fs
core module. - We define an error-first callback function which expects an error to be passed as the first argument and data as the second. If the error is present, the function will print
Something went wrong: ${err}
, otherwise, it will printProvided file contained: ${data}
. - We invoked the
.readFile()
method with three arguments:- The first argument is a string that contains a path to the file
file.txt
. - The second argument is a string specifying the file’s character encoding (usually
'utf-8'
for text files). - The third argument is the callback function to be invoked when the asynchronous task of reading from the file system is complete. Node will pass the contents of
file.txt
into the provided callback as its second argument.
- The first argument is a string that contains a path to the file
Streaming data is often preferable since you don’t 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}`);
});
- We require in the
readline
andfs
core modules. - We assign to
myInterface
the returned value from invokingreadline.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.
We can create a writeable stream to a file using the fs.createWriteStream()
method:
const fileStream = fs.createWriteStream('output.txt');
fileStream.write('This is the first line!');
fileStream.write('This is the second line!');
fileStream.end();
- 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.
- We can indicate the end of a writable stream with the
The http
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.
Here's how the requestListener
callback function works:
- The function expects two arguments: a
request
object and aresponse
object. - Each time a request to the server is made, Node will invoke the provided
requestListener
callback function, passing in therequest
andresponse
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
requestListener
is responsible for setting the response header and body. - The
requestListener
must signal that the interaction is complete by calling theresponse.end()
method.
const http = require('http');
let requestListener = (request, response) => {
response.writeHead(200, {'Content-Type': 'text/plain' });
response.write('Hello World!\n');
response.end();
};
const server = http.createServer(requestListener);
server.listen(3000);
- We required in the
http
core module. - We created a
server
variable assigned to the return value of thehttp.createServer()
method. - We invoked
http.createServer()
with ourrequestListener
callback.- This is similar to running the
.on()
of anEventEmitter
: therequestListener
will execute whenever an HTTP request is sent to the server on the correct port.
- This is similar to running the
- Within the
requestListener
callback, we make changes to the response object,response
, so that it can send the appropriate information to the client sending the request.- The status code 200 means that no errors were encountered.
- The header communicates that the file type is text, rather than something like audio or compressed data.
- The last line starts the server with the port 3000.
- Every server on a given machine specifies a unique port so that traffic can be correctly routed.
- Lynda (via Linkedin Learning)
- Codecademy
- Node.js use cases
- Node.js Intro