Node JS Intro
Node JS Intro
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:
- 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:
temp.replace(/{%Product%}/g, dataReadFromJsonFile)
- 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,
- 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,
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.
- 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.
- 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.
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
Uninstalling packages
- For uninstalling packages use,
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).
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,
//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.
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,
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.
app.use(express.json());
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.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.
//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.route(‘/user).get(getUser).post(checkBody, createUser);
//Here for POST method first checkBody will run and then createUser.
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.
{
“_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.find()
show dbs
show collections
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,
- 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.
- Deleting Documents
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
});
//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.
//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);
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.
- 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();
});
//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.
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.