Home

NodeJS Packages

Useful npm packages

Packages

Package Description
inquirer Used to simply get user input from the terminal
express Simplifies HTTP-related tasks usually for making APIs.
jest Used for tests inside node.
mysql2 MySQL library.
dotenv Used for working with environment variables.
bcrypt Used to create hashes for passwords.
tailwindcss Use tailwind inside nodeJS.
express-session Handle session which are info about the user across multiple requests.

inquirer

Used to simply get user input from the terminal.

Use npm install inquirer@8.2.4 to use require with inquirer.

const inquirer = require('inquirer')

async function askQuestion(question, answerName, validationFunction){
  // List can be an array of strings or an array of objects with name and value
  let list = [`option 1`, `option 2`, `option 3`]
  list = [
    {name: `option 1`, value: 1},
    {name: `option 2`, value: 2},
    {name: `option 3`, value: 3},
  ]
    // name is what's displayed to the user
    // value is the value that is returned if the user chooses that option
  return new Promise((resolve, reject) => {
    inquirer.prompt([
      {
        type: 'input',
        name: answerName,
        message: question,
        validate: validationFunction,
        choices: type === `list` ? list : undefined,
      }
    ])
    .then(answer => {resolve(answer)})
    .catch(error => {reject(error)})
  })
}

The validation function should return true if the input is valid or return an error message.

function validationFunction(input){
  if(input === "Test"){
    return true
  }
  return `The input must be "Test"`
}

express

Simplifies HTTP-related tasks usually for making APIs. It’s a framework for handling http requests.

// server.js
const express = require("express")
const path = require("path")

const app = express()

// Settings
const settingName = "view engine"
const settingValue = "ejs"
app.set(settingName, settingValue)

// Middleware. Middleware functions are functions that can intercept and process incoming requests before they reach route handlers.
  // You need to parse the data because it comes in as a stream of binary data in packets that need to be constructed together
  // The content type header defines which middleware is triggered.

app.use(express.json()) // Parses the json body to an object
app.use(express.static('public')) // Setting for serving static files from the public folder. You can directly call the file. Ex: localhost:3000/images/image.jpg
  // This is needed to use js and css files in you html. To reference them the file path should be /folderInPublicFolder/file.js
app.use(express.urlencoded({ extended: true })) // Old browsers might send json through URL encoded
  // URL encoded is used to parse information sent in the URL. Ex: https://website.com/information
  // extended: true uses the "qs" library to parse the information passed in the url. This allows for more complex data structures like arrays and objects. Ex: key1=value1&key2=value2&nested[key3]=value3
  // extended: false uses the "querystring" library. This only supports simple jey value pairs. Ex: key1=value1&key2=value2

// HTTP Methods/Endpoints

// Listen
const port = process.env.PORT || 3000 // This is used when deploying on 3rd party servers. The port already defined or port 3000
app.listen(port, () => {
  // Optional function that is run when connected to the port
  console.log(`Server is running on ${port}.`)
})

Express HTTP Methods

Making/Receiving an http method uses the format of app.httpMethod("path", callbackFunction)

app.get("/", (req, res) => {
  // Gets data from the server
  console.log("Test") // Outputs to the terminal of the server
})
app.post("/", (req, res) => {
  // Creates new data in the server
  // Used for login in
  const data = req.body
})
app.put("/", (req, res) => {
  // Replaces a resource in the server
})
app.delete("/", (req, res) => {
  // Removes data in the server
})

app.patch("/", (req, res) => {
  // Changes some of the information in a resource in the server
  // This is very rarely ever used
})
//etc.
Sending methods Description
res.sendStatus(statusCode) Sends a HTTP status code
res.json({ message: “error”}) Sends json. .json only accepts an object.
res.download(“./fileToDownload.txt”) Sends file to be downloaded.
res.send(“html”) The Content-Type header is set to text/html by default.
res.sendFile(path.join(__dirname, “/index.html”)) Renders html file in the browser
res.redirect(‘/newURL’) Redirects the client to a new URL.
res.end() Ends the response.

