https://github.com/bgoonz/express_js_notes
Notes on node, express, sql, Sequelize, MongoDB, authentication, graphQL
https://github.com/bgoonz/express_js_notes
authentication express graphql mongodb nodejs sequelize sql
Last synced: 3 months ago
JSON representation
Notes on node, express, sql, Sequelize, MongoDB, authentication, graphQL
- Host: GitHub
- URL: https://github.com/bgoonz/express_js_notes
- Owner: bgoonz
- Created: 2023-06-01T00:02:02.000Z (about 3 years ago)
- Default Branch: master
- Last Pushed: 2024-08-31T15:33:17.000Z (almost 2 years ago)
- Last Synced: 2025-01-31T13:16:07.467Z (over 1 year ago)
- Topics: authentication, express, graphql, mongodb, nodejs, sequelize, sql
- Language: JavaScript
- Homepage:
- Size: 1.23 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# NodeJS
> Remove node modules:
```bash
find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +
```
**Main Core Modules**
- http - create a web server, send requests, etc.
- https - create a secure web server, send requests, etc.
- fs - file system, read/write files, etc.
- path - path to files, etc.
- os - operating system, etc.
**http.createServer()** - create a web server
```js
const http = require("http");
const server = http.createServer((req, res) => {
console.log(req);
});
```
**server.listen()** - listen to a port
```js
const http = require("http");
const server = http.createServer((req, res) => {
console.log(req);
});
server.listen(3000);
```
- the server.listen() method takes a port number as an argument and will keep the server running while it listens for requests on that port number.
**Node.js Program Lifecycle**
- Node.js is a single-threaded application, but it can support concurrency via the concept of event and callbacks.
- the **Event Loop** is a single thread that performs all I/O operations asynchronously.
- The function we pass to createServer is an event listener, and the server object will emit events when a request is made, but it will not execute the event listener function right away. Instead, it will wait for the event loop to be free, and then it will execute the event listener function.
`process.exit();` - exit the event loop... this is not recommended, but it is possible, and will terminate the program.
`response.setHeader( "Content-Type", "text/html" );` - set the header of the response, this represents the type of data that is being sent back to the client.
`response.write( "
Hello
" );` - write data to the response, this is the data that will be sent back to the client.
`response.end();` - end the response, this will send the response back to the client.
**Complete Code To Send Basic HTML to Client**
```js
const http = require("http");
const server = http.createServer((request, response) => {
console.log(request.url, request.method, request.headers);
response.setHeader("Content-Type", "text/html");
response.write("");
response.write("My First Page");
response.write("
Hello from my Node.js Server!
");
response.write("");
response.end();
});
server.listen(3000);
```
> Shortcut to open developer tools in Chrome: `Ctrl + Shift + I`
> How to view the request in the network tab of the developer tools in Chrome:
> 
- as you can see there are some default headers set by the browser in addition to the headers we set in our code.
- If we view the response we see the response body is
```html
My First Page
Hello from my Node.js Server!
```
[Available Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
**GET Requests** - GET requests are the most common type of request, and they are used to fetch data from a server.
**POST Requests** - POST requests are used to send data to a server.
**PUT Requests** - PUT requests are used to send data to a server to create or update a resource.
**DELETE Requests** - DELETE requests are used to delete a resource from a server.
**PATCH Requests** - PATCH requests are used to update a resource.
**OPTIONS Requests** - OPTIONS requests are used to fetch information about a server.
> In the example below:
```js
response.write(
" {
if (err) {
console.error(err);
return;
}
console.log("Data written to file.txt");
});
```
`fs.writeFileSync`, on the other hand, is a synchronous function that writes data to a file. It takes the file path, data to be written, and an optional encoding. Unlike fs.writeFile, it doesn't require a callback function and returns undefined. Here's an example:
```js
const fs = require("fs");
try {
fs.writeFileSync("file.txt", "Hello, world!", "utf8");
console.log("Data written to file.txt");
} catch (err) {
console.error(err);
}
```
The choice between `fs.writeFile` and `fs.writeFileSync` depends on the requirements of your application. If you need to perform other tasks or handle other events while the file is being written, then `fs.writeFile` is recommended because it is non-blocking and allows your code to continue executing. On the other hand, if writing the file is a critical operation and you want to ensure it completes before moving on, `fs.writeFileSync` can be used, but be aware that it will block the execution of further code until the file write is finished.
---
### Single Thread, Event Loop & Blocking Code
- nodeJs uses only one thread to exicute all the code.
- The event loop automatically starts up when a Node.js process is launched, and it is responsible for executing the code, collecting and processing event call-backs, and executing queued sub-tasks.
- Operations that take a long time like file system operations are sent to a worker pool, which runs on different threads than your code.
- The event loop keeps the nodeJs process running and handles all the callbacks and has a certain order (pending callbacks, pending timers, pending I/O, idle, prepare, poll, check, close callbacks) and it keeps looping through this order.
##### Final Code for the server
> routes.js
```js
const fs = require("fs");
function requestHandler(req, res) {
const url = req.url;
const method = req.method;
if (url === "/") {
res.write("");
res.write("Enter Message");
res.write(
'Send',
);
res.write("");
return res.end();
}
if (url === "/message" && method === "POST") {
const body = [];
req.on("data", (chunk) => {
console.log(chunk);
body.push(chunk);
});
return req.on("end", () => {
const parsedBody = Buffer.concat(body).toString();
console.log(parsedBody);
//parsedBody: message=Hello+all
const message = parsedBody.split("=")[1].replace(/\+/g, " ");
//here the write file sync exicutes after the code that comes after it.
fs.writeFileSync("message.txt", message);
res.statusCode = 302;
res.setHeader("Location", "/");
return res.end();
});
}
res.setHeader("Content-Type", "text/html");
res.write("");
res.write("My First Page");
res.write("
Hello from my Node.js Server!
");
res.write("");
res.end();
}
module.exports = { handler: requestHandler };
//Or alternatively:
// module.exports.handler = requestHandler;
```
> app.js
```js
const http = require("http");
const routes = require("./routes");
const server = http.createServer(routes.handler);
server.listen(3000);
```
###### Closing Notes
- Node.js runs non-blocking code and uses the event loop for running your logic.
- A node program exits as soon as there is no more work to do.
- The `createServer()` event never finishes by default.
### Useful resources:
- Official Node.js Docs:
- Full Node.js Reference (for all core modules):
- More about the Node.js Event Loop:
- Blocking and Non-Blocking Code:
---
---
## Express.js
[Express.js Docs](https://expressjs.com/en/4x/api.html)
[Express API Notes](./00-notes/express.md)
- Express.js is useful for middleware, routing, and templating.
- in each middleware you can either send a response `res.send()` or call the next middleware `next()`.
**To use a middleware:**
```js
app.use((req, res, next) => {});
```
- the function you pass to app.use will be exicuted for every incoming request.
- the next function which gets passed as the third argument to the function you pass to app.use has to be called to allow the request to move on to the next middleware.
**Res.send() vs Res.write():**
- `res.send()` is often used with frameworks like Express.js and provides a higher-level abstraction for sending responses. It simplifies the process of setting headers and handling different response types.
- `res.write()` is a lower-level method and is typically used in raw Node.js HTTP server implementations. It requires more manual work to set headers and handle various response aspects.
```js
app.listen(3000);
```
> is the same as:
```js
const server = http.createServer(app);
server.listen(3000);
```
> If you call `res.send` ... that is a pretty good indication that you do not want to call the `next()` function. Because if you call `res.send` that will send a response to the client and the client will not wait for the next middleware to be executed.
`res.redirect()` is used to redirect the user to another page.
**How to serve shop.html from the views folder**
```js
const express = require("express");
const path = require("path");
const router = express.Router();
router.get("/", (req, res, next) => {
res.sendFile(path.join(__dirname, "../", "views", "shop.html"));
});
module.exports = router;
```
##### How to serve static files
```js
app.use(express.static(path.join(__dirname, "public")));
```
---
## Templating Engines
`app.set()` is used to set a global configuration value, which is then stored in Express and can be retrieved using `app.get()`.
### Pug
- Indentation matters in pug.
> How to render pug (in shop.js)
```js
const path = require("path");
const express = require("express");
const rootDir = require("../util/path");
const adminData = require("./admin");
const router = express.Router();
router.get("/", (req, res, next) => {
console.log("shop.js", adminData.products);
res.render("shop", {
prods: adminData.products,
pageTitle: "Shop",
path: "/",
hasProducts: adminData.products.length > 0,
activeShop: true,
productCSS: true,
});
});
module.exports = router;
```
##### We don't need to specify the extension of the file in the render method because we already set the view engine to pug in app.js
- to output dynamic data in pug we use `#{}`
```pug
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title #{docTitle}
link(rel="stylesheet", href="/css/main.css")
link(rel="stylesheet", href="/css/product.css")
body
```
### [Converting HTML to PUG](./03-templating-engines/NOTES.md)
**Using Layouts in Pug**
- Layouts are used to avoid repeating code in pug files.
- We start by creating a generalized html page in out `views/layouts/main-layout.pug` file.
```pug
doctype html
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
title Page Not Found
link(rel="stylesheet", href="/css/main.css")
block styles
body
header.main-header
nav.main-header__nav
ul.main-header__item-list
li.main-header__item
a(href="/") Shop
li.main-header__item
a(href="/admin/add-product") Add Product
block content
```
- And then we can use the layout to (for example) create our 404.pug file.
```pug
extends layouts/main-layout.pug
block content
h1 Page Not Found!
```
- We can also use it like so to create our add-product.pug file.
```pug
extends layouts/main-layout.pug
block styles
link(rel="stylesheet", href="/css/main.css")
link(rel="stylesheet", href="/css/product.css")
block content
main
form.product-form (action="/admin/add-product", method="POST")
div.form-control
label (for="title") Title
input (type="text", name="title")#title
button.btn (type="submit") Add Product
```
---
### Setting Up Express Handlebars
**Installing Express Handlebars**
```bash
npm install --save express-handlebars@3.0
```
> In app.js
```js
const expressHbs = require("express-handlebars");
app.engine("handlebars", expressHbs());
app.set("view engine", "handlebars");
```
**The way you pass data into templates does not change with respect to the template engine you are using**
> So the following works for any template engine:
```js
res.render("add-product", {
pageTitle: "Add Product",
path: "/admin/add-product",
});
```
- Handle bars works with normal html and the way we pass values from the route into the template is as follows.
```html
{{pageTitle}}
```
---
### EJS
- Ejs is a templating engine... that (like pug) works out of the box.
**How we get dynamic route parameters in EJS**
```ejs
<%=pageTitle %>
```
- You can write regular javascript in ejs like so:
```ejs
<% if(prods.length > 0) { %>
Great Book
$19.99
A very interesting book about so many even more interesting things!
Add to Cart
<% }else{ %>
No products found
<% } %>
```
**How to include partials using EJS**
```html
<%- include('includes/head.ejs') %>
Page Not Found!
```
- The code will render the html from head.ejs into the html file.
---
---
## Model View Controller (MVC)
- The view is the UI that your user interacts with.
- The model is a representation of the data in your code (allows you to work with your data... i.e. fetch & save)
- The controllers connect models and views... they contain the in between logic.
- controllers are often split across middleware functions.
- Controllers are a type of middleware.
The following is an example of controller logic:
```js
router.get("/", (req, res, next) => {
const products = adminData.products;
res.render("shop", {
prods: products,
pageTitle: "Shop",
path: "/",
hasProducts: products.length > 0,
activeShop: true,
productCSS: true,
});
});
```
#### Models:
- A model is a representation of a data structure. A model contains the data, logic, and rules of the application. Unlike the view, it also knows about the database.
> The product model:
```js
const products = [];
module.exports = class Product {
constructor(title) {
this.title = title;
}
save() {
products.push(this);
}
static fetchAll() {
return products;
}
};
```
- In the save method, `this` refers to the object that is created from the class.
- The static keyword defines a static method for a class. Static methods are called without instantiating their class and cannot be called through a class instance. Static methods are often used to create utility functions for an application.
> products.js controller refactored to use product model:
```js
const Product = require("../models/product");
exports.getAddProduct = (req, res, next) => {
res.render("add-product", {
pageTitle: "Add Product",
path: "/admin/add-product",
formsCSS: true,
productCSS: true,
activeAddProduct: true,
});
};
exports.postAddProduct = (req, res, next) => {
const product = new Product(req.body.title);
product.save();
res.redirect("/");
};
exports.getProducts = (req, res, next) => {
const products = new Product.fetchAll();
res.render("shop", {
prods: products,
pageTitle: "Shop",
path: "/",
hasProducts: products.length > 0,
activeShop: true,
productCSS: true,
});
};
```
**Refactoring Save method to use file storage**
```js
save(){
const filePath = path.join(rootDir, 'data', 'products.json');
fs.readFile(filePath, (error, fileContent)=>{
let products = [];
if(!error){
console.log(fileContent);
products = JSON.parse(fileContent);
}
products.push(this);
fs.writeFile(filePath, JSON.stringify(products), (error)=>{
console.log(error);
});
})
}
```
- In order for products.push(this) to refer to the correct object we need to use an arrow function.
**How to support decimal numbers in the price input (using step)**
```html
Price
```
---
---
## Dynamic Routes & Advanced Models
**How to add a unique id to our products when the Model class is instantiated**
```js
save() {
//this way is not gaurenteed to be unique but it will work for now.
this.id = Math.random().toString()
getProductsFromFile(products => {
products.push(this);
fs.writeFile(p, JSON.stringify(products), err => {
console.log(err);
});
});
}
```
```html
Details
```
---
#### Dynamic Route Parameters:
- When defining routes in your application, you can specify dynamic parameters in the path. These parameters are prefixed with a colon (:) and can match any value in their position within the URL. Here's an example using Express.js:
```js
router.get("/products/:productId", (req, res) => {
// You can access :productId via req.params.productId
// This route will match any path like /products/1, /products/abc, etc.
});
```
###### Order of Route Definitions Matters
- When you have both dynamic routes and static routes that could potentially match the same URL pattern, the order in which you define your routes is crucial. Routes are evaluated in the order they are defined, and the first match will handle the request.
```js
// Dynamic route
router.get("/products/:productId", (req, res) => {
// Handles any /products/{anything} pattern
});
// Static route
router.get("/products/delete", (req, res) => {
// Intended to handle a specific case: /products/delete
});
```
- In this setup, the route `router.get('/products/delete')` will never be reached if it's defined after the dynamic route router.get('/products/:productId'). This is because the dynamic route will match any /products/{anything} pattern, including /products/delete, and it is evaluated first.
- To ensure that the static route gets the chance to handle its specific case, you should define it before the dynamic route:
---
#### Query Parameters:
- created in a url using a `?` and a key value pair seperated by an `=` sign...
- Query parameters are components of a URL that are used to sort, filter, or customize the content or results returned by a web server. They follow the question mark (?) in a URL and are separated by ampersands (&) when multiple parameters are used. Each parameter consists of a key-value pair, connected by an equals sign (=). For example, in the URL `http://example.com/posts?sort=asc&category=science`, `sort=asc` and `category=science` are query parameters where `sort` and `category` are keys, and `asc` (ascending order) and `science` are their respective values. These parameters instruct the server to return posts in ascending order from the science category, allowing for a dynamic and customized user experience based on their needs or preferences.
---
---
## SQL
#### Choosing a Database(SQL vs NoSQL):
- SQL is structured as tables where you have fields (columns) and we have rows or records where our data is stored.
- SQL has a strong data schema where all data in a table has to conform to the data type of it's field.
- SQL has relationships between data (one to one, one to many, many to many)

- NoSQL has collections with documents
- Documents are of a similar format to a js object.
- In NoSQL we don't have relations between collections, instead we duplicate the data.
- Documents do not has a strictly enforced schema.

#### Comparing SQL & NoSQL (Horizontal vs Vertical Scaling):

