0% found this document useful (0 votes)
20 views

Node JS Intro

Uploaded by

Mohsin Sheikh
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
20 views

Node JS Intro

Uploaded by

Mohsin Sheikh
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 18

Node JS Intro

- JavaScript runtime built on Google’s open source V8 engine.


- We can use JavaScript for server-side using NodeJS.

NodeJS Pros, Use and Don’t Use


- Fast and Scalable.
- Use for API with database behind it, data streaming, real-time chat app, server-side web app.
- Don’t use it for applications with heavy server-side processing (use Ruby on Rails, PHP, or Python).

Node Modules
- NodeJS additional functionality is built around modules e.g., reading of files, and these modules are known as
Node Modules.
- We require a module in our code to use it.

const fs = require(‘fs’)

File Handling
- FS module is used for file handling.
- Synchronous reading and writing:

fs.readFileSync(‘./path’, ‘character-coding(utf-8)’)
fs.writeFileSync(‘./path’, ‘text’)

- Synchronous code can block code execution for other users, since NodeJS is single threaded.
- Asynchronous reading and writing, last argument is callback function:

fs.readFile(‘./path’, ‘character-coding(utf-8)’, (err, data) => {});


fs.writeFile(‘./path’, ‘text’, ‘character-coding(utf-8)’, (err, data => {});

Creating and Starting Server


- Create server using code below, callback will be called whenever new client connects, req object contains request
info, and res can be used to send response back:

const server = http.createServer((req, res) => {


res.end(‘Hello from server’);
});

- The server needs to listen to incoming requests, use the code below, call back will be called whenever the server
starts listening on the specified port:

server.listen(‘8000’, ‘127.0.0.1’, () => {


console.log(‘Listening on port 8000);
});

Replacing Text in HTML Template


After reading an html file, and storing it in a variable (temp), you can replace text within it using the function,

temp.replace(/{%Product%}/g, dataReadFromJsonFile)

This will return the replaced template.


Basic Routing
- routing is implementing different actions for different URLs.
- here is how you implement a basic route whenever you get a request,

http.createServer((req, res) => {


const pathName = req.url;
if (pathName === ‘/overview’ || pathName == ‘/’) {
res.end(‘This is overview’);
}
});

- Through req.url you can send different responses for different paths of request.

API
- Service from where a user can request some data. On a specific route you can send some data back to the user
after he requests, by reading from a file or database. That’s the simplest API.

Parsing URL
- To parse URL, you can use,

const {query, pathname} = url.parse(req.url, true)

- The url.parse function will convert the URL into an object, containing information regarding URL such as
pathname, href etc. It will also contain query object that contains the query string in nicely formatted query object
in a field value format. You can extract the pathname and query object directly from object returned using this
function.
- The 1st argument to this function is the URL you want to parse and second argument if set to ‘true’ will also
include query object in the main object.

Nodes Module
- Every single file in NodeJS is treated as a module. You can import module using ‘require’ function. For
example,

const fs = require(‘fs’)

- Similarly, you can export your own modules containing different functions that could be imported into other
modules and use those functions there.
- There are many ways to export a module, one of the ways when you want to export a function in a module is,

module.exports = (x1, x2) => x1 + x2 //exporting anonymous function

Node Package Manager (NPM)


- NPM is Command Line Interface app that comes with NodeJS.
- We use this to install and manage 3rd party open-source packages.
- These packages come from a package repository also called ‘NPM’.
- You can look at all the packages included in NPM repository at http://www.npmjs.com/

NPM INIT
- Example of using npm is whenever we start a new project, we do it using ‘npm init’ command.
- This command will create a package.json file. This file is a configuration file for our project, where data about
our project is stored.
- After using this command, the cli will ask you some questions regarding the project like package name, version,
description, entry point, test command, git repository, keywords, author, license and store this information in
package.json file.
- A lot of other stuff can be added to package.json file, like packages you have installed to use in your current
project are included in the file.

Installing Packages with NPM


- There are two types of packages we can install with NPM. Simple Dependencies or Development
Dependencies.
- Simple or Regular Dependencies are simply packages that contain some code that we will include in our own
code. For example, installing a simple dependency,

npm install slugify

- This will download and install slugify and update package.json file to include a new field called dependencies,
which is an object containing installed dependencies as keys and their version as value.
- Development dependencies are just tools for development, like code bundler, webpack or a debugger tool or a
testing library. They are not needed for production, so our code does not really depend on them. We simply use
them to develop our applications. For example, nodemon.

npm install nodemon --save-dev

- This will also create a new field called devDependencies in package.json which is an object containing our
development dependencies like dependencies.

Types of Installs
- There are two types of installs called local install same as you installed slugify before which makes the package
available locally in that project only.
- And there is global install which makes that package available globally in all other projects.

npm i nodemon --global

- Global install will not add package in package.json


- Note: To run a local package that is also installed globally, you need to include the command in script object in
package.json and give it a name.
- Then you can type the command npm run script-name to run that script and will use the local package.

Versioning in NPM
- Most of the packages in npm follow semantic version notation, i.e., their version number is always expressed with
3 numbers. For example, “nodemon”: “^1.18.11”, first number is major version, second number is minor
version, and third number is patch version.
- The patch version is used to fix bugs. The minor version introduces new features into the package without
breaking-changes i.e., it is backwards compatible and does not cause old code to break. The major version is used
for a huge new release which includes breaking-changes. Old code may not work with newer versions.

Updating PackPages
- The “^” symbol before version is included by default and it specifies that it only accepts minor and package
updates. Similarly, “~” symbol before version is a bit safer and specifies that it only accepts patch updates.
Symbol “*” specifies that it accepts all of the updates including major.
- For updating packages check for outdated packages first,

npm outdated

- This will give you a table of outdated packages.

Uninstalling packages
- For uninstalling packages use,

npm uninstall package-name


Request Response Model
- Also known as Client-Server Architecture.
- Client sends a request for a web page to the server and server sends a response back to the client containing the
web page.
Example -> https://www.google.com/maps
o https -> protocol
o www.google.com -> domain name
o /maps -> resource
o Client will acquire the IP address and port of the domain from DNS.
o A TCP/IP socket connection will be established between client and server.
o An HTTP request will be sent to the server. HTTP is a protocol and defines how will data and
communication be transferred (request and response messages) between client and server.
o The HTTP request will have information,
 Method used in the request (GET, PUT, POST, PATCH).
 Request target (here /maps).
 HTTP Version.
 Request Headers -> contains information regarding the request.
 Request Body -> contains data e.g., data from a form. (Only when sending data to the server, e.g.,
POST).
 HTTPS is encrypted using TLS or SSL.
 Server will receive the request, perform some operations (if required), to get the data ready to be
sent back to the client.
o Server will send a response back to the user containing the following information,
 HTTP Version.
 Status Code -> to let client know if the request is successful or not.
 200 -> OK, 404 -> not found and many more.
 Status Message -> Message associated with status code.
 Response Headers -> contains information regarding response.
 Response Body -> HTML of webpage requested, or JSON data.

Node JS Architecture
- NodeJS has two dependencies. V8 JavaScript Engine and libuv.
- V8 engine converts JavaScript code into machine code that a computer can understand.
- libuv is an open-source library, and gives Node access to computer operating systems, file system, networking
etc.
- libuv also implements two extremely important features of NodeJS,
o Event Loop -> for doing simple tasks like executing call backs, and network I/O.
o Thread Pool -> for more heavy work like file access, compressions etc.
- V8 is written in C++ and JavaScript while libuv is written in C++.
- Other parts of NodeJS include http-parser, c-ares, OpenSSL, zlib.

Thread Pool
- NodeJS process has a single thread (a sequence of instructions) even if you have a lot of users.
- Single thread makes it easy to block NodeJS applications.
- Here is the process that NodeJS follows,
o Program is initialized.
o Top-level code is executed (code that is not in call back), all modules are required, and all call backs are
registered.
o After that Event Loop starts to execute.
o Most of your work is handled in the Event Loop.
o But heavy tasks are not executed in the Event Loop because they would block the Single Thread.
o Here the Thread Pool comes in which is also provided by the libuv library.
o It gives 4 additional threads (can be configured to 128 threads) that are separate from the main thread.
These threads together form the Thread Pool.
o Heavy tasks are automatically offloaded from Event Loop to the Thread Pool by the Event Loop.
o Tasks that are offloaded,
 File System APIs
 Cryptography (like caching passwords)
 Compression
 DNS lookups

Event Loop
- All the application code that is inside callback functions is executed.
- NodeJS is built around callback functions and has an Event-driven architecture,
o Events are emitted.
 New HTTP request.
 Any timer expired.
 Finished file reading.
o Event loop picks them up.
o Callbacks are called.
- This whole process is called orchestration (events are picked and callbacks are called, more heavy work offloaded
to Thread Pool).

Phases and Single Tick


- When we start our application Event Loop starts to run right away (after top level code, requiring modules and
registering event callbacks). It has 4 phases, with each phase containing a callback queue, which are the callbacks
coming from the events that the Event Loop receives.
- Expired Timer Callbacks -> In this phase the callbacks of timer that have expired are handled e.g., from
setTimeout(callback) function. If a timer expires later during other phases of the Event Loop, that callback will
only be called as soon as the Event Loop comes back to this phase.
- I/O Polling and Callbacks -> Callbacks of Networking and File Access e.g., callback of readFile function of fs
module.
- setImmediate Callbacks -> Special kind of timer that we can use if we want to process callbacks immediately
after the I/O Polling and Callbacks phase (more advanced usecases).
- Close callbacks -> All close events are processed here e.g., following a webserver or web socket shutdown.
- Two other queues, ,
o PROCESS.NEXTTICK() QUEUE -> when we need to execute a call back right after the end of current
phase.
o OTHER MICROTASKS QUEUE -> related to promises and executed right after end of current phase.
- This whole process is called single tick. After this program should decide whether it should exit or continue to the
next tick. This decision is made upon whether there are any pending timers or I/O tasks. If there are then Event
Loop is continued for the next tick.

Summary of Sequence of Instructions


- Initialize Program.
- Execute top-level code.
- Require Modules.
- Register event callbacks.
- Start Event Loop

Event-Driven Architecture
- In NodeJS there are certain objects called Event-Emitters.
- They emit named events as soon as something important happens (e.g., an http request, or a timer expiring).
- These events are picked up by Event Listeners, which are set up by developers, which will then fire a callback
function attached to each listener.
- These listeners will run in order in which they were written in the code.
- Example,

const EventEmitter = require(‘events’);


const myEmitter = new EventEmitter();

//start listening to event


myEmitter.on(‘eventName’, stock => {
console.log(`There are now ${stock} left in stock`);
}

//emitting event
myEmitter.emit(‘eventName’, 9)

Streams
- Used to process (read and write) data piece by piece (chunks), without completing the whole read or write
operation, and therefore without keeping all the data in memory.
- We read part of the data, do something with it, free memory, and read next part. This whole process is repeated
until all the data is dealt with.
- Perfect for handling large volumes of data, for example videos.
- More efficient data processing in terms of memory and time.
- Streams are instances of the EventEmitter class (they can emit and listen to named events).
- 4 types of streams
o Readable Streams
 From where we can read (consume) data. e.g., http requests, fs read streams.
 Important events are,
 data (when there is new piece of data to consume)
 end (no more data to consume).
 Important functions are,
 pipe() -> automatically handles back pressure
 read()
o Writable Streams
 To where we can write data e.g., http responses, fs write streams.
 Important events are,
 drain
 finish
 Important functions are,
 write()
 end()
o Duplex Streams
 Streams that are both readable and writable, e.g., net web socket.
o Transforms Streams
 Duplex streams that transform data as it is written or read. e.g., zlib Gzip creation.

NodeJS Module System


- Each JavaScript file is treated as a separate module.
- NodeJS uses the CommonJS module system: require(), exports or module.exports
- ES module system is used in browsers: import/export.
- There have been attempts to bring ES modules to NodeJS (.mjs).
- Process behind requiring a module,
o The path to the required module is resolved and the file is loaded.
 First start with core modules.
 If path begins with ‘./’ or ‘../’, then try to load the developer module.
 If no file found, try to find folder with index.js in it.
 Else go to node_modules/ and try to find the module there.
 If no module is found after all these steps, an error is shown.
o Then wrapping happens.
 The module code is wrapped in a special function which will have access to a couple of special
objects.
 The function is an immediately invoked function.
 Node does not directly execute the code that we write into a file, but instead a wrapper function
that will contain the code in its body.
 The function has parameters, exports, require, module, __filename, __dirname.
 These parameters act as global variables.
 So that’s why in every module we have access to ‘require’ function.
 It also keeps the top-level variables that we define in our module private. They are only available
in the current module instead of leaking everything into the global object.
 require -> function to require modules.
 module -> reference to the current module.
 exports -> a reference to module exports, that is used to export objects from a module.
 __filename -> absolute path of the current module’s file.
 __dirname -> directory name of the current module.
o Then module code is executed.
o Module exports are then returned.
 require function returns exports of the required module.
 module.exports is the returned object.
 use module.exports to export one single variable, e.g., one class or one function.
 module.exports = Calculator
 Use exports to export multiple named variables.
 exports.add = (a, b) => a + b
o Finally, the entire module is cached.
 In subsequent calls to require the same module, it is simply retrieved from the cache.
 This means that code in required module is executed only once, that is when it is required for the
first time, because it is loaded.
 In all subsequent require calls, the exported module is retrieved from the cache instead of loading
again and so whole code of required module is not executed.

Superagent module
- Used for sending http requests.
- npm module.

const require(‘superagent’)
superagent.get(/url).end((res, err) => {})

Express
- Express is a minimal NodeJS framework, (a higher level of abstraction).
- A lot of features,
o Complex routing.
o Easier handling of requests and responses.
o Middleware.
o Server-side rendering.
- Allows for rapid development of NodeJS applications.

Using Express
- Basic Express,

const express = require(‘express’)


app = express()
app.get(‘/url’, (req, res) => {
res.status(200).send(‘Hello from the server side’);
}
app.post(‘/url’, (req, res) => {
res.status(200).json({data: ‘data’});
}
//app.patch(), app.delete()
app.listen(3000, () => {
console.log(‘app running on port 3000’);
}

API
- Application Programming Interface: a piece of software that can be used by another piece of software, to allow
applications to talk to each other.
- For example, Web APIs (client-server), a class exposes public methods and objects to be used by other parts of
program or another software, so that is the basic API.

REST Architecture (Representational State Transfer)


- Building API in logical way, making them easy to consume. To build such APIs, some rules are to be followed:
o Separate API into logical resources.
 All the data to be used in API, should be divided into logical resources.
 A resource is an object or representation of something which has data associated to it.
 Anything that can be named can be a resource e.g., tours, users, reviews.
o Expose structured resource-based URLs.
 e.g., https://www.natours.com/addNewTour
 Different end-points to perform different actions.
 /getTour
 /updateTour
 /deleteTour
 /getTourByUser
 /deleteToursByUser
 These end-points are still wrong because they don’t follow the HTTP method stated next.
They should only contain resources (noun), and use HTTP methods.
o Use HTTP methods (verbs).
 For example, for tours, use only end-point “/tours” and then use HTTP method to perform the
required action.
 GET /tours (to get data regarding tours), POST /tours (sending data to server, to create a new
resource), PUT /tours (sending data to server, entire updated object), PATCH /tours (sending
data to server, only parts of object to be updated), DELETE /tours/id (to delete resource).
o Send data as JSON (usually).
o Be stateless.
 All state is handled on the client side.
 Each request should contain all the information necessary to process a certain request.
 The server should not have to remember previous requests.
 State refers to piece of data in application that might change over time, for example a certain user
is logged in.
 For example, we can make and endpoint to the next page, /tours/nextPage, but for that the
server will have to remember what the current page is so to send next page back. So, the
state is handled at server side, and it is bad. Instead, we should create the endpoint,
/tours/page/{number}, so the state is handled on client and the specific page that is
required is sent to the server.

POST Request Data


- To get data out of POST request, you have to use middleware. Middleware is basically a function that can modify
an incoming request data.
- Middleware sits between the middle of request and response. It is just a step the request goes through while is
being processed. Here in this upcoming request, the middleware adds data from the body to the request object. We
will discuss more about middleware later, but now you will use,

app.use(express.json());

- To get the data from the body, use,

app.post(‘/user’, (req, res) => {


data = req.body; //object containing the data
});
Some Routing Information
- You can use ‘ : ’ to specify parameters in route, e.g., /tours/:id.
- For optional parameters use ‘ ? ’ after the route, e.g., /tours/:id/:x?
- To get parameters in code, use,

app.get(‘/user/:id’, (req, res) => {


id = req.params.id
//code to find the required user and sending back data
...
});

Middleware
- Our express app receives a request when someone hits the server, for which it will create the request and response
objects.
- This data will then be used and processed to generate and send back a meaningful response.
- To process data, we use something called middleware, that can manipulate the request and response object or
execute any other code that we would like.
- This is called middleware because it is executed between receiving a request and sending back the response.
- So, everything is middleware, even our route definitions (They are middleware functions that are executed for
certain routes).
- All the middleware that we use in our app is known as middleware stack.
- The order of middleware in the stack is the order in which they are defined in the code.
- The request-response cycle starts with a request, then executing all the middleware in the middleware stack step
by step, then sending back the response.
- To create a middleware, (here calling next() at the end is important for the code to continue.)

app.use((req, res, next) => {


console.log(‘Hello from the middleware’);
next();
});

app.route()
- Using app.route(), you can specify one route and then specify by chaining what you want to have on that route on
specific methods like get, post etc.

app.route(‘/users’).get(getUser).post(createUser)

Router
- You can separate your resources into logical files (each resource having its own), by using the router.
- For this you will create a separate router for each resource, and routes of specific resources, will be handled by
their own router.

const userRouter = express.Router()

//To connect your router with the application we use middleware


app.use(‘/users’, userRouter); //Mounting a router

//You can now define specific routes for this router, these specific routes are //preceded by the original route
defined for the router

userRouter.route(‘/’).get(getAllUsers).post(createUser) // /users/
userRouter.route(‘/:id).get(getUser).delete(deleteUser) // /users/id
Better File Structure
- Routers
o For each resource create a separate router file, in which you will create a router and define all the routes
for that resource.
o Export each of these router files using module.exports.
o You will import these routers into the app.js file and mount them using middleware.
- Controllers
o You will separate all your handler functions into their own files, and these handler functions are also
known as controllers.
o For each resource you will have a separate handler file.
o Write all your handler functions in these files and export these functions using the exports object.
o Now import these handlers in the routers, which will call the specific handler required for the specific
route and http method.

- Server
o Create a server.js file. This is for separation of concern, everything related to express will be in app.js and
those related to server be in server.js like listening on a specific port, database configurations, environment
variables etc.
o You will require app.js in server.js and start listening on the port there.

Params Middleware
- It is a middleware that only runs when the url has specific parameters.
- It is used to perform common tasks that are required in every route containing that parameter and requires use of
that parameter. For example, checking if the ID is valid.

router.params(‘id’, (req, res, next, val) => {


if (val * 1 > tours.length) {
return res.status(404).json({status: “fail”, message: “Invalid ID”});
}
next();
});

Chaining Multiple Middleware Functions (For a specific method of a route)


- You can chain middleware functions to a specific HTTP method for a specific route.
- These middleware functions will then only run for that specific HTTP method on that route.
- For this purpose,

router.route(‘/user).get(getUser).post(checkBody, createUser);
//Here for POST method first checkBody will run and then createUser.

Serving Static Files


- To serve static files e.g., static html files you will use middleware.

app.use(express.static(`${__dirname}/public}`));

- This will set ‘public’ directory as root directory from where you can select files inside that directory.
- For example, ‘127.0.0.1:3000/overview.html’ will open ‘overview.html’ file present in ‘public’ folder.
- This will only work for static files present in the folder (root folder) specified in the middleware.
- So, this is how you serve static files from a folder and not from a route.

Environment Variables
- NodeJS or Express Apps can run in different environments.
- Most important ones are development environment and production environment.
- Depending on different environments we might use different databases and might turn logging on or off and use
other different settings.
- By default, express turns the environment to development, because that’s what we do when we start a new
project.

MongoDB
- MongoDB is a NoSQL database; other databases are called relational database.
- NoSQL database contains collections (tables). Each collection contains one or more data structures called
documents (rows). Each document contains information about one single entity.
- A “users” collection may have many documents with each document specifying a specific user.
- MongoDB is a document database with the scalability and flexibility that you want with the querying and
indexing that you need.
- MongoDB features:
o Document Based: MongoDB stores data in documents (field-value pair data structures, NoSQL).
o Scalable: Very easy to distribute data across multiple machines as your users and amount of data grows.
o Flexible: No document data schema required, so each document can have a different number and type of
fields.
o Performant: Embedded data models, indexing, sharding, documents, native duplication, etc.
o Free and open-source published under the SSPL License.

Documents, BSON and Embedding


- Document Structure:

{
“_id”: ObjectID(‘9375209372634926’),
“title”: “Rockets, Cars”,
“author”: “Elon Musk”,
“length”: 3280,
“published”: true,
“tags”: [“MongoDB”, “space”, “ev”]
}

- MongoDB uses a data format similar to JSON. It uses a data format called BSON. It is basically the same as
JSON, but it’s typed, meaning all values will have a data type like string, boolean, data, teacher, double, object
and more.
- Embedding/Denormalizing: Including related data into a single document. This allows for quicker access and
easier data models (it’s not always the best solution though). Relational databases are always normalized.

MongoDB Shell
- You can use MongoDB shell, to enter commands and work with databases. Make sure your MongoDB server is
running as well.
- To create a new document inside a database, use,

db.tours.insertOne({ name: “The Forest Hiker”, price: 297, rating: 4.7 })

- To find all documents in a collection,

db.tours.find()

- To view all the databases

show dbs

- To view all collections in database you are working on,

show collections

- To create more than one documents inside a database, use,

db.tours.insertMany([{ name: “The Sea Explorer”, price: 497, rating: 4.8 }, { name: “The Snow Adventure”,
price: 997, rating: 4.9, difficulty: “easy”}])
- Search for a specific documents, whose specific field has a specific value,

db.tours.find({ name: “The Forest Hiker” })

- Field having values greater than, less than as well as equal to

db.tours.find({ price: {$lte: 500} })


//$gte – greater than or equal to
//$lt - less than
//$gt – greater than

- AND Query and OR Query

db.tours.find({ price: 500, name: “The Forest Hiker” }) //AND


db.tours.find({ $or: [ {price: {$lte: 500}}, {rating: 4.9} ] }) //OR

- Projecting Results

db.tours.find({ price: 497 }, {name: 1}) //Only name property of the results will //appear

- Updating Documents (note: if there are multiple matches for updateOne() then only 1 st one will be updated.

db.tours.updateOne({name: “The Snow Adventurer”}, { $set: {price: 597} })


//use updateMany() to update all documents that match the query
//use replaceOne() and replaceMany() for replacing entire documents, you will //also use query to find documents
similar to before to replace them

- Deleting Documents

db.tours.deleteOne({name: “The Snow Adventurer”})


//use deleteMany() to delete all the documents matching the query.

Mongoose Introduction
- Mongoose in an Object Data Modeling (ODM) library for MongoDB and Node.js, a higher level of abstraction.
- Allows for rapid and simple development of MongoDB database interactions.
- Features:
o Schemas to model data and relationships.
o Easy data validation.
o Simple query API
o Middleware
- Schemas: Where we model our data, by describing the structure of the data, default values, and validation.
- Mongoose Model: A wrapper for the schema, providing an interface to the database for CRUD operations.

Mongoose Detail
- Mongoose is all about models. A model is like a blue-print that we use to create documents.(similar to class and
object relation in Object Oriented Programming).
- The model is also used to query, update, and delete documents. (CRUD operations)
- In order to create a Mongoose Model, we need a Schema.
- We use the schema to describe our data, to set default values, to validate the data and other similar stuff.

//connecting to a database
mongoose.connect(connection_string, {}) //returns a promise
//defining a schema
const tourSchema = new mongoose.Schema({
name: { //property name and configuring it
type: String,
required: [true, “A tour must have a name”],
unique: true
},
rating: {
type: Number,
default: 4.5
},
price: Number
});

//creating a model from Schema


const Tour = mongoose.model(‘Tour’, tourSchema) //this will act as class of which
//we can create objects
//(documents) and get access to
//many methods.

//Creating an object using ES6 syntax


const testTour = new Tour({name: ‘The Park Camper’, price: 997});

//Now using one of the methods to save this tour in the database
testTour.save();

MVC Architecture
- Model
o Related to everything about application’s data and business logic.
- Controller
o Handle the application’s request, interact with models, and send responses to the client.
o All that is known as application logic.
- View
o Necessary when have a graphical interface in our app.
o Consists of templates used to generate the view. So, it is the presentation logic.

MVC Workflow
- A request will hit one of our router. We have multiple routers, one for each of our resource.
- The goal of the router is to delicate the request to the correct handler function which will be in one of the
controllers (there will be one controller for each of our resources).
- Depending on the incoming request, the controller might need to interact with one of the models, e.g., to retrieve
a certain document from the database or to create a new one. (There is one model file for each of the resources.)
- After getting the model from the resource the controller will then be ready to send back the response to the client
containing that data.
- In case we want to render a website there is one more step involved. After getting the data from the model, the
controller will then select one of the view templates and inject the data into it.
- That rendered website will then be sent back as the response.
- In the view layer, there is usually one view template for each page e.g., overview page, tour page etc.
Application Logic vs. Business Logic
- Application logic:
o It is only concerned with the application’s implementation, not the underlying business problem we’re
trying to solve (e.g., showing and selling tours).
o Concerned about managing requests and responses.
o More about technical aspects.
o Also serves as bridge between model and view layers.
o Keep your application logic in controllers.

- Business logic:
o Code that actually solves the business problem we set out to solve.
o The code is directly related to business rules, how the business works, and business needs.
o Examples,
 Creating new tours in the database.
 Checking if the user’s password is correct.
 Validating user input data.
 Ensuring only users who bought a tour can review it.
o Keep your business logic in models.

- Fat Models/Thin Controllers: Offload as much logic as possible into the models and keep the controllers as
simple and lean as possible.

Continuing Mongoose
//save a tour another method – we already have imported Tour model
const newTour = await Tour.create(tourObject); //Tour.create() returns a promise.

//while using async/await use try/catch block to catch errors.

//get all tours


const tours = Tours.find();
const tours = Tours.find(queryObject); //to get all Tours that match the query
//get one tour
const tour = Tours.findOne(filterObject);

//find Tour By ID
const tour = Tours.findById(req.params.id);

//Update Tour By ID
const tour = Tours.findByIdAndUpdate(id, updatedFieldsObject, {
new: true,
runValidator: true,
};

//Delete Tour By ID
Tours.findByIdAndDelete(id);

//Another method to find objects through querying


Tours.find().where(‘duration’).equals(5).where(‘difficulty’).equals(‘easy’);

Mongoose Aggregation Pipeline


//Transforming and doing calculations with the data through a pipe line.
Tour.aggregate([
{
$match: { ratingsAverage: { $gte: 4.5 } }
},
{
$group: {
_id: ‘$difficulty’,
numTour: { $sum: 1 },
numRatings: { $sum: ‘$ratingsQuantity’ }
avgRating: { $avg: ‘$ratingsAverage’ },
avgPrice: { $avg: ‘$price’ },
minPrice: { $min: ‘$price’ },
maxPrice: { $max: ‘$price’ }
}
},
{
$sort: { avgPrice: 1 }
}
]);
//stage unwind
$unwind: ‘$property’, it will unwind the array to make separate documents from original documents each having
one of the value from the array e.g.,
{
name: ‘Mountains Tour’,
startDate: [‘2-5-2023’, ‘2-6-2023’]
}

//after unwinding we will have two documents


{
name: ‘Mountains Tour’,
startDate: ‘2-5-2023’
},
{
name: ‘Mountains Tour’,
startDate: ‘2-6-2023’
}

Mongoose Middlewares
- Mongoose also has concept of middlewares through which we can make something happen between two events,
e.g., each time a document is saved to the databases we can run a function between when the save command is
issued and when it is actually saved, or we can also run after it is saved.
- There are four types of middlewares in Mongoose.
o Document
o Query
o Aggregate
o Model
- Document Middleware: Allows us to run middleware for documents, e.g., before they are saved in the database
or after they are saved in the database.

//this refers to the current document


schema.pre(‘save’, function (next) {
console.log(this);
next();
});
schema.post(‘save’, function(doc, next) {
console.log(doc);
next();
});

- Query Middleware: Allows us to run middleware before or after a certain query is executed.

//this refers to the Query Object, on which you can further query before it is executed
schema.pre(‘find’, function(next) {
this.find({ secretTour: { $ne: true } });
next();
});

- Aggregate Query: Allows us to run middleware before or after aggregate happens.

//this refers to the Aggregate Object, you may add any stage to aggregate object
schema.pre(‘aggregate’, function(next) {
this.pipeline().unshift({ $match: { secretTour: { $ne: true } } });
next();
});
Validators
- You can put in-built validators in your schema for each property, to validate the data whenever you are creating a
new object/document to store in the database.
- For example, minlength: [10, ‘error message’], can be used to specify the minimum length of the property.
More examples are required, maxlength, min, max, enum.
- These validators will also run while updating values if you have set runValidators property to true in the
options object of update function in the controller.
- You can also build your custom validators, using the validate property.

validate: {
validator: function(val) { //val refers to current value
return val < this.price; //this refers to current document
}, //the function must return a boolean value
message: ‘error message ({val}) should be less than price’;
}

- The validate property will not work while updating the objects.

Error Handling in Express


- Operational Errors: Problems that we can predict will happen at some point, so we just need to handle them in
advance.
o Invalid path accessed.
o Invalid user input.
o Failed to connect to server.
o Failed to connect to database.
o Request timeout.
- Programming Errors: Bugs the developers introduce in their code. Difficult to find and handle.
o Reading properties on undefined.
o Passing a number where an Object is expected.
o Using await without async.
o Using req.query instead of req.body.
- When we are talking about error handling with express, we mainly just mean operational errors. These are easy to
catch and handle with express application. And express itself comes with error handling. So, we have to write a
global express error handling middleware, which will then catch error coming over from all over the
application.

Global Error Handling Middleware


//You can build global error handling middleware in this way,

app.use((err, req, res, next) => {


err.statusCode = err.statusCode || 500;
err.status = err.status || ‘internal server error’;

res.status(err.statusCode).json({
status: err.status;
message: err.message;
});
});

//Express will recognize this as error handling middleware and will call it whenever there is an error.
//Whenever there is an error inside your application, you can create an Error object and configure it, then call
next(err), passing in the Error object as an argument, in this way Express will automatically know there was an
error in the application, and this middleware will be called and receive that Error object.

JSON Web Tokens (Authentication and Authorization)


- Mechanism
o User starts by making a POST request to /login with {email, password}.
o The application then checks if the user exists, and if the password is correct.
o If so, a unique JSON web token for that user is created using a secret string and is stored on the web-
server.
o The server then sends the JWT back to the client, which will either store it in a cookie or local storage.
o And based on this the user is authenticated and logged in, without leaving any state on the server.
o Each time the user accesses a protected route, he sends his JSON web token along with the request.
o Once the request hits the server, our application will verify whether the JSON web token is valid or not.
o If it is valid, the requested data will be sent back to the client.
o All this communication must happen over https.
- JWT – JSON Web Token
o JSON Web Token is an encoded String, made up of 3 parts.
o The Header, the Payload, and the Signature.
o The Header is the metadata about the token itself.
o Payload is the data that we encode in the String.
o This Signature gets created using the Header, the Payload and Secret that is saved on the server. This
process is called signing the JSON web token.

- How the authentication works,


o The server will check that no third party altered either the header or the payload of the JSON web token.
o It will take out the header and payload, combine it with the secret string and generate a signature.
o This signature will then be compared with the original signature sent with the JSON web token to
authenticate the user.

You might also like