Your http methods need to return something, even an empty response, to indicate that the request was successfully handled. You can use res.send("GET / handled successfully")

You can change the default header type by res.header("Content-Type", "application/javascript")

Parameters

Parameters are data sent through the URL. This can include regular parameters like /api/test/${id} or query parameters like /api/test?id=${id}

// Regular parameters
app.get("/api/test/:id", (req, res) => {
  const id = req.params.id
})
// Query parameters
app.get("/api/test", (req, res) => {
  const id = req.query.id
})

Routers

Routers are used to help you organize your routes and the code for those routes.

In your main express file (usually server.js) you need to add:

// In server.js. There is more to server.js
const api = require("./routes/index.js") // The exported app from index.js.

// Middleware
app.use("/api", api) // If the route uses /api it sends the endpoint to the api variable which is a separate Router in index.js.
// In ./routes/index.js
const router = require("express").Router()

const tipsRouter = require("./tips") // The exported .Router() from tips.js

// Middleware
router.use("/tips", tipsRouter) // If the path is /api/tips send the endpoint to the tipsRouter

modules.exports = router
// In ./routes/tips.js
const tipsRouter = require("express").Router()

tipsRouter.get("/", (req, res) => { // This path is /api/tips/
  res.send("The path is /api/tips")
})

module.exports = tipsRouter

Middleware Functions

const middleware = (req, res, next) => {
  console.log(`Middleware function: ${req.method} with the ${req.get('Content-Type')}`)
  next() // Used to call the next middleware
}

app.use(middleware)

Middleware functions are often used to see if the user is logged in.

const withAuth = (req, res, next) => {
  if(!req.session.loggedIn) res.redirect("/login")
  next()
}

View Engines

View engines allow you to change the html from the server before it is sent.

View engines offer ___ inside of html

This allows you to keep your view and your controller separated.

/views/layouts/main.handlebars is the default location for where handlebars will look for.

Handlebars
const exphbs = require("express-handlebars")
const hbs = exphbs.create({})

app.engine("hbs", hbs.engine) // Sets the template engine which tells express how to render templates of a specific type
app.set("view engine", "hb") // Sets the default template engine defined in the first arg of app.engine()
  // The string is the default file extension when not specifying one. Ex. main.hbs

// Or you can use render to send data
app.get("/", (req, res) => {
  // res.render(template file path, locals)
  // You could also do res.render('template') which would make the template file template.hbs
  // locals are variables that can be put into views
  res.render('template.html', { // the file path starts from views. views/template.html
    layout: 'main', // views/layout/main.hbs will be used as default
    title: 'Title',
    name: "Ryan",
    thisCanBeAnything: "anything",
    array: ["Item 1", "Item 2", "Item 3"],
    object: {item1, item2, item3},
    boolean: true
  })
  // This will send the rendered(with handlebars) html
})

View File Example:

