Node.js & Express - robbiehume/CS-Notes GitHub Wiki
- In Node.js, there is no window object because it operates outside the browser environment
- Instead, Node.js uses its own global object,
global
, to store globally accessible properties and functions - While we can reference properties on the
global
object explicitly, in most cases, we can access global properties directly without needing theglobal.
prefix - In a browser, the top-level scope is the global scope, whereas, in Node.js, the top-level scope is not the global scope; it is local to each module
- A
var
defined in a browser will be global and accessible everywhere - A
var
defined in a Node.js module will be local to that module alone- That being said, using global variables is often not recommended
- A
- Node is often described as having event-driven architecture
- The
setTimeout
function allows us to delay the execution of a function. This can be useful in asynchronous programming when we want to execute code after a certain amount of time-
setTimeout(() => { console.log("This message appears after a 1-second delay"); }, 1000);
-
- The
setInterval
function is similar but repeatedly executes the function at specified intervals until stopped.-
const intervalId = setInterval(() => { console.log("This message repeats every second"); }, 1000); clearInterval(intervalId);
-
- Each separate file is treated as a module in Node.js
- Modules in
global
scope (don't need to callrequire()
-
console
,process
,error
,Buffer
, timers (setInterval
,setTimeout
, etc.)
-
- Gives info about the OS and program
- Need to require it to use it:
const events = require('events');
-
EventEmitter
class:const myEmitter = new events.EventEmitter()
-
.on()
: assigns a listener callback function to a named event- It takes the name of the event as a string and the listener callback function
- Ex: `myEmitter.on('new user', newUserListener)
- An event can have more than one listener added to it
-
.emit()
: execute the named event- It takes the name of the event as a string and the data to pass to the callback defined in
.on()
- Ex: `myEmitter.emit('new user', 'Robbie')
- It takes the name of the event as a string and the data to pass to the callback defined in
-
- Listener functions registered using the
.on()
method are called each time their specific event is ommitted- To only have it run once, use
.once()
instead:myEmitter.once('new user', newUserListener)
- To only have it run once, use
-
Handling errors: errors can cause issues when they occur in an
EventEmitter
- If you don't have a listener registered to handle an
error
event, the process will exit and print a stack trace - Therefore, it is good coding practice to have a listener for an
error
event:-
myEmitter.on('error', handleError)
: handleError function will be called if an error occurs
-
- If you don't have a listener registered to handle an
- Examples:
-
console.log('Hello World'); console.warn('This is a warning!'); console.error('This is an error'); console.error(new Error('This is a different error'));
-
-
console.trace('output message')
: used to print a stack trace -
console.table(<iterable>)
: outputs an iterable (JSON object, array, etc.) in a nice table format -
console.time('timer name')
: starts a timer with a name that we pass to it as a string -
console.timeEnd('timer name')
: stops the specified timer and outputs the time elapsed since it started - Previously, we were using the global
console
for printing to the console. What if we wanted to output to a file?- For that, we can use the
Console
class:const fs = require('fs'); const { Console } = require('console'); const output = fs.createWriteStream('./stdout.log'); const errorOutput = fs.createWriteStream('./stderr.log'); const logger = new Console({ stdout: output, stderr: errorOutput }); logger.log('number:', 5); // In stdout.log: number 5 logger.error('error code:', 'code');
- For that, we can use the
- Allows us to control and get info about the current process
- Since it's an instance of the
EventEmitter
class, it has a few important events that we should know about-
beforeExit
andexit
-
beforeExit
event: emitted when the event loop is empty (Node.js has done all its work and is about to exit) -
exit
event: emitted whenprocess.exit()
is called, or the event loop has no additional work to perform- The exit event executes synchronously, and asynchronous calls may not return properly. This is why the console.log() call in the setTimeout below won't get output
- You can register the events to take action
process.on('beforeExit', (code) => {console.log('Process beforeExit event with code:', code)})
-
process.on('exit', (code) => { setTimeout(() => { console.log('This will not work.') }, 0); console.log('Process exit event with code:', code); });
-
NOTE: if we explicitly call
process.exit()
, thebeforeExit
event is not emitted
-
-
uncaughtException
- This event is fired when an exception occurs that has not been handled or dealt with in the program
- By default, Node.js exits and prints the stack trace in case an
uncaughtException
arises - Using the process object, we can cater to these exceptions and deal with them as we wish:
process.on('uncaughtException', (err, origin) => { console.error('This caused a problem:', origin); console.error(err.stack); });
-
- Other methods
-
process.cwd()
: returns the current working directory -
process.chdir(directory)
: change the working directory of the process -
process.exit(<code>)
: terminate the process with an exit status of; the default is 0
-
- Streams
- A stream is an abstract interface for working with streaming data in Node.js
- Streams can be readable, writable, or both
- All streams are instances of
EventEmitter
- The streams include:
process.stdin
process.stdout
process.stderr
-
process.argv
: array of command line arguments passed to the program-
argv[0]
: the path of the node executable (process.execPath
) -
argv[1]
: the path of the JavaScript file that is being run -
argv[2], argv[3], ...
: the command line argument(s) - There are also packages, such as yargs that make parsing CLI arguments easier
-
-
process.stdin
: get input-
.stdin.on()
: retrieve input from the user -
process.stdin.on('data', (userInput) => { let input = userInput.toString() console.log(input) });
- Here, we were able to use
.on()
because under the hoodprocess.stdin
is an instance ofEventEmitter
- 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 NodeBuffer
class ([link(https://nodejs.org/api/buffer.html#buffer_buffer)), so we convert it to a string before printing
-
-
process.stdout
: write input-
.stdout.write()
: write to the console- The
console.log()
method is a “thin wrapper” on theprocess.stdout.write()
method
- The
-
-
readline
: get input- The
readline
module needs an interface to work- It can be a file, or in the example below, the console
- Ex:
const readline = require('readline'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); let query = 'What is your name?\n' rl.question(query, (answer) => { console.log(`Hello ${answer}!`); rl.close(); });
- The
- The
error
module has all the standard JS errors Cush as EvalError, SyntaxError, RangeError, ReferenceError, TypeError, and URIError as well as the JavaScript Error class for creating new error instances - Within our own code, we can generate errors and
throw
them, and, with synchronous code in Node, we can use error handling techniques such astry...catch
statements - 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 no error was thrown, the first argument will be
undefined
- Ex:
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}`); } }
- If no error was thrown, the first argument will be
- Node uses error-first callbacks in many of its asynchronous APIs because traditional
try...catch
statements won't work for error thrown during asynchronous operations
- Provides us with a class that stores an array of bytes that is used to represent binary data
-
Buffer
objects correspond to fixed-sized blocks of memory, which cannot be changed after they are created - Different ways to allocate:
-
const buf1 = Buffer.alloc(10); // 10 is the length of the Buffer const buf2 = Buffer.alloc(10, 15); // 15 is the value it fills with; defaults to 0 if not provided const buf3 = Buffer.allocUnsafe(10); // unsafe (Buffer may or may not be empty), but faster const buf3.fill(1); // fills the Buffer with the argument passed (1's) const buf2.write('abcdef'); // write a string to the Buffer; only writes the amount that will fit const buf4 = Buffer.from([265, 6.5, -255, '7']); // creates a Buffer from the provided data const buf5 = Buffer.from('Hello world'); // can create from a string console.log(buf5.toString()); // convert back to string ('Hello world')
-
-
fs
allows us to interact with the file system - It must be required before using:
const fs = require('fs')
- It has quite a few methods, some of which have both sync. and async. variants
- NOTE: the async. forms of the methods always take a completion callback as the last argument
- For this callback, the first argument is reserved for an exception
- Reading files:
fs.readFile
andfs.readFileSync
- Takes a file path, optional arguments (such as encoding), and a callback function to call with data read from the file (no callback needed for sync. version)
- Asynchronous way: doesn't wait for reading to finish; outputs 'Hello!' first, then file data
fs.readFile('test.txt', 'utf-8', (err, data) => { if (err) { console.error(err) return } console.log(data) }) console.log("Hello!") // outputs before file data since it's async
- Synchronous way: waits for reading to finish; outputs file data first, then 'Hello!'
try { const data = fs.readFileSync('test.txt', 'utf8') console.log(data) } catch (err) { console.error(err) } console.log("Hello!") // outputs after file data since it's sync and waits for reading to finish
- Writing to files:
fs.writeFile
andfs.writeFileSync
- Takes a file path, the content to write, and a callback function to call when finished (no callback needed for sync. version)
- Asynchronous way: doesn't wait for reading to finish; outputs 'Hello!' first, then file data
fs.writeFile('test.txt', 'hello', (err) => { if (err) { console.error(err) return } console.log('File written!') })
- Synchronous way: waits for reading to finish; outputs file data first, then 'Hello!'
try { const data = fs.writeFileSync('test.txt', 'hello') console.log('File written!') } catch (err) { console.error(err) }
-
fs.stat()
: provides useful info about files -
fs.open()
/fs.read()
/fs.write()
: for reading/writing in chunks, fine-grained control; best for streaming or large files -
fs.readFile()
/fs.writeFile()
: for reading/writing entire files at once
-
net
module provides an asynchronous network API for creating stream-based TCP or IPC servers - Allows you to create client-server socket connections
- Express is a framework built on top of Node.js
- Express offers many benefits over Node.js
- Reduces boilerplate code
- Simplified routing
- Built-in Middleware Support
- Easy Route Management
- Database Integration