Node.js Streams, MVC, Sessions, Express, CORS, and React Hooks
Understanding Streams in Node.js
A stream in Node.js is a collection or a sequence of data that is processed in a continuous flow. Streams are essential concepts for handling reading and writing of data in chunks rather than loading the entire data at once. Unlike an array or a string, the entire data in a stream object is not stored at once in memory. Instead, a single chunk of data from the stream is brought into memory at a time. This makes streams more efficient.
Streams are particularly useful when dealing with large amounts of data by breaking it down into smaller, manageable chunks. They are often used to process data in real-time, such as processing video or audio files, handling large amounts of network traffic, handling HTTP requests/responses, and reading or writing large files. Node.js applications are best suited for developing data streaming applications.
Model-View-Controller (MVC) Architecture
MVC stands for Model-View-Controller. It is an architectural pattern used for developing user interfaces. It reduces complexity in larger applications and separates presentation from the domain. Making model code separate—without reference to any UI—allows it to be modeled more correctly. The MVC architecture contains three components:
- Model: Responsible for maintaining the behavior and data of an application.
- View: Used to display the model in the user interface.
- Controller: Acts as an interface between the Model and the View components.
Model: It represents the data and the business logic of the application. It directly manages the data, logic, and rules of the application.
View: It represents the UI of the application and is responsible for rendering data from the model to the user interface.
Controller: It takes user input, processes it (by manipulating the model), and updates the view.
Sessions in Express.js
Sessions are handled in Express using the library Express-session.
npm install --save express-session
The session middleware handles all things by enabling persistent user sessions, i.e.,
- Creating the session: This session holds user-specific data, such as login status, and assigns a unique session ID to the user. This ID is stored in the backend and sent to the browser.
- Setting the session cookie where the session ID is stored.
- Creating or attaching the session object in the
req
object: This allows you to store or access user-specific data (like login status, user preferences, etc.) directly in the session object.
Whenever a request comes from the same client again, we will have their session information stored on the server or in the database. We can add more properties to the session object. Session middleware transforms HTTP interactions into stateful experiences, allowing the server to recognize and track users across requests with unique session IDs, ensuring continuous user-state monitoring and enhanced security.
Session Options
There are options for manipulating the session. Details of each common option, such as httpOnly
, secret
, and secure
, can be found in the official documentation.
secret
: Key for signing the session cookie.resave
: Save session even if unmodified.saveUninitialized
: Save new but unmodified sessions.cookie
: Options for session cookie.secure
: Ensures the cookie is only sent over HTTPS.maxAge
: Sets the cookie’s expiration time in milliseconds (e.g.,24 * 60 * 60 * 1000
for 1 day).httpOnly
: Ensures the cookie is not accessible via JavaScript (helps prevent cross-site scripting attacks).path
: Sets the path for which the cookie is valid.domain
: Defines the domain for which the cookie is valid.
store
: Custom session store (e.g., Redis, MongoDB).rolling
: Refresh the cookie expiration on each request.
Express.js Framework
Express.js is a minimalist web application framework for Node.js that simplifies building server-side applications. It is open-source and designed on top of Node.js.
Advantages of Express.js
- Quick development
- Makes it easier to create and manage routes.
- Handles HTTP requests and responses efficiently.
- Lightweight and flexible.
- High performance
- Supports Model-View-Controller (MVC) architecture patterns
Cross-Origin Resource Sharing (CORS)
Cross-Origin Resource Sharing (CORS) is required when the frontend and backend are on different origins (e.g., different ports or domains). Install it using npm install cors
.
It is a feature implemented by web browsers to restrict web applications from making HTTP requests to domains other than the one from which the application was loaded for security purposes. This is part of the web’s security model to prevent malicious sites from accessing data on another domain without the user’s knowledge.
CORS is not required when the frontend and backend share the same origin (e.g., when both are served from the same server in production). A request is considered cross-origin when:
- The protocol (e.g., http, https),
- The domain (e.g., example.com, api.example.com), or
- The port (e.g., 80, 443, 3000) differs between the requesting page and the server.
For instance, if your frontend (React app) is running on http://localhost:3000 and it makes a request to an Express backend running on http://localhost:5000, this is considered a cross-origin request.
How CORS Works
- When a frontend (client) makes a cross-origin request to a server, the browser first sends a preflight request (an OPTIONS request) to check if the server will allow the actual request.
- If the server responds with the correct headers, the browser allows the actual request.
Axios vs. Fetch
- Axios is a stand-alone third-party package that needs to be installed. Fetch is built into most modern browsers; no installation is required.
- Axios has built-in XSRF (Cross-Site Request Forgery) protection. Fetch does not.
- Axios
data
contains the object. Fetch’sbody
has to be stringified. - Axios request is ok when
status
is 200 &statusText
is ‘OK’. Fetch request is ok when the response object contains theok
property. - Axios performs automatic transforms of JSON data. Fetch is a two-step process when handling JSON data—first, to make the actual request; second, to call the
.json()
method on the response. - Axios allows canceling requests and request timeout. Fetch does not.
- Axios has the ability to intercept HTTP requests. Fetch doesn’t provide a way to intercept requests.
- Axios has built-in support for download progress. Fetch does not support upload progress.
- Axios has wide browser support. Fetch only supports Chrome 42+, Firefox 39+, Edge 14+, and Safari 10.1+.
Express.js vs. Node.js
- Express.js is a Node.js framework that helps to build the backend of a web application. Node.js is a JavaScript runtime environment that builds both the frontend and backend of an application.
- The framework was written with the help of JavaScript. It was written with a combination of different programming languages, including JavaScript, C++, and C.
- It is considered a web framework. It is not a web framework.
- Express is a framework for Node.js. It was developed on Google’s V8 JavaScript engine.
- Developing ExpressJS server-side applications on Node.js. Building server-side and networking applications.
- Encourage quick development of web applications on Node.js. Encourage building scalable and fast web applications, alongside real-time collaborative editing apps.
- Works on the server-side. Works both on the server-side and client-side.
- Provides routing components and middleware functions to develop web applications. Provides an expansive range of features to develop an effective web application.
- Supports JavaScript. Supports several other languages, including CoffeeScript, TypeScript, and Ruby.
- Used by renowned brands like PayPal, Fox Sports & IBM. Used by business tycoons, like Uber, LinkedIn, Walmart, etc.
React Hooks
React Hooks are special functions introduced in React 16.8 that allow function components to have access to state and other React features, so that we do not need to use class components.
- When we write a function component and want to add some state to it, we will need to use Hooks.
- Hooks simplify the way you can manage component state, side effects, and other lifecycle features without writing classes.
- We import Hooks from
react
. - Hooks will not work in React class components.
- We can have built-in Hooks or custom Hooks.
React Hooks Types
The built-in Hooks can be divided into two parts:
- Basic Hooks
useState
useEffect
useContext
- Additional Hooks
useRef
useReducer
: An alternative touseState
for managing complex state logic like ReduxuseCallback
useMemo
useImperativeHandle
useLayoutEffect
useDebugValue
Features of Express.js
Middleware
Functions that execute during the lifecycle of a request to the server.
Routing
Handling different HTTP requests (GET, POST, PUT, DELETE).
Templating Engine Support
ExpressJS allows you to choose from varied templating engines, such as Handlebars, Pug, and EJS to deliver dynamic HTML pages. These engines enable developers to create dynamic HTML pages and simplify the process of rendering views. Understanding how to use template engines and modify views based on user data is important for building user-friendly applications.
Error Handling
Handling errors effectively is essential for preventing bugs and improving application performance. In ExpressJS, middleware can be used to catch and express handle errors that occur during request processing. It can be defined using the app.use()
.
Request and Response
Objects that allow interaction between the client and the server.
Security Best Practices in Express.js
Use middleware or other measures to prevent Cross-Site Scripting (XSS), SQL Injection, Brute-force attacks, Data leaks, etc.
Middleware in Express.js
Middleware gets executed after the server receives the request and before the controller actions send the response. Middleware has access to the request object, responses object, and next
. It can process the request before the server sends a response.
An Express-based application is a series of middleware function calls. Middleware is a request handler that allows you to intercept and manipulate requests and responses before they reach route handlers. They are the functions that are invoked by the Express.js routing layer. It is a flexible tool that helps in adding functionalities like logging, authentication, error handling, and more to Express applications.
The basic syntax for the middleware functions is: app.get(path, (req, res, next) => {}, (req, res) => {})
The middle part (req,res,next)=>{}
is the middleware function. Middleware functions take 3 arguments: the request object, the response object, and the next
function in the application’s request-response cycle, i.e., two objects and one function. It executes some code that can have side effects on the app and usually add information to the request or response objects. If they don’t send the response when they are done, they start the execution of the next function in the stack. This triggers calling the 3rd argument, next()
.
Router in Express.js
A router defines endpoints that handle various HTTP requests (GET, POST, PUT, DELETE), as well as URL paths/patterns for incoming requests and a function that is called to handle that pattern. This enables us to easily map requests to specific functions and customize responses based on user inputs.
To design RESTful APIs or web application endpoints, the router divides application logic into modular components based on routes, with callbacks or middleware executed for each route to quickly access request parameters, query strings, and bodies. The app.get()
, app.put()
, app.post()
, and app.delete()
are one of the most common approaches to describe routes that respond to diverse HTTP and URLs.
Express.js’ Router class provides a middleware-like framework for routing within Node.js applications, allowing route-handling logic to be modularized and organized. It lets you define routes, specify HTTP methods, and run callbacks or middleware.
Importance of Routing
- Handling HTTP Requests: Routing is necessary for handling different types of HTTP requests (GET, POST, PUT, DELETE). Each request is mapped to a specific function to handle the operation, such as fetching data, submitting a form, or deleting a record.
- Organizing Code: Routing provides structure to your application by defining a clear organization for handling different requests and endpoints. This avoids having a single function handle all possible requests.
- Creating APIs: When building APIs, routing allows the definition of various endpoints that return specific data or perform actions based on the client request.
HTTP Methods
- GET() Method: The GET() method is essentially a request for data or resources that is served by the server with a status code of 200, which means “OK”. If the request is invalid, it will return the status code 404.
- POST() Method: The POST() method is used to deliver data from the client to the server, which then responds with a status code of 200. If the request is invalid, the status code 404 is returned, just as the GET() method.
- PUT() Method: The PUT() method is used to update the existing data of a single object on the server, and it returns a status code of 200. If the request is invalid, a 404 status code is returned, similar to the GET() and POST() methods.
- DELETE() Method: The DELETE() method is used to erase data from the server; it also returns a status code of 200. If the request is invalid, the status code 404 is returned, just like any other HTTP method.
Node.js
Node.js is a runtime, similar to the Java virtual machine, that converts JavaScript code into machine code. It is used to develop I/O-intensive web applications like video streaming sites, single-page applications, and other web applications.
With Node.js, it is possible to use JavaScript as a backend. With JavaScript already being a popular choice for frontend development, application development around MERN (MongoDB, Express, React, and Node.js) and MEAN (MongoDB, Express, Angular, and Node.js) stacks is being increasingly employed by developers. Node.js is used for server-side programming with JavaScript. Hence, you can use a single programming language (JavaScript) for both front-end and back-end development.
Node.js implements asynchronous execution of tasks in a single thread with async
and await
technique. This makes Node.js applications significantly faster than multithreaded applications. Node.js is being used to build command-line applications, web applications, real-time chat applications, REST APIs, etc.
Strategies for Handling Asynchronous Operations
- Callbacks: Callbacks are the traditional way of handling asynchronous operations in NodeJS. A callback function is passed as an argument to an asynchronous function and is executed once the operation completes. It is effective, but nesting callbacks can lead to callback hell, making code difficult to read & maintain.
- Promises: Promises provide a cleaner alternative to callbacks for managing asynchronous operations. A promise represents the eventual completion or failure of an asynchronous task and allows the chaining of
then()
andcatch()
methods to handle success and error conditions respectively. Promises help mitigate callback hell and improve code readability. - Async/Await:
async/await
syntax offers a more concise and synchronous-like way of handling asynchronous operations in Node. Async functions return promises implicitly, allowing developers to write asynchronous code in a synchronous style.Async/await
simplifies error handling and enhances code readability compared to traditional callback-based approaches.
Flux Architecture
Handling data inside a client-side application is a challenging and complex task. Flux is a method of handling complex data.
Flux is the application architecture for building client-side web applications with React. It is a programming concept, where the data is unidirectional. The data enters the app & flows through it in one direction until it is rendered on the screen. It is useful when the project has dynamic data, and we need to keep the data updated in an effective manner. There’s no official Flux library, but it’s a pattern we can use in React. It reduces runtime errors.
Flux Architecture Elements
- Actions
- Dispatcher
- Stores
- Views (React components)
Stores
- It primarily contains the application state and logic.
- It is similar to the model in a traditional MVC.
- It is used for maintaining a particular state within the application, updates themselves in response to an action, and emits the change event to alert the controller view.
Views
- It is also called controller-views.
- It is located at the top of the chain to store the logic to generate actions and receive new data from the store. It is a React component that listens to change events and receives the data from the stores and re-renders the application.
Actions
- The action is what triggers the sequence of events & defines actions that describe what happened.
- The dispatcher method allows us to trigger a dispatch to the store and include a payload of data, which we call an action.
- It is an action creator or helper method that passes the data to the dispatcher.
Dispatcher
It is a central hub for dispatching actions in the React Flux application & it manages all data flow of the Flux application. It is a registry of callbacks into the stores. It has no real intelligence of its own, and simply acts as a mechanism for distributing the actions to the stores. A store registers itself and provides a callback. When an action creator provides a new action to the dispatcher, all stores receive that action via the callbacks in the registry. The dispatcher’s API has five methods.
Need for Flux
Flux addresses the remaining issues by introducing a structured, unidirectional flow of data:
Unidirectional Data Flow
- Flux enforces a clear, predictable flow:
- Actions are dispatched.
- Stores update in response to actions.
- Views re-render based on the state from stores.
- This prevents chaotic data flow and ensures that state changes are predictable.
Centralized State Management
- Dispatcher: Central hub for managing all actions and coordinating updates.
- Stores: Hold application state and logic, reducing the complexity of managing state spread across various components.
Decoupling and Scalability
Components do not directly modify each other’s state but interact through the Flux pattern, allowing for easier scalability and debugging.
Debugging and Testing
With a clear flow, debugging becomes more manageable as you can track what action led to a particular state change, something that is hard in both direct mutation (Code 1) and the observer pattern (Code 2).
Callbacks in Node.js
In Node, a callback is a non-blocking function that executes upon task completion, enabling asynchronous processing. It facilitates scalability by allowing Nodejs to handle multiple requests without waiting for operations to conclude. Node.js makes heavy use of callbacks. All the APIs of Node are written in such a way that they support callbacks. The callback is called when the function that contains the callback as an argument completes its execution, and allows the code in the callback to run in the meantime.
File System in Node.js
In Node.js, to handle file operations like creating, reading, deleting, etc., an inbuilt module called FS (File System) has been provided.
var fs = require("fs")
Features of FS module
- Asynchronous and Synchronous Methods:
- Every method in the
fs
module has synchronous and asynchronous forms. - Asynchronous methods take a last parameter as a completion function callback.
- Asynchronous method is preferred over synchronous method because it never blocks the program execution whereas the synchronous method blocks.
- Every method in the
- Error Handling: Includes robust error handling to manage issues such as file not found or permission errors.
- Directory Management: Allows creation, deletion, and listing of directories.
File Operations
- Open Files
- Read Files
- Write Files
- Append Files
- Close Files
- Delete Files