<!DOCTYPE html>
<html>
    <head> <!-- Contains metadata -->
        <title>{{title}}</title>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    </head>
    <body> <!-- Contains the visible content -->
    {{#if boolean}}
      <p>Welcome {{name}}</p>
    {{else}}
      <p>No name</p>
    {{/if}}

    {{#unless boolean}} {{! if not}}
      <p>No name</p>
    {{/unless}}

    <p>I can put {{thisCanBeAnything}} into my html</p>
    {{! This is  comment}}
    <ul>
      {{#each array}}
        {{if @index 'greater' 1}} {{! Skips the fist element}}
          <li>{{this}}</li> {{! this is th current item in the loop}}
        {{/if}}
      {{/each}}
    </ul>

    {{! If you don't want to use this}}
    <ul>
      {{#each array as |item|}}
        {{if @index 'greater' 1}} {{! Skips the fist element}}
          <li>{{item}}</li> {{! this is th current item in the loop}}
        {{/if}}
      {{/each}}
    </ul>

    <p>I can also use keys in objects. {{object.item1}}</p>

    {{{ body }}} {{! This is the template}}
    </body>
</html>

Definitions

Particles are placed into layouts in order to make a completed page.

Custom Helpers

In your handlebars file: `` to use the helper function

In your helper JS file:

module.exports = {
  custom_helper: (arg1) => {
    return arg1 // do some stuff
  }
}

In your server JS file where you create your engine:

const helperJSFile = require("./utils/helpers")
const hbs = exphbs.create({helperJSFile})

Front end and Back end

When using links in the font end with express you should use /s and then crete an express source to render that page.

jest

Used for tests inside node.

Tests are used to test your code usually before sending them towards production. Install jest as a dev package.

Tests are usually done in small units(Unit Testing) so that if one test fails then you know where some code is broken.

In your package.json add

"script": {
  "test": "jest"
}

npm test will now run jest and thus all of your tests.

To run a test file individually you can run npx jest --testPathPattern=./filePath/file.test.js

In order to create tests they have to be named fileName.test.js

describe("test title" () => {
  test("test description", () => {
      // JS code tests
      expect(/*Code to be run*/).toBe(/*Weather the output of expects ===(strictly equals) this code.*/)
        // or
      expect(/*Code to be run*/).toEqual(/*Checks for structural equality. Compares the contents of two objects or arrays*/)
      expect(/*object*/).toBeInstanceOf(/*Class. Checks if the object is an instance of that Class.*/)
  })
  it("test description", () => {
    // This is the same thing as test
  })
})

mysql2

const mysql = require("mysql2")

const db = mysql.createConnection(
  {
    host: 'localhost',
    user: 'root',
    password: 'your_password',
    database: 'database_name'
  },
  console.log("Connected to the database.")
)

db.query(`SELECT * FROM table_name;`, (err, results) => {
  console.log(results)
})

mysql2 with Promises

const mysql = require("mysql2/promise")

const db = mysql.createPool(
  {
    host: 'localhost',
    user: 'root',
    password: 'your_password',
    database: 'database_name'
  },
  console.log("Connected to the database.")
)

async function asyncQuery(){
  try{
    const [results, ] = await db.query(`SELECT * FROM table_name;`)
    return results
  }catch(error){
    console.error(error)
  }
}

Prevent SQL Injections

You can use the ? in you SQL query to prevent SQL injections.

async function unsafe(){
  const unsafeName = "' OR 1 = 1; DROP TABLE users; --" // SQL Injection
  const query = `SELECT * FROM users WHERE name = '${unsafeName}';`
  const [results, ] = await db.query(query)
  return results
}
async function safe(){
  const firstName = "' OR 1 = 1; DROP TABLE users; --" // SQL Injection
  const lastName = "Sheehy"
  const query = `SELECT * FROM users WHERE first_name = ? AND last_name = ?;`
  const [results, ] = await db.query(query, [firstName, lastName])
  return results
}

dotenv

Used to work with environment variables so that you don’t have your passwords or keys stored in plane text.

Environment variables are variables that are local on your server or computer. Usually environment variables are all capitalized.

When deploying code on a server you can set that server to have specific environment variables.

Dotenv loads environment variables from a .env file into process.env. .env needs to be put into .gitignore so that it doesn’t get pushed.

// This sets up dotenv
require("dotenv").config()

console.log(process.env.API_KEY)

Example of .env file:

# These are comments
API_KEY=08fe01a78943266193fc7a23625f68fa
DB_PASSWORD=password

bcrypt

Used to hash passwords. Bcrypt automatically creates the salt.

const bcrypt = require('bcrypt')

Creating a Hash

const saltRounds = 14 // Number of times the hash is applied. This sets the time to make the hash
// The salt Rounds are exponential. 2^saltRounds.
  // As computational power increases the salt rounds need to increase

bcrypt.hash(`password`, saltRounds, (err, hash) => {
  if(err){
    console.error(err)
    return
  }
  console.log(`Hashed password: ${hash}`)
})

// You can also use await
let hash = await bcrypt.hash(`password`, saltRounds)

// You can also use sync
let hash = bcrypt.hashSync(`password`, saltRounds)

Comparing a Password

const hash = `$2b$07$i7vcjUJXJbczMVmbiJiQBOHEtZHk/N93Sh1H862iC9iKxVqIveihG`
// $ version of hash $ salt rounds $ 22 character salt and then 31 character hash
const password = `password`

bcrypt.compare(password, hash, (err, result) => {
  if(err){
    console.error(err)
    return
  }
  // Result is true or false
  console.log(`Is password correct? ${result}`)
})

// You can use async/await
let isPassword = await bcrypt.compare(password, hash)

// You can use sync
let isPassword = bcrypt.compareSync(password, hash)

tailwindcss

Tailwindcss goes through all your HTM, JS, and any other files to find which tailwind classes are being used and then creates one css file which is used.

npm install -D tailwindcss

  1. Initialize tailwind. Creates a tailwind.config.js file
  2. Add file extensions you want tailwindcss to search through inside tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{html,js}"],
  theme: {
    extend: {},
  },
  plugins: [],
}
  1. Add tailwind directives to your custom css file.
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. Start tailwind-cli to build your css file
  2. Link the output file in your html

express-session

Express middleware which manges sessions and cookies across multiple requests.

When a session is created a cookie with that session’s id and hash is sent to the client.

Authentication

Sessions and cookies are used for authentication.

Client sends username and password -> Server checks username and password in database -> Server logs in client, creates a new session(user specific data) for them, and sends a cookie with the session id to the client.

If the user logs out the session is destroyed on the server.

Server Side

const session = require("express-session")

app.use(session({
  secret: "secretKey", // Required. Used to encrypt the session id to prevent others from putting in their own session id into the cookie. This should be put in the .env file.
    // The secret key is used for message authentication codes(MACs) which prevent the client from changing the message.
    // On the server: Session ID + Secret Key -> Hash
    // The client's cookie: Session ID and Hash
    // When the server receives the cookie: Cookie's Session ID + Secret Key -> Generated Hash. Compares the Generated Hash and the cookie's hash. If they match then the session ID wasn't modified. If they don't then the session id should not be used.
  cookie: { // Optional. Sets the settings for the session cookie.
    maxAge: 24 * 60 * 60 * 1000, // Expires after 1 day. In milliseconds. Sets the expiration for the session and the cookie.
    httpOnly: true, // The cookie is unaccessible to client side JavaScript. Used to prevent XSS.
    secure: false, // If true then the session is only sent over HTTPS
    sameSite: "strict" // Can the browser send cookies with cross origin requests
  }
  resave: false, // Optional. If set to false the session is saved only when modified. Setting it to true maybe useful to reset the expiration date on the session.
  saveUninitalized: false // Optional. If set to false sessions that aren't initialized aren't saved. Sessions only initialized are saved.
    // This is useful to only make sessions for users who are logged in.
}))

Storing and Retrieving

express-session stores the session data in the req.session object which can be used inside express endpoints.

Storing data
app.get('/login', (req, res) => {
  // Assuming user is authenticated with the db
  req.session.username = 'john_doe';
  res.send('Logged in successfully!');
});
Retrieving data
app.get('/dashboard', (req, res) => {
  const username = req.session.username;
  if (username) {
    res.send(`Welcome, ${username}!`);
  } else {
    res.redirect('/login'); // Redirect if user is not logged in
  }
});

Destroying session

app.get('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      console.error(err);
      res.send('Error logging out');
    } else {
      res.send('Logged out successfully');
    }
  });
});

http-server

http-server for Node.js is a simple, zero-configuration command-line HTTP server that serves static files from a specified directory.