NodeJS - herougo/SoftwareEngineerKnowledgeRepository GitHub Wiki

Source:

Node.js Notes

  • ECMAScript: industry standard for "JavaScript" for browsers to implement
  • JavaScript Engine: "compiler" for JavaScript into machine code (e.g. Google's V8 engine)
  • JavaScript Runtime: includes engine, api (e.g. document.ready, etc), event loop, etc
  • Node.js IS a runtime environment
    • it IS NOT a language or framework
  • Node.js Runtime includes
    • JS library
    • C/C++ features
    • dependencies: V8, libuv, zlib, crypto, etc

Hello World

node index.js # run index.js

Modules

Types of modules

  • built-in

  • local

  • third party modules

  • CommonJS is a standard that states how a module should be structured and shared (adopted by Node.js in its early days).

  • In node.js, each file is a module that is isolated by default

  • Before a module's code is executed, Node.js will wrap it with a function wrapper that provides module scope (i.e. (function(...) { // module code }). Here are the parameters of said function.

    • exports - copy of module.exports
    • require
    • module
    • __filename
    • __dirname
  • module caching: sebsequent requires to the same module will not reload it (it is cached).

CommonJS Way of Doing Modules

const add = (a, b) => {
  return a + b;
}

module.exports = add;
/* Can also do
module.exports = { add }
exports.add = add;
*/
const add = require('./add.js');  // require('./add'); also works and is common

ES Modules

  • Introduced with ES2015
  • export can be default or named
  • default exports can be assigned any name while importing
  • named exports must have the imported name be the same
  • mjs is the file format for this and is only for node.js
const add = (a, b) => {
  return a + b;
}

export default add;
/* Can also do
export default (a, b) => { ... }
export default { add }
*/
import add from './add.mjs';  // note the mjs

Alternatively

export const add = (a, b) => { ... };
import * as math from './add.mjs';  // note the mjs
// OR use import {add} from './add.mjs';

Unsorted

Can import json files as variables

const data = require("./data.json");

Built-In Modules

Path module

const path = require("node:path");

console.log(__filename);  // /path/to/file.js
console.log(__dirname);  // /path/to

console.log(path.basename(__filename));  // file.js
console.log(path.basename(__dirname));  // to

console.log(path.extname(__filename));  // .js
console.log(path.extname(__dirname));  // 

console.log(path.isAbsolute(__filename));  // true

console.log(path.join("folder1", "folder2", "index.html"); // folder1/folder2/index.html

Events Module

  • event: an action or an occurrence that has happened in our application that we can respond to
const EventEmitter = require("node:events");

const emitter = new EventEmitter();

emitter.on("order-pizza", (size) => {
  console.log("Order received");
}
emitter.emit("order-pizza", "large");  // trigger event
const EventEmitter = require("node:events");

class PizzaShop extends EventEmitter {
  constructor() {
    super();
    this.orderNumber = 0;
  }
  order(size, topping) {
    this.orderNumber++;
    this.emit("order", size, topping);
  }
}

fs Module

const fs = require("node:fs");

const fileContents = fs.readFileSync("file.txt", "utf-8");

fs.readFile("file.txt", "utf-8", (error, data) => {
  if (error) {
    console.log(error);
  } else {
    console.log(data);
  }
});

fs.writeFileSync("greet.txt", "Hello");

fs.writeFile("greet.txt", " world", { flag: "a" }, (err) => {
  if (err) {
    console.log(err);
  } else {
    console.log("File written");
  }
});

Promises version of fs (less performant, but otherwise recommended)

const fs = require("node:fs/promises");
fs.readFile("file.txt", "utf-8")
  .then((data) => console.log(data))
  .catch((error) => console.log(error));

async function readFile() {
  try {
    const data = await fs.readFile("file.txt", "utf-8");
    console.log(data);
  } catch (err) {
    console.log(err);
  }
}
readFile();

Streams

Question: How is the data event emitted?

const readableStream = fs.createReadStream("file.txt", {encoding: "utf-8"});
const writeableStream = fs.createWriteStream("file2.txt");

readableStream.pipe(writeableStream);

// Alternatively
// readableStream.on("data", (chunk) => {
//   console.log(chunk);
//   writeableStream.write(chunk);
// });

http Module

Simple Node server (you should use a web framework instead for your own projects)

Content Type options

  • text/plain
  • application/json
  • text/html
const http = require("node:http");

const server = http.createServer((req, res) => {
  // req.method GET POST PUT DELETE
  if (req.url === "/about") {
    // ...
  } else {
    const superHero = {
      firstName: "Bruce",
      lastName: "Wayne",
    }
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify(superHero));
  }
}

server.listen(3000, () => {
  console.log("Server running on port 3000");
});

Express

const express = require("express");
const PORT = process.env.PORT || 3001;
const app = express();

app.use((req, res, next) => {
  console.log("App-wide middleware");
  next(); // runs next middleware/route
});

function apiSpecificMiddleware(req, res, next) {
  console.log("API-specific middleware");
  next();
  // next(new Error("error")); // causes an error to occur in the middleware chain
}

// use async when dealing with a DB
app.get("/api", async (req, res) => {
  console.log(`Saying hello`);
  const dbResult = await prisma.post.findUnique(...);
  res.send("Hello 1 from server!" );
});

app.get("/api2/:id", apiSpecificMiddleware, (req, res) => {
  let id = req.params.id;
  console.log(`Saying hello 2`);
  res.send(`Hello 2 from server with ${id}!`);
});

// General error handler
app.use(function (err, req, res, next) {
  if (process.env.NODE_ENV !== "test") console.error(err.stack);
  const status = err.status || 500;
  const message = err.message;

  return res.status(status).json({
    error: { message, status },
  });
});

app.listen(PORT, () => {
  console.log(`Server listening on ${PORT}`);
});

/* localhost:3001/api
App-wide middleware
Hello 1 from server!
*/

/* localhost:3001/api2/2
App-wide middleware
API-specific middleware
Hello 2 from server with 2!
*/

Routes

router = express.Router();

router.use(loggerMiddleware);

// method 1
router
  .route("/:id")
  .get(userGet)
  .put(userPut)
  .delete(userDelete);

// method 2
/*
router.get("/:id", userGet);
router.put("/:id", userGet);
router.delete("/:id", userGet);
*/

Express CORS

Source: https://expressjs.com/en/resources/middleware/cors.html#enable-cors-for-a-single-route

var cors = require('cors')

var corsOptions = {
  origin: function (origin, callback) {
    // db.loadOrigins is an example call to load
    // a list of origins from a backing database
    db.loadOrigins(function (error, origins) {
      callback(error, origins)
    })
  }
}

app.get('/products/:id', cors(corsOptions), function (req, res, next) {
  res.json({msg: 'This is CORS-enabled for an allowed domain.'})
})

Express morgan

Source: https://dev.to/devland/how-to-use-morgan-in-your-nodejs-project-21im

morgan is a logging library

app.use(morgan('tiny'));

// equivalent to
// app.use(morgan(':method :url :status :res[content-length] - :response-time ms'))
// prints e.g. "GET / 404 139 - 7.136 ms

Socket.io

Socket.IO - a library that enables low-latency, bidirectional and event-based communication between a client and a server.

By default, every user has a room with with their id

# server.js
const io = require("socket.io")(3000, {
  cors: {
    origin: ["http://localhost:8080"]  // where the client lives
  },
});

io.use((socket, next) => {
  if (socket.handshake.auth.token) {
    socket.username = getUsernameFromToken(socket.handshake.auth.token);
    next();
  } else {
    next(new Error("Please send token"));
  }
});

io.on("connection", socket => {
  console.log(socket.id);
  socket.on('send-message', (message, room) => {
    console.log(message);
    if (room === '') {
      socket.broadcast.emit('receive-message', message);
      // broadcast: to all clients but sender
      // emit: send to all clients
    } else {
      socket.to(room).emit('receive-message', message);
      // broadcast is assumed
    }
  });

  socket.on("join-room", (room, cb) => {
    socket.join(room);
    cb(`Joined ${room}`);
  })
});

# script.js
import { io } from 'socket.io-client';

const socket = io("http://localhost:3000");
socket.on('connect', () => {
  displayMessage(`You connected with id: ${socket.id}`);
});
...
  socket.emit("send-message", message, room);
...
  socket.emit("join-room", room, message => {
    displayMessage(message);
  });
...

Setting up a Project in TypeScript

https://medium.com/@induwara99/a-step-by-step-guide-to-setting-up-a-node-js-project-with-typescript-6df4481cb335