This commit is contained in:
mrinal1224
2024-05-07 12:57:28 +05:30
parent b6c1969f0e
commit 98aa698dd9
48 changed files with 24210 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
## Agenda
We will try to cover most of these topics in today's sessions and the remaining in the next.
* Build the Home Page
* Map movies to theaters
* Build the shows components
* Build the seating arrangements
* Book tickets
* Make Payments
* Deploy
It is going to be a bit challenging, advanced, but very interesting session covering topics that are asked very frequently in interviews.
So let's start.
## Build the Home Page
Whenwever you login to the home page you must see what all movies are playing and then you want to book that particular movie.
But before moving that as we left a part in shows component , basically the working of the delete button.
**Asking the question:**
Have you attempted the homework of making the delete button?
**No**
Lets quickly write the code for the delete button, lets add the route of the "delete-show".
Basically on hitting the endpoint "/delete-show", will await the shows model and find by id to delete by getting the showID and then respectively shows the sucess message or the respective error.
For this route, will go to the client and make the axios instance for this to delete-movies. So now will be writing the axios instance where we will bw hitting on the route "/api/theatres/delete-show" and sending the id in the payload.
Now will be writing the handledelete method that will be accepting the id and will be calling the deleteshow and id will taken as payload and the `getdata()` gets called or else the error message. and just put this handledelete in the onClick.
Now just demonstarate, by inserting the show name, date, time, movie, ticket_price, and total_seats. Now will be trying the delete button.
Lets start with home page,
### Start by Discussing the Wireframe of The Homepage
* In the home page, will be search part for searching the movies name.
* Then will be adding the posters of the different movies which will be clickable that goes to the some another page.
* On the other page , that will have the details of the movies and below that there will details of the theaters and in the nother space there will be date picker. The available theaters with the selected movie and date/time will be shown with name and time.
* When we will click on the theaters components, that will consist of the seating arrangements and then the users will be able to select the particular seat.
* Then after clicking on the Book Ticket button which will go to the another page that will show the movies details with seat, amount, and the make payment button.
* On clicking the make_payment button that will take us to the stripe portal to make the payments.
### Let's start changing the index.js file as follows:
First of all add the input space for searching the movie name and give classname as search-input.
* Add the height and border-radius to the search-input to give some styling.
* Now as you see in the mongodb in the movies we are having the information of the movies stored.
* To get the details of the movies to add them in the cards that will be done using the get_all_movies route.
* To start building the card, lets divide it int row and col using antd.
* In the row, there will be movies title, seach space and the column properties.
* Now will bw creating the two states one for the movies and other for the searchText using useState.
* Define the method of getdata for the getting all the movies by using loader and dispatch by useDispatch.
* To call the getData() method , use the useEffect and will get all the movies. Also import the HideLoading and ShowLoading from the loaderSlice. Also the message from the antd.
* For the searchText , will be adding the onChange on the event that will use the setSearchText method with the event's target value. And basically the search for the particular text by after converting to the lower case is done with the movies title.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/390/original/upload_b9165fabb38882138bfde830cd6e8612.png?1695983804)
### Mapping of the Theater and Shows
As there are multiple theaters, with multiple shows and multile movies, then how to map the correct movies details, lets discuss this:
For example in the shows at PVR Superplex having the show of the movie Captain America.
Let's suppose they are having shows of the movies at 3 PM and 9PM so are they will be displayed two times.
Do they show they show the PVR Superplex haing two shows one at the 3pm and the other at 9pm. Or do they show like PVR Superplex two times with differently. The first one is the better approach like in the below image.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/391/original/upload_1e89157fcafdf4aea3f4657bb8190f61.png?1695983835)
Like one theater one time, and then we can add on the shows of different time as above.
We have to use the route parameter for the different routes for the different movies by sending the movies id.
Lets import the useNavigate from the react-router-dom, use this in the navigate method where we are changing the movie_id. Also import moment.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/393/original/upload_75f0d47c5e4a2f8b8f02d693f814204a.png?1695983857)
Hence the route changes as we click on the movie as seen in the route we got the movie id along with the date. Same as we observer on the different websites like amazon.
So now we have to show the movies details along with the show details let start,
* We have to take a another component, names as TheatreForMovies.
* In the app.js whenever there will be route of type movie with id, then we will go to this component of theatresForMovies.
* First of all we have to just build the movies_Details and then the date picker.
* Using the movie id we can get all the data using the function `getData()`.
---
title: Map Movies to the theaters
description: Explanation of all the related components and the routes for the mapping page of the movies with theaters.
duration: 2100
card_type: cue_card
---
## Map Movies to the theaters
**Get a movie by ID route**
Lets create the route for this, go to the movie-route.js by just passing the id, so by simply attaching the movie_id with the axiosInstance we can get the id using the route and api call.
Here's a explanation of the code:
1. **Imports**:
- The code imports necessary modules and functions from various libraries such as Ant Design (`antd`), React (`react`), React Router (`react-router-dom`), and others.
2. **React Hooks**:
- The `useParams` and `useNavigate` hooks are used from React Router to access the URL parameters and navigate between routes, respectively.
- The `useState` and `useEffect` hooks are used to manage component state and perform side effects when the component mounts.
3. **Redux Dispatch**:
- The `useDispatch` hook is used to get the Redux dispatch function, which will be used later to dispatch actions.
4. **Data Fetching with getData Function**:
- The `getData` function is defined as an asynchronous function. It is responsible for fetching movie data by its ID from an API using the `GetMovieById` function from the `apicalls/movies` module.
- While fetching data, a loading spinner is displayed using `dispatch(ShowLoading())`.
- After receiving a response, it checks if the response was successful. If successful, it sets the movie data and displays a success message; otherwise, it displays an error message.
- Finally, it hides the loading spinner using `dispatch(HideLoading())`.
5. **Render Movie Information**:
- The movie information is displayed in JSX within a conditional rendering block (`movie && ...`) to ensure that the component renders only when `movie` data is available.
- It displays various details about the movie, such as title, language, duration, release date, and genre. The `moment` library is used to format the release date.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/394/original/upload_5ccf189b12d1fb2422a35e17af842a02.png?1695983908)
**Addition of the date picker using moment:**
* The component will render a section that includes a label "Select Date" and a date picker input field.
* The date picker will have a minimum date restriction set to the current date. Users won't be able to select a date earlier than today's date.
* When a user interacts with the date picker and selects a new date, an onChange event will be triggered.
* Additionally, the code uses the navigate function (likely from React Router) to navigate to a new URL. This new URL includes query parameters with the movie ID (params.id) and the selected date. For example, if the user selects a date of "2023-09-20" for a movie with an ID of "123," the URL might become something like "`/movie/123?date=2023-09-20`."
* The change in the URL, including the selected date as a query parameter, can trigger a different route in your application or update the content displayed on the page.
* This enables your application to react to the selected date and possibly show movie-related information for the chosen date.
**Now start creating threaters and shows running on them:**
Lets start by getting the theater, using the id of the theater for example if the bruce having two 2 theaters with JAWAN movies and the Maek has 1 theater with Captain America then if the user selecting the JAWAN movies how many theater should be given?
**Asking Question** So exactly the 2 theaters should be given which are havin the movie JAWAN.
Lets get all the unique theaters which have the shows of a movie
It's an Express.js route handler that aims to retrieve all unique theaters that have shows of a specific movie on a given date. Here's the explanation:
1. **Route Definition**:
- This code defines an Express.js POST route at the endpoint `/get-all-theatres-by-movie`. It expects the request to be authenticated using the `authMiddleware` middleware.
2. **Request Parsing**:
- In the route handler, it extracts the `movie` and `date` parameters from the request body using destructuring: `const { movie, date } = req.body;`.
3. **Fetching Shows**:
- It then queries the database to find all the shows of a specific movie on the given date using the `Show` model. The `find` method is used with the `movie` and `date` parameters.
- Additionally, it uses `.populate('theatre')` to populate the `theatre` field in the `shows` collection with corresponding theater information.
4. **Finding Unique Theaters**:
- The code initializes an empty array called `uniqueTheatre` to store unique theater data.
5. **Sending Response**:
- The route handler sends a JSON response to the client, indicating that the request was successful (`success: true`).
- The `message` field provides a success message, stating "Unique Data Fetched."
- The `data` field in the response is set to the `uniqueTheatre` array, which contains the unique theater information.
6. **Error Handling**:
- In case an error occurs during the process (e.g., a database query error), the catch block is executed. It sends a JSON response indicating the failure (`success: false`) and includes an error message in the `message` field.
The provided code is a JavaScript function that exports a function named `GetTheatresByMovie`. This function is likely used to make an HTTP POST request to a specific API endpoint in order to get theaters that are showing a particular movie on a given date. Here's an explanation of the code:
1. **export const GetTheatresByMovie = async (payload) => { ... }**:
- This line exports a function called `GetTheatresByMovie` as a named export from the module.
- The function is defined as an asynchronous function, indicating that it can perform asynchronous operations, such as making HTTP requests.
2. **try { ... } catch (err) { ... }:**
- The function is wrapped in a try-catch block, which is used for error handling. It attempts to perform the specified operation within the try block and catches any errors that may occur.
3. **const response = await axiosInstance.post('/api/theatres/get-all-theatres-by-movie', payload);:**
- Inside the try block, the code uses the `axiosInstance` (presumably an Axios instance configured for making HTTP requests) to send an HTTP POST request to the `/api/theatres/get-all-theatres-by-movie` endpoint.
- The `payload` variable is expected to contain the data to be sent as the request body. This likely includes information about the movie and the date.
4. **return response.data;:**
- If the HTTP request is successful (i.e., the server responds with a valid JSON response), the function returns the `data` property of the response. This typically contains the data returned by the server, such as theater information.
5. **return err.response:**
- If an error occurs during the HTTP request (e.g., a network error or a server error), the catch block returns the `response` property of the `err` object.
The provided code appears to be a JavaScript function named `getTheatres`. This function is designed to fetch theater information related to a specific movie and date, similar to the previous code you shared. Here's an explanation of this code:
1. **const getTheatres = async () => { ... }:**
- This function is defined as an asynchronous function and does not take any arguments.
2. **try { ... } catch (err) { ... }:**
- The function is wrapped in a try-catch block for error handling. It attempts to perform the specified operations within the try block and catches any errors that may occur.
3. **dispatch(ShowLoading());:**
- Before making the API request, it dispatches an action (likely a Redux action) to show a loading spinner or loading indicator, indicating that data is being fetched.
4. **const response = await GetTheatresByMovie({ date, movie: params.id });:**
- Inside the try block, it calls the `GetTheatresByMovie` function, passing an object as the payload. This object includes the `date` and `movie` parameters required for the API request. `params.id` is presumably the movie ID.
- The function is awaited, meaning it will wait for the HTTP request to complete and return a response.
5. **console.log(response.data);:**
- It logs the `data` property of the response to the console. This typically contains the data returned by the server, which in this case, should be theater information related to the specified movie and date.
6. **Response Handling:**
- If the response indicates success (`response.success` is `true`), it updates the component state with the theater data using `setTheatres(response.data)` and displays a success message using `message.success(response.message)`.
- If the response indicates failure (`response.success` is `false`), it displays an error message using `message.error(response.message)`.
7. **dispatch(HideLoading());:**
- Finally, it dispatches an action (likely a Redux action) to hide the loading spinner or loading indicator, indicating that the data fetching process has completed.
8. **Error Handling:**
- In case an error occurs during the API request (e.g., network error or server error), the catch block is executed. It hides the loading indicator, and it displays an error message using `message.error(err.message)`.
**Code for the Unique Theaters:**
Route handler for fetching all unique theaters that have shows of a specific movie on a given date. Here's an explanation:
1. **Route Definition**:
- This code defines an Express.js POST route at the endpoint `/get-all-theatres-by-movie`. It expects the request to be authenticated using the `authMiddleware` middleware.
2. **Request Parsing**:
- In the route handler, it extracts the `movie` and `date` parameters from the request body using destructuring: `const { movie, date } = req.body;`.
3. **Fetching Shows**:
- It then queries the database (likely using an ORM like Mongoose) to find all the shows of a specific movie on the given date. The `Show` model is used to query the shows, and the `populate` method is used to retrieve associated theater information for each show: `const shows = await Show.find({ movie, date }).populate('theatre');`.
4. **Finding Unique Theaters**:
- The code initializes an empty array called `uniqueTheatre` to store unique theater data.
- It iterates through each `show` in the `shows` array using a `forEach` loop.
- For each `show`, it checks if there is already a theater with the same `_id` in the `uniqueTheatre` array.
- If no matching theater is found (`!theatre`), it filters all shows with the same theater `_id` and pushes a new object into the `uniqueTheatre` array. This object combines the theater data with an array of shows for that theater.
5. **Response**:
- Finally, the route handler sends a response to the client. If successful, it sends a JSON response with `success`, a success message, and the `uniqueTheatre` array containing theater information and associated shows.
6. **Error Handling**:
- In case of an error during the process (e.g., database query error), it sends a JSON response indicating the failure with an error message.
This route essentially retrieves all theaters that are showing a particular movie on a specific date, eliminating duplicates, and presents the data in a structured format, making it easier for clients to work with unique theater information and associated show details.

View File

@@ -0,0 +1,927 @@
## Agenda
We will try to cover most of these topics in today's sessions and the remaining in the next.
* Build the shows components
* Build the seating arrangements
* Book tickets
* Make Payments
* Deploy
It is going to be a bit challenging, advanced, but very interesting session covering topics that are asked very frequently in interviews.
So let's start.
## Build the Home Page
As till now we have completed the home page, where all the movies and upcoming movies are shown as below:
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/446/original/upload_2f04c7d551fcc86af920399ec3c6ad5b.png?1695995692)
As we click on the movies then the related information about the movies theaters and the shows timings should be visible in the below blank region.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/447/original/upload_5e3b842eca1765968a94ee60bebe0355.png?1695995733)
## Shows
Now we will be implementing those related functionalities as follows:
If we get this below data in the theaterforMovie then we can show the related shows and the timings and all.
```javascript
import { message } from "antd";
import { useEffect, useState } from "react";
import {useNavigate, useParams} from "react-router-dom"
import { GetMovieById } from "../apicalls/movies";
import { useDispatch } from "react-redux";
import { HideLoading, ShowLoading } from "../redux/loadersSlice";
import {GetTheatresByMovie} from "../apicalls/theatres"
import moment from "moment";
export default function TheatreForMovie(){
let [movie,setMovie] = useState()
let [theatres,setTheatres] = useState();
let [date,setDate] = useState(moment().format("YYYY-MM-DD"));
const [isHovering, setIsHovering] = useState(false);
const params = useParams();
const dispatch = useDispatch();
const navigate = useNavigate();
const getData = async() => {
try{
dispatch(ShowLoading());
const response = await GetMovieById(params.id)
if(response.success){
setMovie(response.data);
message.success(response.message)
}else{
message.error(response.message);
}
dispatch(HideLoading());
}catch(err){
dispatch(HideLoading());
message.error(err.message)
}
}
const getTheatres = async () => {
try{
dispatch(ShowLoading());
const response = await GetTheatresByMovie({date,movie:params.id})
console.log(response.data);
if(response.success){
setTheatres(response.data);
message.success(response.message)
}else{
message.error(response.message);
}
dispatch(HideLoading());
}catch(err){
dispatch(HideLoading());
message.error(err.message);
}
}
const handleMouseEnter = (id) => {
setIsHovering(true);
};
const handleMouseLeave = (id) => {
setIsHovering(false);
};
useEffect(() => {
getData();
},[])
useEffect(() => {
getTheatres()
},[date])
return (
movie && (
<div>
{/* movie information */}
<div className = "flex justify-between items-center mb-2">
<div>
<h1 className = "text-2xl uppercase">
{movie.title} ({movie.language})
</h1>
<h1 className = "text-md">Duration : {movie.duration} mins</h1>
<h1 className = "text-md">
Release Date : {moment(movie.releaseDate).format("MMM Do yyyy")}
</h1>
<h1 className = "text-md">Genre : {movie.genre}</h1>
</div>
<div className = "mr-3">
<h1 className = "text-md ">Select Date</h1>
<input
type = "date"
min = {moment().format("YYYY-MM-DD")}
value = {date}
onChange = {(e) => {
setDate(e.target.value);
navigate(`/movie/${params.id}?date=${e.target.value}`);
}}
/>
</div>
</div>
<hr />
{/* movie theatres */}
<div className = "mt - 1">
<h1 className = "text - xl uppercase">Theatres</h1>
</div>
<div className = "mt - 1 flex flex-col gap-1">
{theatres.map((theatre) => (
<div className = "card p-2">
<h1 className = "text-md uppercase">{theatre.name}</h1>
<h1 className = "text-sm">Address : {theatre.address}</h1>
<div className = "divider"></div>
<div className = "flex gap-2">
{theatre.shows
.sort(
(a, b) => moment(a.time, "HH:mm") - moment(b.time, "HH:mm")
)
.map((show) => (
<div key = {show._id} style = {{
backgroundColor: isHovering ? '#DF1827' : 'white',
color: isHovering ? 'white' : '#DF1827',
}}
onMouseEnter = {handleMouseEnter}
onMouseLeave = {handleMouseLeave}
className = "card p-1 cursor-pointer border-primary"
onClick = {() => {
navigate(`/book-show/${show._id}`);
}}
>
<h1 className = "text-sm">
{moment(show.time, "HH:mm").format("hh:mm A")}
</h1>
</div>
))}
</div>
</div>
))}
</div>
</div>
)
);
}
```
The code is a React component named `TheatreForMovie` that is responsible for displaying information about a movie and its associated theaters. It utilizes various React hooks and asynchronous API calls. Here's a breakdown of its functionality:
1. **State Management**:
- The component uses `useState` to manage state variables for `movie`, `theatres`, and `date`.
- It also manages the `isHovering` state to handle mouse hover events.
2. **Initialization**:
- The component extracts route parameters using `useParams()` and obtains a navigation function using `useNavigate()`.
- It also obtains a Redux dispatch function using `useDispatch()`.
3. **Data Retrieval**:
- Two asynchronous functions, `getData()` and `getTheatres()`, are defined to fetch movie details and theater information, respectively.
- These functions use API calls (e.g., `GetMovieById` and `GetTheatresByMovie`) to retrieve data.
- Loading states are managed using Redux actions (`ShowLoading` and `HideLoading`).
4. **Mouse Hover Effects**:
- `handleMouseEnter` and `handleMouseLeave` functions are used to toggle the `isHovering` state when hovering over theater showtimes.
- This is used to change the background and text color of showtime cards.
5. **Effect Hooks**:
- The `useEffect` hook is utilized to fetch movie data (`getData()`) when the component mounts. It runs once because it has an empty dependency array.
- Another `useEffect` hook is used to fetch theater data (`getTheatres()`) whenever the `date` state changes.
6. **Rendering**:
- The component conditionally renders the movie and theater information if the `movie` state exists.
- Movie information, including title, language, duration, release date, and genre, is displayed.
- A date input allows users to select a date, triggering a navigation change.
- Theater information is displayed, including theater name, address, and showtimes.
- Showtimes are sorted by time and displayed in cards that respond to hover events.
7. **Sorting of Theater Shows**:
- Within the rendering section of the component, theater shows are sorted based on their showtimes.
- This sorting is achieved through the `.sort()` method applied to the `theatre.shows` array.
- The sorting logic compares the showtimes, ensuring that shows are displayed in chronological order.
- The `moment` library is used to parse and format showtime values for comparison.
- As a result, theater shows are presented to users in ascending order of showtime, providing a clear and organized view of available showtimes for the selected date.
## Seating Component
Now lets start with the seating component, after selecting the show of the theater as follows.
```javascript
onClick = {() => {
navigate(`/book-show/${show._id}`);
}}
```
When a user clicks an element (like a button), the `onClick` function runs. It uses the `navigate` function to take the user to a new page, typically based on the show's ID.
### Get show by id
```javascript
//get show by id
router.post('/get-show-by-id',authMiddleware,async(req,res) => {
try{
const show = await Show.findById(req.body.showId)
.populate("movie")
.populate("theatre")
res.send({
success:true,
message:"Show fetched",
data:show,
})
}catch(err){
res.send({
success:false,
message:err.message
})
}
})
```
This specific route handles a POST request to retrieve information about a show by its ID. Let's break down what this code does:
1. **Route Definition**:
- This route is defined to listen for POST requests on a specific path, which is "/get-show-by-id".
2. **Middleware**:
- It includes an authentication middleware function called `authMiddleware`. This function is likely responsible for verifying the user's identity and ensuring that only authorized users can access this route.
3. **Request Handling**:
- When a POST request is made to this route, the server executes the code inside the `try` block.
4. **Show Retrieval**:
- Inside the `try` block, the code uses the `Show` model (presumably a MongoDB model) to find a show document in the database based on the provided `showId`. This is done using `Show.findById(req.body.showId)`.
- The `populate` method is used to retrieve additional information related to the show, specifically details about the associated movie and theater. This populates the "movie" and "theatre" fields in the `show` document with their respective data.
5. **Response**:
- If the show is successfully found and populated, a response is sent back to the client with a `success` status set to `true`.
- The response includes a message indicating that the show has been fetched and the `show` data itself.
6. **Error Handling**:
- If any errors occur during the process (e.g., the show with the provided ID doesn't exist or there's an issue with the database), the code inside the `catch` block is executed.
- In case of an error, a response is sent back to the client with a `success` status set to `false`, and an error message is provided.
In summary, this route is designed to handle POST requests to fetch details about a show by its ID. It uses authentication middleware to secure the route, retrieves the show data from a database, populates related movie and theater information, and sends a response with the retrieved data or an error message, depending on the outcome of the database operation.
```javascript
export const GetShowById = async (payload) => {
try{
const response = await axiosInstance.post('/api/theatres/get-show-by-id',payload)
return response.data
}catch(err){
return err.message
}
}
```
The `GetShowById` function serves as a client-side utility for making HTTP POST requests to a server's `/api/theatres/get-show-by-id` endpoint. It is designed to retrieve specific show information based on a provided `payload`, which typically includes data necessary for the request, such as a `showId`.
The function operates asynchronously, utilizing Axios to handle the HTTP request. Within a `try...catch` block, it awaits the completion of the request, ensuring that the program doesn't proceed until a response is received.
On success, the function extracts and returns the response data from the server using `return response.data`. This allows the caller to access and utilize the fetched show data.
However, in the event of an error—whether due to network issues, server problems, or other reasons—the `catch` block captures and handles the error.
### How Many Seats Are There for That Show
Different shows have different number of the shows as below:
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/448/original/upload_04c10a7c926af4fbb8d515abf9873734.png?1695995938)
```javascript
import { message } from "antd";
import { useEffect, useState } from "react"
import { useParams, useNavigate } from "react-router-dom";
import { GetShowById } from "../apicalls/theatres";
import {BookShowTickets, MakePayment } from "../apicalls/bookings";
import Button from "../components/Button";
import { HideLoading, ShowLoading } from "../redux/loadersSlice";
import StripeCheckout from "react-stripe-checkout";
import moment from "moment"
import { useDispatch, useSelector } from "react-redux";
export default function BookShow(){
const { user } = useSelector((state) => state.users);
const [selectedSeats, setSelectedSeats] = useState([]);
let [show,setShow] = useState();
const params = useParams();
const dispatch = useDispatch();
const navigate = useNavigate()
const getData = async() => {
try{
const response = await GetShowById({showId:params.id})
if(response.success){
setShow(response.data);
}else{
message.error(response.message)
}
}catch(err){
message.error(err.message)
}
}
const getSeats = () => {
const columns = 12;
const totalSeats = show.totalSeats; // 120
const rows = Math.ceil(totalSeats / columns); // 10
return (
<div>
<p className = "m - 4">Screen This Side</p>
<hr/>
<div className = "flex gap-1 flex-col p-2 card">
<hr/>
{Array.from(Array(rows).keys()).map((seat, index) => {
return (
<div className = "flex gap-1 justify-center">
{/* 0, 1 ,2, ,3, .. 11
0 [ [ 0, 1, 2, 3, 4, 5,6,7.. ,11],
1 [0, 1, 2, 3, .. ,11],
2 .
.
9 [0,1,2 , .... 11]
] */}
{Array.from(Array(columns).keys()).map((column, index) => {
const seatNumber = seat * columns + column + 1;
// 12*1 + 3+ 1 = 16
let seatClass = "seat";
// seat = 0 // coloumns = 12
//0 + 1 + 1 = 2
if (selectedSeats.includes(seat * columns + column + 1)) {
seatClass = seatClass + " selected-seat";
}
if (show.bookedSeats.includes(seat * columns + column + 1)) {
seatClass = seatClass + " booked-seat";
}
return (
seat * columns + column + 1 <= totalSeats && (
<div
className = {seatClass}
onClick = {() => {
if (selectedSeats.includes(seatNumber)) {
setSelectedSeats(
selectedSeats.filter((item) => item !== seatNumber)
);
} else {
setSelectedSeats([...selectedSeats, seatNumber]);
}
}}
>
<h1 className = "text-sm">{seat * columns + column + 1}</h1>
</div>
)
);
})}
</div>
);
})}
</div>
</div>
);
};
const book = async (transactionId) => {
try {
dispatch(ShowLoading());
const response = await BookShowTickets({
show: params.id,
seats: selectedSeats,
transactionId,
user: user._id,
});
if (response.success) {
message.success(response.message);
navigate("/profile");
} else {
message.error(response.message);
}
dispatch(HideLoading());
} catch (error) {
message.error(error.message);
dispatch(HideLoading());
}
};
const onToken = async (token) => {
// console.log(token)
try {
dispatch(ShowLoading());
const response = await MakePayment(
token,
selectedSeats.length * show.ticketPrice * 100
);
if (response.success) {
message.success(response.message);
// console.log(response.data);
book(response.data);
} else {
message.error(response.message);
}
dispatch(HideLoading());
} catch (error) {
message.error(error.message);
dispatch(HideLoading());
}
};
useEffect(() => {
getData();
},[])
return (
show && (
<div>
{/* show infomation */}
<div className = "flex justify-between card p-2 items-center">
<div>
<h1 className = "text-sm">{show.theatre.name}</h1>
<h1 className = "text-sm">{show.theatre.address}</h1>
</div>
<div>
<h1 className = "text-2xl uppercase">
{show.movie.title} ({show.movie.language})
</h1>
</div>
<div>
<h1 className = "text-sm">
{moment(show.date).format("MMM Do yyyy")} - {" "}
{moment(show.time, "HH:mm").format("hh:mm A")}
</h1>
</div>
</div>
{/* seats */}
<div className = "flex justify-center mt-2">{getSeats()}</div>
{selectedSeats.length > 0 && (
<div className = "mt-2 flex justify-center gap-2 items-center flex-col">
<div className = "flex justify-center">
<div className = "flex uppercase card p-2 gap-3">
<h1 className = "text-sm"><b>Selected Seats</b> : {selectedSeats.join(" , ")}</h1>
<h1 className = "text-sm">
<b>Total Price</b> : {selectedSeats.length * show.ticketPrice}
</h1>
</div>
</div>
<StripeCheckout
token = {onToken}
amount = {selectedSeats.length * show.ticketPrice * 100}
billingAddress
stripeKey = "pk_test_eTH82XLklCU1LJBkr2cSDiGL001Bew71X8"
>
<Button title = "Book Now" />
</StripeCheckout>
</div>
)}
</div>
)
);
}
```
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/449/original/upload_8519871882badc6cea20e45f3b757fda.png?1695996092)
Let's dive into the `BookShow` component and focus on the details, particularly the part that handles rendering the seats with the `for` loops:
1. **Component Structure**:<br> The `BookShow` component is a React functional component responsible for booking show tickets. It receives data about a show and allows users to select seats for booking.
2. **Fetching Show Data**:<br> The `getData` function is called when the component mounts (`useEffect`). It fetches details about the show specified by the `params.id` using the `GetShowById` API call and sets the show data in the component's state.
3. **Rendering Seats**:
- The `getSeats` function is responsible for rendering the seats grid. It calculates the number of rows and columns based on the `totalSeats` of the show.
- It returns a JSX structure that represents the seats grid. The grid is divided into rows and columns using nested `map` functions.
```jsx
{Array.from(Array(rows).keys()).map((seat, index) => {
return (
<div className="flex gap-1 justify-center">
{Array.from(Array(columns).keys()).map((column, index) => {
// ... Seat rendering logic
})}
</div>
);
})}
```
- The outer `map` iterates over the rows, and the inner `map` iterates over the columns. For each seat, it calculates a unique `seatNumber` based on the row and column indices.
- The seat's appearance (e.g., styling) depends on its characteristics, such as whether it's selected or booked. This is determined by the `seatClass`.
- Seats that are already booked (`show.bookedSeats`) are given a "booked-seat" class.
- Seats that are currently selected by the user are given a "selected-seat" class.
- Seats that are neither booked nor selected are rendered without any additional class.
4. **Selecting and Deselecting Seats**:
- Seats can be selected or deselected by clicking on them. This interaction is handled by the `onClick` event attached to each seat `<div>`.
- When a seat is clicked, the `setSelectedSeats` function is used to update the `selectedSeats` state based on whether the clicked seat is already in the selected seats array or not.
5. **Displaying Selected Seats and Total Price**:
- Below the seat grid, there's a section that displays the selected seats and the total price. This section is conditionally rendered only when there are selected seats (`selectedSeats.length > 0`).
- It shows the selected seat numbers joined by a comma and calculates the total price based on the number of selected seats and the ticket price for the show.
6. **Booking Seats and Making Payments**:
- The component includes functions (`book` and `onToken`) for booking seats and handling payment. When the user confirms the booking, these functions are called to initiate the booking process and make a payment.
## Seat Selection/Deselection Functionaities
```javascript
if (selectedSeats.includes(seat * columns + column + 1)) {
seatClass = seatClass + " selected-seat";
}
if (show.bookedSeats.includes(seat * columns + column + 1)) {
seatClass = seatClass + " booked-seat";
}
return (
seat * columns + column + 1 <= totalSeats && (
<div
className = {seatClass}
onClick = {() => {
if (selectedSeats.includes(seatNumber)) {
setSelectedSeats(
selectedSeats.filter((item) => item !== seatNumber)
);
} else {
setSelectedSeats([...selectedSeats, seatNumber]);
}
}}
>
<h1 className = "text-sm">{seat * columns + column + 1}</h1>
</div>
)
);
```
- **Class Assignment**:
- The `seatClass` variable is initially set to an empty string for each seat.
- The first `if` statement checks if the current seat number (calculated as `seat * columns + column + 1`) is included in the `selectedSeats` array. If it is, the `"selected-seat"` class is appended to `seatClass`.
- The second `if` statement checks if the seat number is included in the `show.bookedSeats` array. If it is, the `"booked-seat"` class is appended to `seatClass`.
- **Rendering Seats**:
- The code then returns a `div` element representing the seat.
- The condition `seat * columns + column + 1 <= totalSeats` ensures that only valid seats are rendered based on the total number of seats available.
- The `className` attribute is set to `seatClass`, which may include `"selected-seat"` and/or `"booked-seat"` classes based on the conditions.
- An `onClick` event handler is attached to the seat `div`. When a user clicks on a seat, it triggers this function.
- Inside the `onClick` function:
- If the clicked seat number is already in the `selectedSeats` array, it's removed from the array using `selectedSeats.filter()`. This means the user is deselecting the seat.
- If the clicked seat number is not in `selectedSeats`, it's added to the array, indicating the user's selection.
```css!
/* seats */
.seat {
height: 30px;
width: 50px;
display: flex;
justify-content: center;
align-items: center;
border : 1px solid #b1b1b1;
cursor: pointer;
}
.selected-seat {
background-color: #118838;
}
.selected-seat h1 {
color: white !important;
}
.booked-seat {
background-color: #b1b1b1;
cursor: not-allowed;
pointer-events: none;
}
```
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/450/original/upload_c4f6952777f7fe93082df2c33a466bea.png?1695996181)
The provided CSS code defines styles for rendering seats in a grid. Each seat (`.seat`) has a height and width, is centered both horizontally and vertically, and has a border to separate seats. The `cursor` property changes to a pointer to indicate interactivity. Selected seats (`.selected-seat`) have a green background and white text for visibility.
Booked seats (`.booked-seat`) have a gray background and a disabled cursor, preventing further interaction. These styles effectively visualize seat availability and user selections, enhancing the user experience in a seat selection interface.
## Add the Payment Options
"React Stripe Checkout" refers to a React library or component that allows developers to easily integrate Stripe's payment processing capabilities into their React applications. This library streamlines the process of creating a checkout experience, handling payments, and managing user interactions with the Stripe API.
Key features and steps typically involved in using "React Stripe Checkout" include:
1. **Installation**:<br> Developers can install the library in their React project using npm or yarn.
2. **Configuration**:<br> Setting up Stripe credentials and configuring payment options, such as currency and amount.
3. **Integration**:<br> Adding the "React Stripe Checkout" component to the application's payment page or modal.
4. **User Interaction**:<br> Users can enter their payment details, including card information.
5. **Tokenization**:<br> The library securely tokenizes the payment information, ensuring sensitive data is not exposed to the application server.
6. **Payment Processing**:<br> Stripe processes the payment using the token and completes the transaction.
```javascript
<StripeCheckout
token = {onToken}
amount = {selectedSeats.length * show.ticketPrice * 100}
billingAddress
stripeKey = "pk_test_eTH82XLklCU1LJBkr2cSDiGL001Bew71X8"
>
<Button title = "Book Now" />
</StripeCheckout>
```
The code snippet provided is an example of how you might use the "react-stripe-checkout" library to integrate Stripe's payment processing into a React application. Here's an explanation of the code:
1. **\<StripeCheckout> Component:** This is the main component from the "react-stripe-checkout" library, and it acts as a wrapper around your payment button. It allows you to configure and handle the payment process.
2. **token={onToken}:** This prop specifies the callback function (`onToken`) that will be called when the payment is successful. The `token` contains the payment information, which you can then use to process the payment on the server.
3. **amount={selectedSeats.length * show.ticketPrice * 100}:** This prop specifies the total amount to charge in the smallest currency unit (cents in this case). It calculates the total based on the number of selected seats and their individual ticket prices.
4. **billingAddress:** This prop indicates that you want to collect the customer's billing address during the payment process.
5. **stripeKey:** This prop is where you provide your Stripe API key (in test mode, as indicated by the "pk_test_" prefix). It connects your React app to your Stripe account.
6. **<Button title="Book Now" />:** This is the button or UI element that triggers the payment process when clicked. It's wrapped inside the `<StripeCheckout>` component, making it the "Pay Now" or "Book Now" button.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/451/original/upload_82e3bafc31613a0c60453969ca8cb490.png?1695996351)
When a user clicks the "Book Now" button, the `<StripeCheckout>` component will handle the payment process. If the payment is successful, it will call the `onToken` callback
function, allowing you to perform further actions, such as storing the payment details on your server or updating the UI to reflect the successful payment.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/452/original/upload_ae57e6a2ed7a0a47022fb038eb3b6a53.png?1695996513)
Make sure to replace the `stripeKey` value with your actual Stripe API key, and ensure that your Stripe account is set up correctly to handle payments in test mode.
In bookShow.js file make the below changes as follows:
```javascript
const onToken = async (token) => {
// console.log(token)
try {
dispatch(ShowLoading());
const response = await MakePayment(
token,
selectedSeats.length * show.ticketPrice * 100
);
if (response.success) {
message.success(response.message);
// console.log(response.data);
book(response.data);
} else {
message.error(response.message);
}
dispatch(HideLoading());
} catch (error) {
message.error(error.message);
dispatch(HideLoading());
}
};
```
* The onToken function is defined, which handles payment processing using Stripe.
* It dispatches actions for showing and hiding a loading indicator.
* It makes a payment request to the server using the MakePayment function and the payment token.
* Depending on the response, it displays success or error messages and potentially triggers further actions like booking.
In bookRoute.js file make the below changes as follows:
```javascript
const authMiddleware = require("../middlewares/authMiddleware");
const router = require("express").Router();
const stripe = require("stripe")(process.env.stripe_key);
const Booking = require('../models/bookingModel')
const Show = require('../models/showModel')
router.post('/make-payment',authMiddleware,async(req,res) => {
try{
const {token,amount} = req.body;
console.log(token);
const customer = await stripe.customers.create({
email:token.email,
source:token.id
})
const charge = await stripe.charges.create({
amount:amount,
currency:"usd",
customer:customer.id,
receipt_email:token.email,
description:"Ticket has been booked for a movie"
})
const transactionId = charge.id;
res.send({
success:true,
message:"Payment done, Ticket booked",
data:transactionId
})
}catch(err){
res.send({
success:false,
message:err.message
})
}
})
```
* The Express route "/make-payment" is defined, which expects a POST request with payment-related data, including the token and amount.
* It uses the Stripe API to create a customer and charge their card.
* If the payment is successful, it returns a success message along with a transaction ID.
* If there's an error, it returns an error message.
In booking.js file make the below changes as follows:
```javascript
import { axiosInstance } from "."
export const MakePayment = async(token,amount) => {
try{
const response = await axiosInstance.post('/api/bookings/make-payment',{token,amount});
return response.data
}catch(err){
return err.response
}
}
```
The MakePayment function is defined, which makes an API call to your server's "/make-payment" endpoint with the payment token and amount.
It returns the response data if successful or the error response if there's an issue.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/475/original/upload_9ed9f36f03a3bea064f0f0844874a731.png?1696008818)
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/476/original/upload_a136b39f32498d0585a1b504ab4c675b.png?1696008840)
## Bookings in the Profile
```javascript
const mongoose = require("mongoose");
const bookingSchema = new mongoose.Schema(
{
show: {
type: mongoose.Schema.Types.ObjectId,
ref: "shows",
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "users",
},
seats: {
type: Array,
required: true,
},
transactionId: {
type: String,
required: true,
},
},
{ timestamps: true }
);
module.exports = mongoose.model("bookings", bookingSchema);
```
* A Mongoose schema called bookingSchema is defined, representing the structure of booking documents.
* It includes fields such as show (a reference to the show), user (a reference to the user making the booking), seats (an array of selected seats), and transactionId (a unique identifier for the transaction).
* The schema is exported as a Mongoose model named "bookings."
In bookingRoute.js file make the below changes as follows:
```javascript
//book shows
router.post("/book-show", authMiddleware, async (req, res) => {
try {
// save booking
const newBooking = new Booking(req.body);
await newBooking.save();
const show = await Show.findById(req.body.show);
// update seats
await Show.findByIdAndUpdate(req.body.show, {
bookedSeats: [...show.bookedSeats, ...req.body.seats],
});
res.send({
success: true,
message: "Show booked successfully",
data: newBooking,
});
} catch (error) {
res.send({
success: false,
message: error.message,
});
}
});
router.get("/get-bookings", authMiddleware, async (req, res) => {
try {
const bookings = await Booking.find({ user: req.body.userId })
.populate("user")
.populate("show")
.populate({
path: "show",
populate: {
path: "movie",
model: "movies",
},
})
.populate({
path: "show",
populate: {
path: "theatre",
model: "theatres",
},
});
res.send({
success: true,
message: "Bookings fetched successfully",
data: bookings,
});
} catch (error) {
res.send({
success: false,
message: error.message,
});
}
});
```
* A new route handler "`/book-show`" is defined, which expects a POST request to book a show.
* It creates a new booking record using the provided data and saves it to the database.
* It also fetches the existing show data to update the list of booked seats.
* The response includes a success message and the newly created booking data or an error message in case of failure.
* Another route handler "`/get-bookings`" is defined, which expects a GET request to fetch all bookings of a user.
* It retrieves user-specific booking records, populates relevant data (user, show, movie, and theatre), and sends them in the response.
* The response includes a success message and the booking data or an error message in case of failure.
In bookings.js file make the below changes as follows:
```javascript
// book shows
export const BookShowTickets = async (payload) => {
try {
const response = await axiosInstance.post(
"/api/bookings/book-show",
payload
);
return response.data;
} catch (error) {
return error.response.data;
}
};
export const GetBookingsOfUser = async () => {
try {
const response = await axiosInstance.get("/api/bookings/get-bookings");
return response.data;
} catch (error) {
return error.response.data;
}
};
```
* Two functions are defined to make API calls related to bookings.
* BookShowTickets sends a POST request to the "`/book-show`" endpoint to book show tickets, including the show ID, selected seats, transaction ID, and user ID.
* `GetBookingsOfUser` sends a GET request to the "`/get-bookings`" endpoint to fetch the bookings of the currently authenticated user.
In bookingShow.js file make the below changes as follows:
```javascript
const book = async (transactionId) => {
try {
dispatch(ShowLoading());
const response = await BookShowTickets({
show: params.id,
seats: selectedSeats,
transactionId,
user: user._id,
});
if (response.success) {
message.success(response.message);
navigate("/profile");
} else {
message.error(response.message);
}
dispatch(HideLoading());
} catch (error) {
message.error(error.message);
dispatch(HideLoading());
}
};
```
* The book function is defined, which is responsible for handling the booking of show tickets.
* It makes an API call using BookShowTickets, passing relevant data such as the **show ID, selected seats, transaction ID, and user ID**.
* Depending on the response, it displays success or error messages and potentially navigates to the user's profile page.
* Loading indicators are shown while the booking process is in progress.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/477/original/upload_56c81be1c50ede02d12ccb1deb18708e.png?1696009093)

View File

@@ -0,0 +1,639 @@
## Agenda
**Topics to cover in Node:**
* Exploring different node inbuilt modules
* Understanding server side development with node (http)
* Understanding http module with req and res
* Serving static html sites & json files via the server
We will try to cover most of these topics in today's sessions and the remaining in the next.
It is going to be a bit challenging, advanced, but very interesting session covering topics that are asked very frequently in interviews.
So let's start.
## Nodejs
Node.js is an open-source, server-side JavaScript runtime environment that allows you to run JavaScript code on the server. It was created by Ryan Dahl and first released in 2009. Node.js is built on the V8 JavaScript engine, which is also used in Google Chrome, and it provides an event-driven, non-blocking I/O model that makes it well-suited for building scalable and high-performance network applications.
## Why use nodejs for web server
Node.js is a popular choice for web servers, especially in scenarios involving heavy I/O operations and small server requirements. Here's why Node.js is a suitable option for such use cases:
1. **Non-Blocking I/O Model:** Node.js is designed around a non-blocking, event-driven architecture. This means it can efficiently handle multiple I/O operations concurrently without blocking the execution of other tasks. When performing heavy I/O operations, such as reading and writing files, making network requests, or interacting with databases, Node.js can initiate these operations and continue executing other code while waiting for the I/O operations to complete. This asynchronous approach is highly advantageous for scenarios with many concurrent I/O tasks.
2. **Scalability:** In situations involving heavy I/O, it's common for multiple clients to make simultaneous requests to the server. Node.js's non-blocking model allows it to handle a large number of concurrent connections efficiently, making it a suitable choice for scalable applications. It can process incoming requests as soon as they arrive, rather than waiting for each request to complete before moving on to the next one.
3. **Low Overhead:** Node.js has a relatively small memory footprint compared to some other web server technologies. This makes it well-suited for small server applications where resource utilization needs to be efficient. You can run multiple Node.js instances on a single server without consuming excessive system resources.
5. **Rich Ecosystem:** Node.js has a vast ecosystem of libraries and modules available through npm, which can simplify the development of web servers for various purposes. Developers can find packages to handle specific I/O tasks, such as file uploads, database connections, and HTTP requests, making it easier to build web servers tailored to their needs.
## How are we going to learn nodejs development
Learning Node.js development and building a full-stack MERN (MongoDB, Express.js, React, Node.js) application involves multiple steps and concepts. Here's a roadmap to guide you through the process:
1. **Basic JavaScript Knowledge**:
Before diving into Node.js, ensure you have a strong foundation in JavaScript, as Node.js is JavaScript on the server-side.
2. **Node.js Fundamentals**:
Start by learning the basics of Node.js, including installation, core modules, and working with the Node.js runtime. You can find introductory tutorials and courses online.
3. **Module and Package Management (npm)**:
Understand how to use npm (Node Package Manager) to manage dependencies and create projects. Learn how to initialize a project, install packages, and create a package.json file.
4. **Server Concepts with Node.js**:
Explore the core concepts of building a server with Node.js. This includes creating an HTTP server, handling requests, and sending responses.
5. **Web Server with Express.js**:
Dive into Express.js, a popular Node.js framework for building web applications and APIs. Learn how to set up routes, handle HTTP requests, and use middleware for tasks like authentication and error handling.
6. **RESTful API with Express.js**:
Extend your Express.js knowledge to create RESTful APIs. Understand HTTP methods (GET, POST, PUT, DELETE), request and response handling, and best practices for building APIs.
7. **MVC Architecture**:
Explore the Model-View-Controller (MVC) architectural pattern and how it applies to building web applications with Node.js and Express.js. Organize your code into models, views, and controllers for better maintainability.
8. **MongoDB**:
Learn about MongoDB, a NoSQL database commonly used with Node.js. Understand how to install MongoDB, perform CRUD operations, and work with collections and documents.
9. **Mongoose**:
Integrate Mongoose, an ODM (Object-Document Mapping) library, with your Node.js and Express.js application to simplify database operations and schema management.
10. **JWT Authentication**:
Implement JSON Web Token (JWT) authentication to secure your APIs. Learn how to generate tokens, validate them, and protect routes using JWT.
11. **React Integration**:
If you want to build a MERN application, learn React for front-end development. Understand React components, state management, and how to create a user interface.
12. **Integration of MERN App**:
Combine your knowledge of Node.js, Express.js, MongoDB, and React to build a full-stack MERN application. Create RESTful APIs on the server and connect them to your React front-end.
13. **Miscellaneous**:
Explore other concepts like error handling, testing (using tools like Jest), deployment (using platforms like Heroku or AWS), and performance optimization.
14. **Practice and Build Projects**:
The best way to solidify your skills is by working on projects. Start with simple projects and gradually move on to more complex ones to gain practical experience.
## Fs module in depth
The `fs` module in Node.js stands for "File System," and it provides a way to work with the file system on your computer or server. It allows you to read from and write to files, manipulate directories, perform file operations, and more. Let's explore some of the key functionalities of the `fs` module in-depth:
**1. Reading Files:**
The `fs` module provides methods for reading the contents of files. The most commonly used method for this purpose is `fs.readFile()`:
```javascript
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error(err);
return;
}
console.log(data);
});
```
In this example, `readFile()` reads the content of 'example.txt' and then calls the provided callback function with any errors encountered and the file's contents.
**2. Writing Files:**
You can also use the `fs` module to write data to files using methods like `fs.writeFile()`:
```javascript
const fs = require('fs');
const content = 'Hello, world!';
fs.writeFile('example.txt', content, 'utf8', (err) => {
if (err) {
console.error(err);
return;
}
console.log('File has been written.');
});
```
Here, `writeFile()` creates or overwrites 'example.txt' with the provided content.
**3. Synchronous vs. Asynchronous Operations:**
Most `fs` module functions come in both synchronous and asynchronous versions. The asynchronous versions (e.g., `fs.readFile()`) allow non-blocking file operations, while synchronous versions (e.g., `fs.readFileSync()`) block the Node.js event loop until the operation is complete.
Asynchronous methods are typically preferred in Node.js to maintain the application's responsiveness.
**4. Working with Directories:**
You can perform operations on directories using methods like `fs.mkdir()`, `fs.rmdir()`, `fs.readdir()`, and `fs.stat()`. These methods allow you to create, remove, list, and get information about directories, respectively.
**5. Renaming and Deleting Files:**
`fs.rename()` can be used to rename files, and `fs.unlink()` to delete them:
```javascript
fs.rename('old-file.txt', 'new-file.txt', (err) => {
if (err) {
console.error(err);
return;
}
console.log('File has been renamed.');
});
fs.unlink('file-to-delete.txt', (err) => {
if (err) {
console.error(err);
return;
}
console.log('File has been deleted.');
});
```
**6. File Statistics:**
The `fs.stat()` method provides information about a file's status, including its size, permissions, and modification timestamp.
```javascript
fs.stat('example.txt', (err, stats) => {
if (err) {
console.error(err);
return;
}
console.log('File size: ' + stats.size);
console.log('Is directory? ' + stats.isDirectory());
});
```
**7. Watching for Changes:**
Node.js also allows you to watch for changes to files and directories using `fs.watch()` and `fs.watchFile()` methods, which can be useful for real-time monitoring of file system changes.
In Node.js, you can use the `fs` module to create and delete directories. Here's how you can do it:
**Creating a Directory:**
To create a directory, you can use the `fs.mkdir()` method. Here's an example:
```javascript
const fs = require('fs');
const directoryName = 'my-directory';
fs.mkdir(directoryName, (err) => {
if (err) {
console.error(`Error creating directory: ${err}`);
} else {
console.log(`Directory "${directoryName}" created successfully.`);
}
});
```
In this code, `fs.mkdir()` is used to create a directory named "my-directory." The callback function is called when the directory creation is complete. If an error occurs, it will be logged.
**Deleting a Directory:**
To delete a directory, you can use the `fs.rmdir()` method. Here's an example:
```javascript
const fs = require('fs');
const directoryName = 'my-directory';
fs.rmdir(directoryName, { recursive: true }, (err) => {
if (err) {
console.error(`Error deleting directory: ${err}`);
} else {
console.log(`Directory "${directoryName}" deleted successfully.`);
}
});
```
In this code, `fs.rmdir()` is used to delete the "my-directory" directory. The `{ recursive: true }` option is provided to ensure that the directory and its contents are deleted recursively. The callback function is called when the deletion is complete, and any errors are logged.
Make sure to handle errors appropriately when creating or deleting directories in your Node.js applications to ensure that your code is robust and reliable.
You can check whether a directory or file exists in Node.js using the `fs` module. Here's how you can do it:
**Checking if a Directory Exists:**
To check if a directory exists, you can use the `fs.existsSync()` method. Here's an example:
```javascript
const fs = require('fs');
const directoryPath = '/path/to/your/directory';
if (fs.existsSync(directoryPath)) {
console.log(`The directory "${directoryPath}" exists.`);
} else {
console.log(`The directory "${directoryPath}" does not exist.`);
}
```
Replace `/path/to/your/directory` with the actual path to the directory you want to check. `fs.existsSync()` returns `true` if the directory exists and `false` if it doesn't.
**Checking if a File Exists:**
To check if a file exists, you can use the `fs.existsSync()` method as well. Here's an example:
```javascript
const fs = require('fs');
const filePath = '/path/to/your/file.txt';
if (fs.existsSync(filePath)) {
console.log(`The file "${filePath}" exists.`);
} else {
console.log(`The file "${filePath}" does not exist.`);
}
```
Replace `/path/to/your/file.txt` with the actual path to the file you want to check. `fs.existsSync()` returns `true` if the file exists and `false` if it doesn't.
It's worth noting that `fs.existsSync()` is a synchronous method, so it can block the Node.js event loop. If you prefer an asynchronous approach, you can use the `fs.access()` method. Here's an example:
```javascript
const fs = require('fs');
const pathToCheck = '/path/to/your/directory-or-file';
fs.access(pathToCheck, fs.constants.F_OK, (err) => {
if (err) {
console.log(`The path "${pathToCheck}" does not exist.`);
} else {
console.log(`The path "${pathToCheck}" exists.`);
}
});
```
Replace `/path/to/your/directory-or-file` with the actual path you want to check. The `fs.access()` method asynchronously checks if the path exists and whether it's accessible (in this case, using the `fs.constants.F_OK` flag to check for existence).
Both approaches can be used to determine whether a directory or file exists in your Node.js application, depending on your preference for synchronous or asynchronous code.
## Path Module
The `path` module in Node.js provides utilities for working with file and directory paths. It's an essential module when dealing with file system operations and path manipulation in your Node.js applications. Here are some important functions and concepts from the `path` module:
1. **`path.join([...paths])`**: This method joins multiple path segments into a single path string, taking care of platform-specific path separators (e.g., backslashes on Windows and forward slashes on Unix-like systems).
```javascript
const path = require('path');
const fullPath = path.join('folder', 'subfolder', 'file.txt');
```
2. **`path.resolve([...paths])`**: Resolves an absolute path from multiple path segments, starting from the root directory. It can be helpful for creating absolute paths based on relative ones.
```javascript
const path = require('path');
const absolutePath = path.resolve('folder', 'subfolder', 'file.txt');
```
3. **`path.basename(path[, ext])`**: Returns the base filename of a path, optionally removing a file extension if provided.
```javascript
const path = require('path');
const fileName = path.basename('/path/to/file.txt');
```
4. **`path.dirname(path)`**: Returns the directory name of a path.
```javascript
const path = require('path');
const dirName = path.dirname('/path/to/file.txt');
```
5. **`path.extname(path)`**: Returns the file extension of a path, including the dot.
```javascript
const path = require('path');
const extension = path.extname('/path/to/file.txt');
```
6. **`path.parse(pathString)`**: Parses a path string into an object with properties like `root`, `dir`, `base`, `name`, and `ext`.
```javascript
const path = require('path');
const pathInfo = path.parse('/path/to/file.txt');
```
7. **`path.normalize(path)`**: Normalizes a path by resolving '..' and '.' segments and converting slashes to the appropriate platform format.
```javascript
const path = require('path');
const normalizedPath = path.normalize('/path/to/../file.txt');
```
8. **`path.isAbsolute(path)`**: Checks if a path is an absolute path.
```javascript
const path = require('path');
const isAbsolute = path.isAbsolute('/path/to/file.txt');
```
9. **`path.relative(from, to)`**: Returns the relative path from one path to another.
```javascript
const path = require('path');
const relativePath = path.relative('/path/from', '/path/to');
```
The `path` module is particularly useful when working on cross-platform applications or when dealing with file and directory paths dynamically in your Node.js code. It ensures that your path manipulation is consistent and compatible with various operating systems.
### Copy a file from one folder to another in Node.js
To copy a file from one folder to another in Node.js, you can use the `fs` module. Here's how you can do it:
```javascript
const fs = require('fs');
const path = require('path');
// Define the source and destination file paths
const sourceFilePath = '/path/to/source-folder/source-file.txt';
const destinationFilePath = '/path/to/destination-folder/destination-file.txt';
// Create a readable stream from the source file
const readStream = fs.createReadStream(sourceFilePath);
// Create a writable stream to the destination file
const writeStream = fs.createWriteStream(destinationFilePath);
// Pipe the data from the source file to the destination file
readStream.pipe(writeStream);
// Handle any errors that may occur during the copy process
readStream.on('error', (err) => {
console.error(`Error reading the source file: ${err}`);
});
writeStream.on('error', (err) => {
console.error(`Error writing to the destination file: ${err}`);
});
// When the copy is complete, log a success message
writeStream.on('finish', () => {
console.log('File copied successfully.');
});
```
In this code:
1. Replace `/path/to/source-folder/source-file.txt` with the actual path to the source file you want to copy.
2. Replace `/path/to/destination-folder/destination-file.txt` with the desired path and name for the destination file.
Here's an explanation of what the code does:
- It uses the `fs.createReadStream()` method to create a readable stream from the source file.
- It uses the `fs.createWriteStream()` method to create a writable stream to the destination file.
- It uses the `.pipe()` method to pipe the data from the source stream to the destination stream, effectively copying the file.
- It sets up error event listeners on both the source and destination streams to handle any errors that may occur during the copy process.
- It sets up a finish event listener on the destination stream to log a success message when the copy is complete.
This code will copy the contents of the source file to the destination file. If the destination file already exists, it will be overwritten. Make sure to handle errors and adjust file paths as needed for your specific use case.
---
title: Server side development
description: Exploring Server side Development with http module
duration: 2100
card_type: cue_card
---
## Server side development
Server-side development, client-side development, and working with database clients are essential components of modern web application development. Let's explore these concepts:
**1. Server-Side Development:**
Server-side development refers to the part of web application development that occurs on the server, typically using server-side technologies and programming languages. Here are key aspects:
- **Server:** A server is a computer or software application that responds to client requests over a network. In web development, a server typically hosts the backend of a web application.
- **Server-Side Technologies:** Common server-side technologies include Node.js, Python (with frameworks like Django or Flask), Ruby (with Ruby on Rails), Java (with Spring or Java EE), PHP, and more. These technologies enable you to create the server logic, handle requests from clients, interact with databases, and generate dynamic content.
- **Server Logic:** Server-side code manages user authentication, business logic, data processing, and database interactions. It often generates HTML, JSON, or other data to send back to the client.
- **Security:** Security measures like input validation, authentication, authorization, and protecting against common vulnerabilities (e.g., SQL injection, XSS) are typically implemented on the server side.
**2. Client-Side Development:**
Client-side development focuses on the part of web application development that occurs in the user's web browser. Here are key aspects:
- **Client:** The client is the user's device (e.g., web browser) that sends requests to a server to access web content or services.
- **Client-Side Technologies:** Common client-side technologies include HTML, CSS, and JavaScript. HTML is used for structuring web content, CSS for styling, and JavaScript for adding interactivity and functionality to web pages.
- **Front-End Frameworks:** Developers often use front-end frameworks and libraries like React, Angular, or Vue.js to build complex and responsive user interfaces.
- **User Experience:** Client-side development is responsible for creating an engaging and user-friendly experience. This includes handling user interactions, form validations, and rendering dynamic content without requiring full page reloads.
- **Performance:** Optimizing client-side performance is crucial, as the client device has limited resources. Techniques like lazy loading, minification, and caching are employed to enhance the user experience.
**3. Database Client:**
A database client is a software component or library that allows your server-side code to communicate with a database management system (DBMS). Here are key aspects:
- **Database Management System (DBMS):** A DBMS is software that manages databases, including storing, retrieving, updating, and organizing data. Examples of DBMSs include MySQL, PostgreSQL, MongoDB, and SQLite.
- **Database Client Libraries:** To interact with a DBMS, developers use specific client libraries or drivers for their chosen programming language. These libraries provide functions and methods to connect to the database, execute queries, and retrieve results.
- **ORM (Object-Relational Mapping):** Some server-side frameworks and languages offer ORMs (e.g., Sequelize for Node.js, Hibernate for Java) that provide a higher-level abstraction for working with databases. ORMs map database tables to objects in code, simplifying database interactions.
- **Data Access:** Server-side code uses database clients to perform CRUD operations (Create, Read, Update, Delete) on data stored in databases. This includes querying data, inserting new records, updating existing records, and deleting records.
## Server-side development using the `http` module
Server-side development using the `http` module in Node.js allows you to create a basic HTTP server to handle incoming requests and send responses. Here's a step-by-step guide to building a simple HTTP server using the `http` module:
1. **Import the `http` Module:**
Start by requiring the `http` module in your Node.js script:
```javascript
const http = require('http');
```
2. **Create the HTTP Server:**
You can create an HTTP server using the `http.createServer()` method. This method takes a callback function that will be invoked for each incoming HTTP request.
```javascript
const server = http.createServer((req, res) => {
// Handle incoming requests here
});
```
3. **Handle Incoming Requests:**
Inside the callback function, you can handle incoming HTTP requests. The `req` object represents the request, and the `res` object is used to send the response back to the client.
```javascript
const server = http.createServer((req, res) => {
// Set response header
res.setHeader('Content-Type', 'text/plain');
// Write response content
res.write('Hello, World!');
// End the response
res.end();
});
```
In this example, we set the `Content-Type` header to `'text/plain'`, write "Hello, World!" as the response content, and then end the response.
4. **Specify the Listening Port and Host:**
You need to specify the port and host (usually `'localhost'` for development) on which your server will listen for incoming requests:
```javascript
const port = 3000;
const host = 'localhost';
server.listen(port, host, () => {
console.log(`Server is listening on http://${host}:${port}`);
});
```
5. **Start the Server:**
Finally, you can start the server by calling the `server.listen()` method. This will start listening for incoming HTTP requests on the specified port and host.
6. **Test Your Server:**
Run your Node.js script, and your server will be accessible at the specified URL (e.g., http://localhost:3000). You can use a web browser or tools like cURL or Postman to send HTTP requests to your server.
Here's the complete code for a basic HTTP server:
```javascript
const http = require('http');
const server = http.createServer((req, res) => {
// Set response header
res.setHeader('Content-Type', 'text/plain');
// Write response content
res.write('Hello, World!');
// End the response
res.end();
});
const port = 3000;
const host = 'localhost';
server.listen(port, host, () => {
console.log(`Server is listening on http://${host}:${port}`);
});
```
This simple HTTP server will respond with "Hello, World!" to any incoming request. You can expand upon this foundation to handle different types of requests and serve dynamic content based on the requested URL or route.
You can modify the code to send an HTML response containing an `<h1>` tag. Here's an example of a Node.js HTTP server that responds with an HTML `<h1>` tag:
```javascript
const http = require('http');
const server = http.createServer((req, res) => {
// Set response header with Content-Type as text/html
res.setHeader('Content-Type', 'text/html');
// Write HTML response
res.write('<html><head><title>Node.js HTTP Server</title></head><body>');
res.write('<h1>Hello, World!</h1>');
res.write('</body></html>');
// End the response
res.end();
});
const port = 3000;
const host = 'localhost';
server.listen(port, host, () => {
console.log(`Server is listening on http://${host}:${port}`);
});
```
In this code:
- We set the `Content-Type` header to `'text/html'` to indicate that the response will contain HTML content.
- We use `res.write()` to send the HTML content, which includes an `<h1>` tag with the text "Hello, World!".
- The response is ended with `res.end()`.
Now, when you access the server in your web browser, you will receive an HTML response with the specified `<h1>` tag. This demonstrates how you can send HTML content as a response using the Node.js `http` module.
### Nodemon
Nodemon is a tool that helps you develop Node.js applications by automatically restarting the Node.js process whenever changes are detected in your project's files. It's particularly useful during development because it eliminates the need to manually stop and restart your Node.js application every time you make code changes.
Here's how to use Nodemon:
1. **Installation**:
You can install Nodemon globally using npm (Node Package Manager) with the following command:
```bash
npm install -g nodemon
```
Alternatively, you can install it as a development dependency in your project by running the following command inside your project directory:
```bash
npm install --save-dev nodemon
```
2. **Basic Usage**:
After installing Nodemon, you can use it to run your Node.js application instead of the standard `node` command. For example:
```bash
nodemon your-app.js
```
Replace `your-app.js` with the entry point file of your Node.js application.
3. **Automatic Restart**:
Nodemon will watch for changes in the project directory and its subdirectories. Whenever you save changes to your code, Nodemon will automatically restart your Node.js application with the updated code. This eliminates the need to manually stop and restart the application.
4. **Nodemon Configuration**:
Nodemon allows you to customize its behavior by using a `nodemon.json` configuration file or specifying configuration options in your project's `package.json` file. You can configure things like which files to watch, ignore specific files or directories, and more.
Example `nodemon.json`:
```json
{
"watch": ["src"],
"ignore": ["node_modules"]
}
```
Example `package.json` (under the `"nodemon"` key):
```json
"nodemon": {
"watch": ["src"],
"ignore": ["node_modules"]
}
```
5. **Additional Features**:
Nodemon offers additional features such as running scripts, passing environment variables, and more. You can explore these options in the Nodemon documentation and tailor them to your specific needs.
Nodemon is a valuable tool for improving the development workflow in Node.js projects, as it simplifies the process of testing and iterating on your code. It's commonly used in combination with development frameworks like Express.js to streamline web application development.
### Json in the response and setting the header
You can modify the code to send a JSON response from your Node.js HTTP server. Here's an example of a Node.js HTTP server that responds with JSON data:
```javascript
const http = require('http');
const server = http.createServer((req, res) => {
// Set response header with Content-Type as application/json
res.setHeader('Content-Type', 'application/json');
// Define JSON data
const jsonData = {
message: 'Hello, World!',
date: new Date(),
};
// Convert JSON object to a JSON string
const jsonResponse = JSON.stringify(jsonData);
// Write JSON response
res.write(jsonResponse);
// End the response
res.end();
});
const port = 3000;
const host = 'localhost';
server.listen(port, host, () => {
console.log(`Server is listening on http://${host}:${port}`);
});
```
In this code:
- We set the `Content-Type` header to `'application/json'` to indicate that the response will contain JSON data.
- We define a JSON object called `jsonData`, which contains a message and the current date.
- We use `JSON.stringify()` to convert the JSON object into a JSON string.
- The response is ended with `res.end()`.
Now, when you access the server in your web browser or send an HTTP request to it, you will receive a JSON response containing the specified data. This demonstrates how you can send JSON data as a response using the Node.js `http` module.

View File

@@ -0,0 +1,467 @@
## Agenda
**Topics to cover in Express:**
We will try to cover most of these topics in today's sessions and the remaining in the next.
* What is express
* How to use express with node
* Http methods-get,post,put,delete, patch
* Postman-to test your api endpoints
* Middlewares
It is going to be a bit challenging, advanced, but very interesting session covering topics that are asked very frequently in interviews.
So let's start.
## Express Module
In Node.js, the Express module is a popular framework for building web applications and APIs. It simplifies the process of handling HTTP requests and responses and provides a structured way to create routes and middleware. Let's explore the Express module with some examples.
**Example 1: Setting Up an Express Application**
First, you need to install Express in your project:
```bash
npm install express --save
```
Now, let's create a simple Express application:
```javascript
// Import the Express module
const express = require('express');
// Create an Express application
const app = express();
// Define a route
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
// Start the server
const port = 3000;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
```
In this example:
- We import the `express` module and create an Express application instance.
- We define a route for the root URL ("/") using `app.get()`, which responds with "Hello, Express!" when accessed via a GET request.
- We start the server on port 3000.
**Example 2: Express Routes**
Express allows you to define multiple routes for different URL paths and HTTP methods. Here's an example:
```javascript
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
app.get('/about', (req, res) => {
res.send('This is the about page.');
});
app.post('/data', (req, res) => {
res.send('Received a POST request.');
});
```
In this example:
- We define a route for the root URL ("/") and an "/about" page that responds to GET requests.
- We also define a route for "/data" that responds to POST requests.
---
### Postman
Postman is a popular and powerful tool used by developers and testers to simplify the process of testing APIs (Application Programming Interfaces). It provides an intuitive graphical user interface (GUI) that allows you to send HTTP requests to your API endpoints, inspect responses, and automate API testing. Here are some key features and uses of Postman:
1. **Creating a Request**:
- Open Postman and click the "New" button to create a new request.
- Choose the HTTP method (e.g., GET, POST) for your request.
2. **Request URL**:
- Enter the URL of the API endpoint you want to test in the request URL field.
3. **Headers**:
- You can set request headers by clicking on the "Headers" tab.
- Headers are used to pass additional information to the server, such as authentication tokens or content type.
4. **Request Body**:
- If your request requires a request body (e.g., for POST or PUT requests), you can define it in the "Body" tab.
- You can send data in various formats like JSON, form-data, x-www-form-urlencoded, etc.
5. **Sending a Request**:
- Click the "Send" button to send the request to the specified endpoint.
- Postman will display the response in the lower part of the window.
6. **Response**:
- You can view the response status code, headers, and body in the response section.
- You can also format and highlight the response body using the options provided.
7. **Collections**:
- Organize your requests into collections for better management.
- Create a new collection by clicking the "New" button under "Collections."
8. **Variables**:
- Use environment and global variables to store values that can be reused across requests.
- Variables are helpful for managing different environments (e.g., development, production) or dynamic data.
9. **Tests and Assertions**:
- Write test scripts to validate the response data.
- You can use JavaScript to write custom tests.
- Use the "Tests" tab in the request to add test scripts.
These are some of the basic concepts and features of Postman. It's a versatile tool with many capabilities that can greatly simplify the process of working with APIs, testing, and collaborating with your team. As you become more familiar with Postman, you can explore its advanced features and customization options to suit your specific needs.
Here are simple examples of how to implement POST and DELETE requests in an Express.js application.
**POST Request Example**:
In this example, we'll create a basic Express application that handles a POST request to add a new user to a list of users.
```javascript
const express = require('express');
const app = express();
const port = 3000;
// Middleware to parse JSON request bodies
app.use(express.json());
// Sample user data (in-memory storage)
let users = [
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' }
];
// POST endpoint to add a new user
app.post('/users', (req, res) => {
const newUser = req.body;
// Assign a unique ID to the new user (in a real app, you'd typically use a database)
const userId = users.length + 1;
newUser.id = userId;
// Add the new user to the list
users.push(newUser);
res.status(201).json({ message: 'User created', user: newUser });
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
```
To test this, send a POST request to `http://localhost:3000/users` with a JSON body, for example:
```json
{
"name": "New User"
}
```
The server will respond with a JSON message confirming that the user has been created.
**DELETE Request Example**:
In this example, we'll create an Express application that handles a DELETE request to remove a user from the list.
```javascript
const express = require('express');
const app = express();
const port = 3000;
// Sample user data (in-memory storage)
let users = [
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' }
];
// DELETE endpoint to delete a user by ID
app.delete('/users/:id', (req, res) => {
const userId = parseInt(req.params.id);
// Find the user index by ID
const userIndex = users.findIndex(user => user.id === userId);
if (userIndex === -1) {
return res.status(404).json({ message: 'User not found' });
}
// Remove the user from the array
users.splice(userIndex, 1);
res.json({ message: 'User deleted' });
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
```
To test this, send a DELETE request to `http://localhost:3000/users/1` to delete the user with ID 1.
These examples demonstrate how to implement POST and DELETE requests in an Express.js application. They focus on the core functionality without extensive error handling or database interactions. In practice, you would typically include more robust validation and potentially use a database for data storage.
## Middleware
Middleware functions in Express.js are functions that have access to the request (`req`) and response (`res`) objects and can perform actions or transformations on them. They are used to handle tasks like parsing request data, authentication, logging, error handling, and more. Middleware functions can be added to your Express application using `app.use()` or applied to specific routes using `app.use()` or `app.METHOD()` (e.g., `app.get()`, `app.post()`).
Here's a simple example of how to create and use middleware in an Express.js application:
```javascript
const express = require('express');
const app = express();
const port = 3000;
// Custom middleware function
const loggerMiddleware = (req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // Call the next middleware in the chain
};
// Register the middleware globally for all routes
app.use(loggerMiddleware);
// Route handler
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
```
In this example:
1. We define a custom middleware function called `loggerMiddleware`. It logs the request method and URL along with a timestamp.
2. We use `app.use()` to register the `loggerMiddleware`, which means it will be executed for all incoming requests.
3. We have a simple route handler for the root path ("/") that sends a "Hello, Express!" response.
When you run this Express application, every incoming request will trigger the `loggerMiddleware` to log information about the request. This is just one example of middleware; you can create and use middleware functions for various purposes, including authentication, request validation, error handling, and more.
Middleware functions are executed in the order they are registered with `app.use()`, so the order of middleware registration matters. You can also apply middleware to specific routes by using `app.use()` or `app.METHOD()` for those routes.
For example, if you wanted to apply `loggerMiddleware` only to a specific route, you could do the following:
```javascript
app.get('/special', loggerMiddleware, (req, res) => {
res.send('This route is special!');
});
```
In this case, the `loggerMiddleware` will only run for requests to the "/special" route.
Remember that middleware functions can perform various tasks, and you can create custom middleware to suit your application's needs.
### Authentication middleware
Authentication middleware in an Express.js application is used to check if a user is authenticated before allowing access to certain routes or resources. Below is a simple example of how to create an authentication middleware to protect routes and ensure users are authenticated before accessing them. We'll use a basic username and password authentication mechanism for demonstration purposes.
Here's an example where we use `app.use` to protect all routes with the authentication middleware:
```javascript
const express = require('express');
const app = express();
const port = 3000;
// Sample user data (in-memory storage)
const users = [
{ id: 1, username: 'user1', password: 'password1' },
{ id: 2, username: 'user2', password: 'password2' },
];
// Middleware for basic authentication
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: 'Unauthorized' });
}
const [authType, authCredentials] = authHeader.split(' ');
if (authType !== 'Basic') {
return res.status(401).json({ message: 'Unauthorized' });
}
const credentials = Buffer.from(authCredentials, 'base64').toString('utf-8');
const [username, password] = credentials.split(':');
const user = users.find((u) => u.username === username && u.password === password);
if (!user) {
return res.status(401).json({ message: 'Unauthorized' });
}
req.user = user; // Attach the user object to the request for later use
next();
};
// Apply the authentication middleware globally for all routes
app.use(authenticate);
// Protected route
app.get('/protected', (req, res) => {
res.json({ message: 'You have access to the protected resource!', user: req.user });
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
```
In this updated example:
- We use `app.use(authenticate)` to apply the `authenticate` middleware globally for all routes. This means that every route defined after `app.use(authenticate)` will require authentication.
- The "/protected" route does not have the `authenticate` middleware applied directly to it. Instead, it inherits the authentication requirement from the global middleware.
Now, all routes are protected by the authentication middleware, and you don't need to manually apply the middleware to each individual route.
### Route-level middleware
Route-level middleware in Express.js allows you to apply middleware functions to specific routes rather than applying them globally to all routes. This gives you more fine-grained control over which routes have specific middleware functions applied to them. Here's an example of how to use route-level middleware:
```javascript
const express = require('express');
const app = express();
const port = 3000;
// Sample user data (in-memory storage)
const users = [
{ id: 1, username: 'user1', password: 'password1' },
{ id: 2, username: 'user2', password: 'password2' },
];
// Middleware for basic authentication
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ message: 'Unauthorized' });
}
const [authType, authCredentials] = authHeader.split(' ');
if (authType !== 'Basic') {
return res.status(401).json({ message: 'Unauthorized' });
}
const credentials = Buffer.from(authCredentials, 'base64').toString('utf-8');
const [username, password] = credentials.split(':');
const user = users.find((u) => u.username === username && u.password === password);
if (!user) {
return res.status(401).json({ message: 'Unauthorized' });
}
req.user = user; // Attach the user object to the request for later use
next();
};
// Apply the authentication middleware to a specific route
app.get('/protected', authenticate, (req, res) => {
res.json({ message: 'You have access to the protected resource!', user: req.user });
});
// Another route without authentication middleware
app.get('/public', (req, res) => {
res.json({ message: 'This is a public resource.' });
});
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
```
In this example:
- We define the `authenticate` middleware as before.
- We apply the `authenticate` middleware to the "/protected" route by including it as a second argument after the route path when defining the route.
- The "/public" route does not have the `authenticate` middleware applied, so it does not require authentication.
With this setup, the "/protected" route requires authentication because the `authenticate` middleware is applied to it. The "/public" route, on the other hand, does not require authentication and is accessible without any additional middleware. Route-level middleware allows you to customize the middleware applied to each route in your Express.js application.
### Built-in Middleware
Sure, let me explain these three commonly used built-in middleware functions in Express:
1. **express.static(root, [options])**:
- `express.static` is a middleware function that serves static files such as HTML, CSS, JavaScript, images, and more.
- It is often used to serve client-side assets, making it easy to host static files like HTML, CSS, and JavaScript for your web application.
- The `root` parameter specifies the root directory from which to serve static files.
- The optional `options` object allows you to configure various settings, such as caching and file handling.
Example:
```javascript
const express = require('express');
const app = express();
// Serve static files from the 'public' directory
app.use(express.static('public'));
```
In this example, if you have an "index.html" file in the "public" directory, you can access it in your browser by navigating to `http://localhost:3000/index.html`.
#### Static Hosting
Static hosting, also known as static web hosting or static file hosting, refers to the process of serving static files, such as HTML, CSS, JavaScript, images, and other assets, over the internet using a web server. Static files are files that do not change dynamically based on user interactions or database queries.
Static hosting is commonly used for websites, single-page applications (SPAs), documentation sites, and other web-based content that does not require server-side processing.
2. **express.json([options])**:
- `express.json` is a middleware function that parses incoming JSON requests and makes the parsed data available in the `req.body` property.
3. **express.urlencoded([options])**:
- `express.urlencoded` is a middleware function that parses incoming URL-encoded data from forms and makes it available in the `req.body` property.
These built-in middlewares in Express simplify common tasks like serving static files and parsing request data, making it easier to handle different types of requests in your web application or API.
### Route Parameters and Query Parameters
In web development, route parameters and query parameters are mechanisms for passing data to a web server or an application, typically through a URL. They are commonly used to customize and control the behavior of a web application by providing information about the requested resource or specifying additional options.
**Route Parameters**:
Route parameters are part of the URL's path and are used to define variable parts of a route. They are typically denoted by placeholders in the route pattern, surrounded by colons (`:`). When a client makes a request with a URL that matches the route pattern, the values specified in the URL are extracted and made available to the server or application.
For example, in a RESTful API, you might have a route for retrieving a specific user's profile:
```
GET /users/:userId
```
In this URL, `:userId` is a route parameter. When a request is made to `/users/123`, the server can extract the value `123` from the URL and use it to retrieve the user with that ID.
**Query Parameters**:
Query parameters are part of the URL's query string and are used to provide additional information or data to the server. They are typically specified after the `?` character in the URL and are in the form of key-value pairs.
For example, in a search feature, you might have a URL that includes query parameters to filter results:
```
GET /search?q=keyword&page=2&sort=desc
```
In this URL, `q`, `page`, and `sort` are query parameters. They allow the server to understand the search query, the desired page, and the sorting order.
In summary, route parameters and query parameters are essential for building dynamic web applications and APIs. They allow you to customize the behavior of your routes and pass data between clients and servers effectively. Route parameters are part of the URL's path and are extracted using placeholders in the route pattern, while query parameters are part of the URL's query string and are provided as key-value pairs after the `?` character. Express.js simplifies the handling of both types of parameters in your server-side code.

View File

@@ -0,0 +1,284 @@
### Introduction
Representational State Transfer (REST) constitutes an architectural paradigm that prescribes a set of guidelines for the construction of web services. A RESTful API provides a straightforward and adaptable means to interact with web services, devoid of any intricate processing requirements.
### Working
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/049/541/original/Screenshot_2023-09-20_184131.png?1695215654)
A client communicates with a server by issuing an HTTP request using a web URL, which can take the form of HTTP GET, POST, PUT, or DELETE requests. In return, the server provides a response in the form of a resource, which can take various formats such as HTML, XML, images, or JSON. Currently, JSON is the prevalent and widely used format in the realm of Web Services.
#### Code
```javascript!
const express = require('express')
const fs = require('fs')
const data = fs.readFileSync('data.json')
console.log('This is JSON data -> ' + data)
const app = express()
app.listen(8080, ()=>{
console.log('server started')
})
```
### Introduction
In HTTP there are five methods that are commonly used in a REST-based Architecture:
* GET
* POST
* PUT
* PATCH
* DELETE
We'll revisit these later in this script.
**Code:**
```javascript!
const express = require('express')
const app = express()
const fs = require('fs')
const data = JSON.parse(fs.readFileSync('data.json', "utf-8"))
const products = data.products
app.use(express.json())
console.log('This is JSON data -> ' + data)
//HTTP methods
app.get('/products', (req, res)=>{
res.send(products)
})
//we can pass something in id, find that id in json and find it's properties
//get
app.listen(8080, ()=>{
console.log('server started')
})
```
**Explanation:**
### GET() method
`GET()` is used to request data from a specified resource, such as a web page, an API endpoint, or any other resource accessible via a URL. It is primarily used for read-only operations, where we want to retrieve information without modifying the resource.
When a `GET()` request is made, it fetches data from the server without causing any changes to the resource. The data is then returned in the response, and the resource then remains unchanged on the server.
**Code:**
```javascript!
const express = require('express')
const app = express()
const fs = require('fs')
const data = JSON.parse(fs.readFileSync('data.json', "utf-8"))
const products = data.products
app.use(express.json())
console.log('This is JSON data -> ' + data)
//HTTP methods
app.get('/products', (req, res)=>{
res.send(products)
})
app.get('/products/:id', (req, res) =>{
const id = req.params.id
console.log(id)
const product = products.find(p=>p.id === id)
console.log(product)
})
//we can pass something in id, find that id in json and find it's properties
//get
app.listen(8080, ()=>{
console.log('server started')
})
```
**Output**
```
undefined
```
**Explanation:**
**Now why did we get output as undefined?**
Whenever you pass some thing to your id it comes in string format as opposed to when to pass it as `console.log(2)` it goes as arugment in number format
Let's rectify this and see how it works
**Rectified Code:**
```javascript!
const express = require('express')
const app = express()
const fs = require('fs')
const data = JSON.parse(fs.readFileSync('data.json', "utf-8"))
const products = data.products
app.use(express.json())
console.log('This is JSON data -> ' + data)
//HTTP methods
app.get('/products', (req, res)=>{
res.send(products)
})
app.get('/products/:id', (req, res) =>{
const id = Number(req.params.id)
console.log(id)
const product = products.find(p=>p.id === id)
console.log(product)
})
//we can pass something in id, find that id in json and find it's properties
//get
app.listen(8080, ()=>{
console.log('server started')
})
```
**Output**
```
Server Started
2
{
id: 2,
title: 'iPhoneX',
Description: 'Sim Free Model A1981'
price: 899,
discount Percentage: 17.94
rating: 4.44,
stocks: 34,
brand: 'Apple',
category: 'smartphones',
...
}
```
**Note:**
If you want to structure the output in JSON format then instead of `console.log(product)` you can do `res.json(product)`
### POST() method
`POST()` method is used to submit data to a specified resource, often to create a new resource on the server or to trigger a specific action that involves data submission.
When a `POST()` request is made, the data us sent to the server, and the server processes this data according to the resource's endpoint or the API's defined behavior. A new resource is created, if it already exists it updates it or triggers some other action.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/049/543/original/Screenshot_2023-09-20_184458.png?1695215705)
**Code:**
```javascript!
..same code
//post method
app.post('/products', (req, res)=>{
console.log(req.body)
products.push(req.body)
res.status(201).json(req.body)
})
```
### PUT() method
The `PUT()` method is an HTTP request method used to update a resource or create a new resource at a specific URI (Uniform Resource Identifier)
**Code:**
```javascript!
app.put('/products/:id', (req, res)=>{
const id = Number(req.params.id)
const productIndex = products.findIndex(p=>p.id===id)
products.splice(productIndex, 1, {...req.body, id:id})
res.status(201).json()
})
//talk about splice in terms of array while explaining
```
### PATCH() method
`PATCH()` is used to request partial modifications or updates to an existing resource.
When a `PATCH()` request is made, it typically sends a patch document or set of instructions to the server, indicating how the resource should be modified. The server processes these instructions and applies the specified changes to the resource
**Code:**
```javascript!
app.patch('/products/:id', (req, res)=>{
const id = Number(req.params.id)
const productIndex = products.findIndex(p=>p.id===id)
const product = products(productIndex)
products.splice(productIndex, 1, {...product, ...req.body})
res.status(201).json()
})
```
### DELETE() Method
`DELETE()` is used to request the removal or deletion of a resource located at a specific URI.
When a DELETE() request is made, it instructs the server to delete the resource identified by the URI. After a successful DELETE() operation, the resource no longer exists at that URI.
**Code:**
```javascript!
app.delete('/products/:id', (req, res)=>{
const id = Number(req.params.id)
const productIndex = products.findIndex(p=>p.id===id)
const product = products(productIndex)
products.splice(productIndex, 1)
res.status(201).json(product)
})
```
### Introduction to MongoDB
MongoDB, an open-source document-oriented database, is purpose-built for efficiently handling extensive datasets. It falls within the NoSQL (Not only SQL) database category due to its departure from the conventional table-based storage and retrieval of data.
### MongoDB Architecture
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/049/544/original/Screenshot_2023-09-20_184157.png?1695215721)
Now we know that MongoDB serves as a database server where data is stored. In other words, MongoDB provides an environment where you can initiate a server and subsequently create multiple databases within it.
Data in MongoDB is organized into collections and documents. This establishes a hierarchical relationship between databases, collections, and documents, which can be explained as follows:
**NOTE:** MongoDB's server supports the concurrent operation of multiple databases.
#### Analogy
Let's understand the MongoDB architecture with the help of an analogy
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/049/545/original/Screenshot_2023-09-20_184204.png?1695215735)
Let's consider a Mongoose application connected to a database called "Scaler." Within this database, there are three collection (courses, instructors, and students), and each collection corresponds to a multiple documents each representing data stored in diverse formats.
#### Install MongoDB on your system
You can install MongoDB on your system by typing the following command in your command terminal
```
npm install mongodb
```
#### Adaptability of MongoDB
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/049/546/original/Screenshot_2023-09-20_184211.png?1695215762)
Mongoose is specifically designed for Node.js, and so it's a natural choice for developers working with JavaScript on the server-side. This compatibility ensures seamless integration with Node.js applications.
#### Connect to MongoDB
**Code:**
```javascript!
const mongoose = require('mongoose')
const DB = "API"
mongoose.connect(DB, {
useNewUrlParser : true,
useUnifiedTopology : true,
}).then(()=>{
console.log('connection sucessful')
}).catch((err)=>{
console.log(error)
})
```
Run your file with:
```
npx nodemon test.js
```
**Explanation:**
Once Mongoose library is imported, a constant variable `DB` is defined with the value of API which represents the name or URL of the MongoDB database we want to connect to
A connection is then initiated with the MongoDB database using the `mongoose.connect` method and options object is passed with the `useNewUrlParser` (enables the use of the new URL parser for MongoDB) and `useUnifiedTopology` (enables the use of the new Server Discovery and Monitoring engine in MongoDB) options set to `true`. These options configures the connection.
Once the connection is successful, a message is then logged on the console. If however an error is encountered during the connection to database process the error message is logged onto the console
In this way this code sets up a connection to a MongoDB database named "API" using Mongoose

View File

@@ -0,0 +1,255 @@
## Today's Content
- MVC Architecture
- CRUD operation using MongoDB and Mongoose
# MVC Architecture Overview
Imagine we're using the following analogy to explain the MVC architecture:
"A customer (view) is ordering a pizza, so he makes a request to the waiter (controller). The waiter takes the request and goes to the chef (model) in the kitchen and fetches the items from the kitchen (database) to make the pizza. Once it's ready, the chef serves the pizza back to the waiter, who then serves it to the customer."
Now, let's break down the MVC architecture within this analogy.
**Model:**
- Represents the data and logic of the application.
- In the pizza example, the chef in the kitchen is the model.
- Manages and fetches data (ingredients) and performs operations (cooking) on it.
- The model is unaware of the user interface.
**View:**
- Represents the user interface or what the user interacts with.
- In the pizza example, the customer is the view.
- Displays information (menu options) to the user and captures user input (order).
- Passes user input to the controller.
**Controller:**
- Acts as an intermediary between the model and the view.
- In the pizza example, the waiter is the controller.
- Receives and processes user requests (orders) from the view.
- Interacts with the model (chef) to fetch data (ingredients) and perform actions (cooking).
- Sends updates back to the view to display the result (serving the pizza).
**The following image gives an idea about MVC architecture**:
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/052/060/original/upload_24f17def1d1ac7e73c3a74069cf7b9f8.png?1696316119)
In essence, MVC separates the application into three distinct components, making it easier to manage and maintain. The view handles the presentation and user interaction, the model manages the data and logic, and the controller orchestrates the communication between the view and the model. This separation of concerns enhances code organization and promotes scalability and maintainability in software development.
# Question
What is the primary responsibility of the Model component in the MVC architecture?
# Choices
- [ ] Handles user interface interactions.
- [ ] Manages routing and URL handling.
- [x] Represents the data and logic of the application.
- [ ] Interacts with the user and captures input.
# Benifits of MVC architecture
- **Separation of Concerns:**
- Divides the application into Model, View, and Controller for clear separation of responsibilities.
- **Modular Development:**
- Supports development and maintenance of separate, reusable modules for each component.
- **Improved Code Reusability:**
- Allows reuse of Models, Views, and Controllers in different parts of the application or other projects.
- **Enhanced Maintainability:**
- Changes in one component have minimal impact on the others, simplifying maintenance and debugging.
- **Scalability:**
- Facilitates parallel development and the addition of new features without major rework.
- **User Interface Flexibility:**
- Adapts to various user interfaces while keeping the core logic intact.
- **Efficient Testing and Debugging:**
- Enables isolated unit testing for each component, easing issue identification and resolution.
- **Parallel Development:**
- Supports multiple developers or teams working on different components simultaneously.
- **Support for Multiple Views:**
- Utilizes the same Model and Controller with multiple Views for diverse user interfaces.
- **Long-Term Maintainability:**
- Promotes organized and understandable code, reducing technical debt over time.
# Question
Which of the following is NOT a benefit of using the MVC architecture in software development?
# Choices
- [ ] Enhanced Maintainability
- [ ] Efficient Testing and Debugging
- [ ] Improved Code Reusability
- [x] Tight Coupling between Components
# Mvc Implementation in Project
### index.js Usage
- **Responsibility**: The `index.js` file serves as the entry point of your application, handling server setup and routing initialization.
- **Server Setup**: It initializes an Express server using the `express` library.
- **Routing Configuration**: The `productRouter` is imported from `productRoutes.js` and configured to handle incoming requests.
- **Middleware**: Express middleware, such as `express.json()`, is applied to handle JSON request bodies.
- **Server Start**: The server is started on port 8080, listening for incoming requests.
```javascript
// index.js
const express = require('express')
const app = express()
// Import the productRouter for handling routes
const productRouter = require('./routes/productsRoutes')
// Use the productRouter middleware to handle routes
app.use('/', productRouter.router)
// Apply middleware to handle JSON request bodies
app.use(express.json())
// Start the server, listening on port 8080
app.listen(8080, () => {
console.log('Server Started')
})
```
**In this section of code**:
- `express` is used to create and configure the server.
- The `productRouter` is used to define and manage the routes, connecting them to the Controller component.
- Express middleware is applied, such as `express.json()`, which parses incoming JSON request bodies.
- The server is started and listens on port 8080, making your application accessible to clients.
This file essentially serves as the glue that ties together the MVC components, initializing the server, configuring routes, and ensuring proper request handling and response generation.
### Model Implementation
- **Responsibility**:<br> Manages data, performs database interactions, and defines data structure.
- **Data Definition**:<br> The Model defines the structure of core entities (courses) in your application.
- **Database Interaction**:<br> It handles database connections and operations using Mongoose.
- **Entity Schema**:<br> The model includes a schema definition for the 'Course' entity, specifying fields like name, creator, published date, isPublished, and rating.
- **Entity Model**:<br> The 'Course' model is created using Mongoose to represent the data structure.
**Code**:
```javascript
// myDb.js
// ... (previous code)
// Schema definition for the 'Course' model
const courseSchema = new mongoose.Schema({
name: String,
creator: String,
publishedDate: { type: Date, default: Date.now },
isPublished: Boolean,
rating: Number
})
// Model creation for the 'Course' entity
const Course = mongoose.model('Course', courseSchema)
// Functions for creating and interacting with 'Course' documents
// (createCourse, getCourse, updateCourse, deleteCourse)
```
#### Code Explanation:
- `mongoose.connect(DB, { useNewUrlParser: true, useUnifiedTopology: true })`: Establishes a connection to the database using the provided MongoDB connection string.
- `courseSchema`: Defines the structure of the 'Course' entity with its fields and data types.
- `Course = mongoose.model('Course', courseSchema)`: Creates a model named 'Course' based on the defined schema, allowing you to interact with the database using this model.
- Functions like `createCourse`, `getCourse`, `updateCourse`, and `deleteCourse` are used for various operations on 'Course' documents in the database.
### Controller Implementation
- **Responsibility**:<br> Acts as an intermediary between the Model and the View (routes), handling incoming requests and preparing responses.
- **Data Processing**:<br> Controller functions process various actions such as retrieving data, creating, updating, and deleting data.
- **Data Interaction**:<br> These functions interact with the Model to perform data operations.
- **Business Logic**:<br> The Controller contains business logic for processing and validating data.
- **Response Preparation**:<br> It prepares and sends responses back to the View (routes) based on the request handling.
**Code**:
```javascript
// productControllers.js
const fs = require('fs')
// Data retrieval from JSON file
const data = JSON.parse(fs.readFileSync('data.json', "utf-8"))
const products = data.products
// Controller functions for handling different actions
// (getAllProducts, getProduct, createProduct, replaceProduct, updateProduct, deleteProduct)
```
#### Code Explanation:
- The Controller functions, such as `getAllProducts`, `getProduct`, `createProduct`, etc., are responsible for handling specific HTTP requests related to products.
- These functions perform data processing, including reading from a JSON file and manipulating product data.
- Business logic, such as finding products by ID or updating product information, is contained within these functions.
- Responses are prepared and sent back to the View (routes) with appropriate HTTP status codes and data.
# Question
In a typical MVC implementation, what is the role of the Controller component?
# Choices
- [ ] Manages the data and logic of the application.
- [ ] Represents the user interface and displays information.
- [ ] Handles routing and URL configuration.
- [x] Acts as an intermediary between the Model and the View, processing requests and preparing responses.
### View (Routes) Implementation
- **Responsibility**:<br> In this project, the View component is represented by routes, which define endpoints for user interactions.
- **Request Handling**:<br> Routes receive incoming HTTP requests from users.
- **Controller Interaction**:<br> They invoke Controller functions to process requests and provide data.
- **Response Generation**:<br> Based on the Controller's response, routes generate and send appropriate HTTP responses.
- **User Interaction**:<br> Routes facilitate user interactions with the application through defined endpoints.
**Code**:
```javascript
// productRoutes.js
const express = require('express')
const productController = require('../controllers/productControllers')
const router = express.Router()
// Routes for different user interactions
router.get('/products', productController.getAllProducts)
router.get('/products/:id', productController.getProduct)
router.post('/products', productController.createProduct)
router.put('/products/:id', productController.replaceProduct)
router.patch('/products/:id', productController.updateProduct)
router.delete('/products/:id', productController.deleteProduct)
```
#### Code Explanation:
- Express routes, defined in `productRoutes.js`, specify various endpoints for user interactions related to products.
- These routes handle HTTP requests, such as GET, POST, PUT, PATCH, and DELETE, for product-related actions.
- The routes interact with the Controller component by invoking the corresponding Controller functions, passing request data as needed.
- Responses generated by the Controller are returned to the client through these routes.
In summary, this project demonstrates the separation of concerns through the Model-View-Controller pattern, with distinct roles for the Model (data management), Controller (business logic), and View (routes for user interaction). The bullet-pointed code explanations provide a detailed breakdown of each component's responsibilities and how they interact within your application.

View File

@@ -0,0 +1,638 @@
## Agenda
The agenda for today's session includes implementing authentication and authorization, exploring protected routes for both users and administrators, discussing BookMyShow integration, covering payment processing with Stripe, and generating tickets. Toward the end, we will focus on deployment. Our initial focus for today will be on the authentication and authorization aspects.
---
title: Project Setup and Adding Frontend Code for Registration
description: In this segment, we'll set up React, Express, Node.js, and a MongoDB database, and we'll also write frontend code for user registration.
duration: 2700
card_type: cue_card
---
## Project Setup and Adding Frontend Code for Registration
Before we move into authentication and authorization, it's essential to set up the project infrastructure. We'll start by configuring React, Express, Node.js, and MongoDB. These components must be installed and configured correctly to proceed.
To begin, let's create a folder named "bookMyShow-project." Inside this folder, create another one called "client," where we'll develop the frontend of our application. To kickstart the React app, use the following command:
```cpp
npm create-react-app
```
Additionally, create a folder named "server" in the main project directory. The "client" folder will house all the frontend code, while the "server" folder will be dedicated to setting up our Express backend. With the React app initialized, let's navigate to the "App.js" file to continue.
#### Pseudocode
```cpp
function App() {
return (
<div>
<h1>
Hello
</h1>
</div>
)
}
export default App;
```
Now, let's initiate our client application by running the command npm start.
> Note to instructor - Take a moment to show the local host and see how our app looks.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/324/original/upload_c3cacc7db150560f524aa121bf3cb867.png?1695968640)
With our React app successfully set up, it's time to create two fundamental pages: the login and registration pages. These pages are essential components of any application.
Our initial set of pages includes:
1. Home Page
2. Register Page
3. Login Page
To expedite the development process, we will leverage Ant Design, a library that provides pre-built components, eliminating the need for extensive custom CSS. Within the component section, you'll find various components, each with an example code.
Since our focus is on building the login and registration pages, we'll primarily work with forms. So, let's navigate to the component section, search for "form," and explore multiple form examples. You can choose a form that suits your needs and copy the code from there.
In our client directory, let's begin by installing the required dependencies:
```javascript
npm install react-router-dom antd axios
```
Now, let's structure our project by creating a "pages" folder. Inside this folder, create three subfolders: "login," "register," and "Home." Within each of these subfolders, create an "index.js" file.
Here's the structure for "index.js" within the "Home" folder:
#### Pseudocode
```javascript
const Home = () => {
return (
<div>
This is my home page
</div>
)
}
export default Home;
```
Similarly, for the "index.js" file in the "login" folder:
#### Pseudocode
```javascript
const Login = () => {
return (
<div>
This is my Login page
</div>
)
}
export default Login;
```
And for the "index.js" file in the "register" folder:
#### Pseudocode
```javascript
const Register = () => {
return (
<div>
This is my Register page
</div>
)
}
export default Register;
```
We will navigate to the 'App.js' file import all of these components set up React Router DOM and create our first route
#### Pseudocode
```javascript
import Home from "./pages/Home";
import Login from "./pages/Login";
import Register from "./pages/Register";
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<div>
<BrowserRouter>
<Routes>
<Route path = '/' element = {<Home />} />
<Route path = '/login' element = {<Login />} />
<Route path = '/register' element = {<Register />} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
```
> Note to instructor - Test the functionality of our home page
Within our 'client' folder, we'll create a stylesheet that we'll use in our components. We already have five CSS files prepared:
* '`alignment.css`' contains alignment-related code for flex, margin, justify, padding, and more, so we won't need to worry about these aspects.
* `'custom.css'` is available for customization if you want to tailor the appearance of your application.
* `'form-elements.css'` provides styles for form elements.
* `'sizes.css'` is for adjusting various sizes.
* `'theme.js'` contains definitions for themes, text styles, fonts, and colors that we'll be using.
Now, let's turn our attention to the registration page. Here's what we need for this page:
* **Three fields:** name, email, and password.
* A 'Register' button that users can click to complete the registration process.
We'll use the form components provided by Ant Design to facilitate this."
#### Pseudocode
```javascript
import React from 'react';
import { Form } from 'antd';
const Register = () => {
return (
<div>
<Form>
<Form.Item label = 'Name' name = 'name' rules = {[{ required: true, message: "Please enter your name" }]}>
<input type = 'text' />
</Form.Item>
</Form>
</div>
);
}
export default Register;
```
Now, let's proceed to include the remaining fields.
#### Pseudocode
```htmlembedded
import React from 'react';
import { Form } from 'antd';
const Register = () => {
return (
<div>
<Form>
<Form.Item label = 'Name' name='name' rules = {[{ required: true, message: "Please enter your name" }]}>
<input type = 'text' />
</Form.Item>
<Form.Item label = 'Email' name = 'Email' rules = {[{ required: true, message: "Please enter your email" }]}>
<input type = 'text' />
</Form.Item>
<Form.Item label = 'Password' name = 'Password' rules = {[{ required: true, message: "Please enter your password" }]}>
<input type = 'text' />
</Form.Item>
</Form>
</div>
);
}
export default Register;
```
Now, let's make sure to include all our stylesheets in 'App.js' for consistency
#### Pseudocode
```javascript
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import Login from './pages/Login';
import Register from './pages/Register';
import './stylesheets/alignment.css';
import './stylesheets/sizes.css';
import './stylesheets/form-elements.css';
import './stylesheets/theme.css';
import './stylesheets/Custom.css';
function App() {
return (
<div>
<BrowserRouter>
<Routes>
<Route path = "/" element = {<Home />} />
<Route path = "/login" element = {<Login />} />
<Route path = "/register" element = {<Register />} />
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
```
We've already added the necessary styling, and to save time, we'll directly integrate our form code into the 'index.js' file of the 'Register' component. We're removing the previously written code and replacing it with Ant Design components to help you understand how to use them effectively.
But there's one element missing - a button. To address this, let's create a 'components' folder. Inside this folder, we'll create a 'buttons' subfolder where we'll add the button code. Then, we'll import the button component into our 'Register' page."
#### Pseudocode
```javascript
import React, { useEffect } from 'react';
import Button from '../../components/Button';
import { Link, useNavigate } from 'react-router-dom';
import { Form, message } from 'antd';
import { RegisterUser } from '../../apicalls/users';
const Register = () => {
const navigate = useNavigate();
const onFinish = async (values) => {
try {
const response = await RegisterUser(values);
if (response.success) {
message.success(response.message);
console.log(response.message);
} else {
message.error(response.message);
console.error(response.message);
}
} catch (error) {
message.error(error);
}
};
useEffect(() => {
if (localStorage.getItem('token')) {
navigate('/');
}
}, []);
return (
<div className = "flex justify-center h-screen items-center bg-primary">
<div className = "card p - 3 w - 400">
<h1 className = "text - xl mb - 1">Welcome to Scaler Shows! Please Register</h1>
<hr />
<Form layout = "vertical" className = "mt - 1" onFinish = {onFinish}>
<Form.Item
label = "Name"
name = "name"
rules = {[{ required: true, message: 'Please input your name!' }]}
>
<input type = "text" />
</Form.Item>
<Form.Item
label = "Email"
name = "email"
rules = {[{ required: true, message: 'Please input your email!' }]}
>
<input type = "email" />
</Form.Item>
<Form.Item
label = "Password"
name = "password"
rules = {[{ required: true, message: 'Please input your password!' }]}
>
<input type = "password" />
</Form.Item>
<div className = "flex flex - col mt - 2 gap - 1">
<Button fullWidth title = "REGISTER" type = "submit" />
<Link to = "/login" className = "text-primary">
{' '}
Already have an account? Login
</Link>
</div>
</Form>
</div>
</div>
);
};
export default Register;
```
## Implementation of the Login Page
Next, we will craft the login page, following a similar approach. We'll modify the button text to 'Login' and eliminate the 'name' field from the login page's form.
#### Pseudocode
```javascript
import React , {useEffect} from 'react'
import {Form, message} from "antd";
import Button from "../../components/Button";
import { Link , useNavigate } from "react-router-dom";
import { LoginUser } from '../../apicalls/users';
const Login = () => {
const navigate = useNavigate()
const onFinish = async(values) => {
try {
const response = await LoginUser(values)
if(response.success){
message.success(response.message)
localStorage.setItem('token' , response.data)
window.location.href = "/";
}
else{
message.error(response.message)
}
} catch (error) {
message.error(error.message)
}
}
useEffect(() => {
if (localStorage.getItem("token")) {
navigate("/");
}
}, []);
return (
<div className = "flex justify-center h-screen items-center bg-primary">
<div className = "card p - 3 w - 400">
<h1 className = "text-xl mb-1">Welcome Again! Please Login</h1>
<hr />
<Form layout = "vertical" className = "mt - 1" onFinish = {onFinish}>
<Form.Item
label = "Email"
name = "email"
rules = {[{ required: true, message: "Please input your email!" }]}
>
<input type = "email" />
</Form.Item>
<Form.Item
label = "Password"
name = "password"
rules = {[{ required: true, message: "Please input your password!" }]}
>
<input type = "password" />
</Form.Item>
<div className = "flex flex - col mt - 2 gap - 1">
<Button fullWidth title = "LOGIN" type = "submit" />
<Link to = "/register" className = "text-primary">
{" "}
Don't have an account? Register
</Link>
</div>
</Form>
</div>
</div>
)
}
export default Login
```
## Establish Server-Side Architecture
Next, we'll move into the server-side operations. Within our server directory, we'll create a file named 'server.js' and proceed to install the Express framework.
To enhance security, we'll make use of 'bcryptjs', a package specialized in password hashing. The necessary packages can be installed with the following command:
```javascript
npm install express mongoose bcryptjs jsonwebtoken
```
While the client side encompasses all frontend code, the server side will house the backend logic. In 'server.js', we'll initiate the Express server. This step forms the foundation for our backend operations.
#### Pseudocode
```javascript
const express = require('express');
const app = express();
app.listen(8082, () => {
console.log('Server is Running');
});
```
In the code snippet provided, we have the 'server.js' file for our Express application. To start the server, you can use 'npx nodemon server.js.' This command ensures that your server automatically restarts upon code changes, streamlining the development process.
As part of our server setup, we'll create an environment configuration ('`.env`') file where you can store essential information such as the MongoDB URL and `JSON Web Token (JWT)` secrets. This allows us to keep sensitive data secure.
Furthermore, we'll establish a 'dbconfig.js' file in the 'config' folder. This file will contain the configuration settings for connecting to our MongoDB database.
#### Pseudocode
```javascript
const mongoose = require('mongoose')
mongoose.connect(process.env.mongo_url)
const connection = mongoose.connection
connection.on('connected' , () => {
console.log('Connection Succesful')
})
```
To bring everything together, the revised 'server.js' code snippet includes the necessary 'require' statements, environment variable loading, and the inclusion of the 'dbConfig' module. This ensures that our server is properly configured and ready to run on port 8082."
#### Pseudocode
```javascript
const express = require('express');
const app = express();
require('dotenv').config(); // Load environment variables
const dbConfig = require('./config/dbConfig'); // Import database configuration
app.listen(8082, () => {
console.log('Server is Running');
});
```
## Exploring Authentication
Authentication is a fundamental aspect of our application. We'll define the schema and create a model based on that schema. For user registration, our schema will encompass essential fields like name, password, and email.
To structure our project effectively, we'll establish a 'model' folder. Our initial model, 'userModel.js,' will commence with defining a schema. With this schema in place, we'll proceed to create a model, enabling the creation of various user documents. Mongoose will play a pivotal role in defining our schema.
#### Pseudocode
```javascript
// userModel.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
isAdmin: {
type: Boolean,
required: true,
default: false
}
});
module.exports = mongoose.model('users', userSchema);
```
Additionally, we'll implement email validation and introduce an 'isAdmin' property to manage admin-specific permissions for select users.
Routing is another crucial aspect. We'll create user routes within our 'routes' folder.
#### Pseudocode
```javascript
// userRoutes.js
const router = require('express').Router();
const User = require('../models/userModel');
// Register a user
router.post('/register', async (req, res) => {
try {
const userExists = await User.findOne({ email: req.body.email });
if (userExists) {
return res.send({
success: false,
message: 'User Already Exists'
});
}
const newUser = new User(req.body);
await newUser.save();
res.send({ success: true, message: 'Registration Successful, Please login' });
} catch (error) {
console.log(error);
}
});
module.exports = router;
```
In the 'server.js' file, we will establish a route for user-related operations:
#### Pseudocode
```javascript
const express = require('express');
const app = express();
require('dotenv').config();
const dbConfig = require('./config/dbConfig');
const userRoute = require('./routes/userRoute');
app.use(express.json());
app.use('/api/users', userRoute);
app.listen(8082, () => {
console.log('Server is Running');
});
```
> Note to instructor - Test the functionality till this point
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/325/original/upload_0a1c4c3b0afa456fb137116cf0e88e23.png?1695969450)
## Investigating Hashing and Encryption
Hashing and encryption are both methods used to protect data, but they operate differently, and their purposes vary.
Encryption is a two-way process that involves converting data into a coded format (cipher) that can be easily reversed back to its original form (plaintext) using a decryption key. In encryption, there's always a corresponding decryption process, allowing data to be securely stored and transmitted while maintaining its reversibility.
Hashing, on the other hand, is a one-way process. It involves converting data, such as a password, into a fixed-length string of characters, often a hash value or digest. The key distinction is that there's no straightforward way to reverse this process and obtain the original data. If, for instance, a password like "abc" is hashed, it becomes something like "%B#82^&," and this transformation is irreversible.
One common library used for hashing in JavaScript is bcrypt. Now, let's implement bcrypt in the 'userRoute' code:
#### Pseudocode
```javascript
const router = require('express').Router();
const bcrypt = require('bcryptjs');
const User = require('../models/userModel');
// Register a user
router.post('/register', async (req, res) => {
try {
const userExists = await User.findOne({ email: req.body.email });
if (userExists) {
return res.send({
success: false,
message: 'User Already Exists'
});
}
// Hash the password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(req.body.password, salt);
req.body.password = hashedPassword;
const newUser = new User(req.body);
await newUser.save();
res.send({ success: true, message: "Registration Successful, Please login" });
} catch (error) {
console.log(error);
}
});
module.exports = router;
```
In this code, we've integrated bcrypt to securely hash the user's password before storing it in the database. This enhances password security ensures that plaintext passwords are not stored.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/326/original/upload_ea5e3fdb5425c91bf4e666263f4ffa03.png?1695969502)
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/327/original/upload_e13d594015ef8e048d6515afeecbd113.png?1695969539)

View File

@@ -0,0 +1,407 @@
# Full Stack LLD & Projects: FullStack-5: Project Part 2- (Creating User's & Admins Route)
## Agenda
* Revise the concepts taught in the previous class.
* Registration route creation
* Login route creation for the user
* JWT and Protected Route
## Login and Registration Route Creation
>Before proceeding, show a glimpse of the project done till now.
>Ask the students if they have any doubt uptil now.
Now that we have written the code for registration, we will be going ahead with the login process.
* Registration is a one-time process.
* Login- After entering the username and password, we only need to check for the credentials if the user exists in the database or not
>Ask the students which method should be used to create login route. Like for registration, we used the POST method, similarly, what should be used here?
The answer is, we will be using the POST method for login route creation. It is so, because we are sending the data to the backend and then matching it.
### Steps
**Step 1**- Create a post request with name `/login` and it will have a async callback.
**Step 2**- We will have the first check for email whether it is present in the database or not.
```javascript
const user = await User.findOne({email : req.body.email})
```
The above line is used to find the user email in the particular list of emails.
**Step 3**- If the user is not present, we will not allow the user to login and give an error message. We will set `success` as `false` and message as 'user does not exist'.
```javascript
if(!user){
return res.send({
success : false,
message : 'User does not exist'
})
}
```
**Step 4**- Now we will be checking for password. We will check if the entered password matches the password in the database. In Bcrypt, there are two methods to compare- `compare` and `compareSync`. As we are using `await`, we will be using `compare`. It will compare the entered password into hash form with the password in the database.
```javascript
const validPassword = await bcrypt.compare(req.body.password , user.password)
```
Here, the first parameter is the password we have entered and the second parameter is the password corresponding to the username.
**Step 5**- If the passwords do not match, we will display an error message.
```javascript
if(!validPassword){
return res.send({
success : false,
message : 'Invalid Password'
})
}
```
**Step 6**- If everything is successful, we will send a successful message that the user has been logged in successfully.
```javascript
res.send({
success : true,
message : 'User Logged in',
})
```
Now, let us test our code. The route in postman will be changed to `login`. We also do not require the `name` key in our data so we erase it and only keep the `email` and `password`. We send the data and we received 'success' message.
>Also show the message after entering an incorrect email and password.
Now, the data needs to be shown on the frontend. We need to link the client and the server. We will be splitting the terminal- zsh and open client and server in split view.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/343/original/upload_f3674562643f00dcbd6f06ce1e68219a.png?1695972822)
On the left is Client and on the right is Server. To run the server we will use the following commands one by one-
```javascript
cd bookmyshow-project
cd server
server.js
```
Now, we have to link our server and client. In our `client` folder, we will go under `package.json` and add the following code-
```javascript
"proxy": "http://localhost:8082"
```
The address is the server address which can be found from postman. Our client will now use the above address as proxy.
Frontend should know what data should be there, what data is coming, what data to validate. We will be creating a folder `apicalls` under `client` folder. We will be using **axios** to make API calls.
We will create a file called `index.js` under `apicalls` where we will set axios.
We will import axios and create axios instance.
```javascript
import axios from 'axios'
export const axiosInstance = axios.create({
headers : {
credentials : 'include',
method : 'post',
'Content-Type' : 'application/json'
}
})
```
The `create` method of axios is used to create an instance. We will add the above headers.
Now we will set up the route for frontend with file name `user.js`.
**Step 1**- We will require axios instance. As it is in the same folder, we will just use a `.`(dot)
**Step 2**- Register a new user. We will create a function`RegisterUser` which will take in the payload. it is an async function. Inside it we will use a try-catch function. A variable `response` is created and we will be using `axiosInstance` and the method is `post`. The route which we will hit on is `/register` right now. Then the response data will be returned.
```javascript
export const RegisterUser = async ()=>{
try {
const response = await axiosInstance.post('/register' , payload)
return response.data
} catch (error) {
return error
}
}
```
>Open `pages` folder and then `Register` folder and within it open `index.js`.
**Payload**
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/344/original/upload_9441d841911b694a4ceef8a4b3bc465f.png?1695972854)
Now in the above code, we can see that there are various fields and we have to enter some data in it. There is a button of `type="submit"` which sends the data which is entered by the user. That data is known as the **payload**.
In our `registerUser` function, our route is `/register`, and the method is `axios.post`. The proxy that we have set- `"proxy": "http://localhost:8082"` is the server's proxy. The axios instance that we have created will look at the server/ proxy that we have made and inside our proxy we have `register` which will set the data. Basically that user will be registered at the backend.
**Step 3**-
* Now, we need to get our payload out and send it to the `register` function. In our `index.js` file opened before, we will be making changes to it.
* We will be using the `onFinish` function by AntD. It is an AntD version of `onSubmit`.
* We will implement a `onFinish()` function. Whatever data we get, we will call it as `values`.
* We will also import the `registerUser` function.
```javascript
import { RegisterUser } from '../../apicalls/users'
```
* We will have a try-catch block. So, inside the try block, we will try to send values to the `registerUser` function as payload values.
* If it has been successfully submitted, there won't be any error and success message will be displayed. If it has an error, an error will be displayed.
```javascript
const Register = () => {
const onFinish= async (values)=>{
try {
const response = await RegisterUser(values)
if(response.success){
message.success(response.message)
console.log(response.message)
}
else{
message.error(response.message)
console.log(response.message)
}
} catch (error) {
message.error(error)
}
}
```
We will also add payload as a parameter to
```javascript
export const RegisterUser = async (payload) => {
try {
const response = await axiosInstance.post('/register' , payload)
return response.data
} catch (error) {
return error
}
}
```
We will also add the `onFinish` prop to the Form Component.
```javascript
<Form layout = "vertical" className = "mt - 1" onFinish = {onFinish}>
```
Now let us try to register our user on the webpage.
If the application runs successfully, you will see a success message. If there is any error, like CORS error, we need to fix it. Let's know more about it-
CORS error stands for Cross Origin Resource Sharing. These issues arise due to cross resource sharing. To prevent this, we have to install CORS first and add it in our index.js by using the following lines-
```javascript
var cors = require('cors')
app.use(cors())
```
### Login Route Creation
For login route, inside our users.js file, we will create a `LoginUser` function. You can change the `/login` route to `api/users/login` and similarly for registration and in our `server.js` where we had declared the routes.
```javascript
export const LoginUser = async (payload) => {
try {
const response = await axiosInstance.post('api/users/login' , payload)
return response.data
} catch (error) {
return error
}
}
```
In our `index.js` file of the Login folder, we will write our code. for login data submission to the backend.
**Login**
* If the user exists, then login successfully.
* As soon as we login, we will send the user to the home page. And we need the user to navigate from one page to another. For that we will first import hook `useNavigate` from React DOM. Modify the line to
```javascript
import { Link , useNavigate } from "react-router-dom";
```
We will add the location to the navigate function to where we want the current page.
```javascript
const Login = () => {
const navigate = useNavigate()
const onFinish = async(values) =>{
try {
const response = await LoginUser(values)
if(response.success){
message.success(response.message)
localStorage.setItem('token' , response.data)
window.location.href = "/";
}
else{
message.error(response.message)
}
} catch (error) {
message.error(error.message)
}
}
}
```
## JWT
### Introduction
* JWT stands for **JSON Web Token**.
* Tell the students that in most of the websites, we do not need to login again and again. In sites like FB, Instagram, when we login before, it does not ask us to login again whenever we open the app. It is possible because of **JWT**.
* JWT is important to get logged in for a particular session.
* Send [JWT](https://jwt.io/) token link to the students.
* JWT tokens are unique for each session.
### Generate Tokens
Let us try to generate a unique token for in our `userRoute.js` file for login.
We will first require the jwt package.
```javascript
const jwt = require('jsonwebtoken')
```
We will define a `token` variable and use the `sign` method that creates a token. We have to pass it an ID for it to generate a token.
In our `.env` file, we will define `jwt-secret` is a text that you create by yourself. It should not be exposed which might cause harm to the system.
In our case, we will assign it-
```javascript
jwt_secret = scaler-movies
```
The `expiresIn` function is used to mention for how much time we want the token to remain alive.
```javascript!
const token = jwt.sign({userId : user._id} , process.env.jwt_secret , {expiresIn :"1d"})
console.log(token)
````
Using the above token, a session will be available to us. A session is a time period. In the above example, we have used `1d` which means 1 day. The token is alive for 1 day and will expire after it.
We will also send data as token using the following-
```javascript
res.send({
success : true,
message : 'User Logged in',
data : token
})
```
In the Postman app, let us try to login and check whether we receive tokens or not.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/345/original/upload_737138f77bbf2405b8dd1a6bccbd797e.png?1695973009)
Now, paste the data part as selected above and let us go to the JWT website and paste it.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/346/original/upload_ccaf8a491276cc6f0702d41fa1e16f30.png?1695973028)
In the payload part, you can see the `userId` starts with `64` and ends with `56`. Now, when we check in database in postman, we can see the a user with same starting and ending id.
If you want to check for which particular user a token was generated, we can go to JWT website, paste the token and get the ID of that user.
In our frontend, we will be going to our `index.js` file and setting the token in our local storage.
```javascript
if(response.success){
message.success(response.message)
localStorage.setItem('token' , response.data)
window.location.href = "/";
}
```
The token saved as `response.data` will be saved as token in the local storage. That token will be saved for the time period set by us earlier.
Many times we see the message **session expired** that means the token saved in the local storage has gone.
we login again now. After logging in we can see under the Applications tab and under local storage the token being stored.
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/347/original/upload_86e590e42dbba2888f15677de6f5857f.png?1695973053)
>Ask the students if they have any idea about Protected Routes.
### Introduction
* Protected Route is a route that is not accessible publicly.
* Login and register page are public routes. But, if you want to see the home page, you will have to pass through the login and registration.
* Some of the resources like Paid courses are coded in such a way that they are under Protected Routes.
Open `userRoute.js` and create a protected route.
### Create a Protected Route
* We will be creating a route. Here we want to get the details so we will be using the `get` method.
* We do not need the password, so we will be using `-name of the property` hyphen along with the name of the property which we do not want.
* For the code to work, we will have to create a middleware. Inside our `server` folder, we will create a middlware. File called `authMiddleware.s` will be created.
```javascript
router.get('/get-current-user',authMiddleware, async (req , res) => {
try {
const user = await User.findById(req.body.userId).select('-password')
res.send({
success : true,
message : 'User details fetched Successfully',
data : user
})
} catch (error) {
res.send({
success: false,
message: error.message,
});
}
})
```
For the middleware, we will write the following code-
Inside the try block, we will use the authorization token. It will contain all the details how you are authorized.
Authorization token consists of two parts-
* Bearer
* JSON web token
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/051/348/original/upload_0a53611cf032de2e3578251e93142fac.png?1695973084)
* We want the JSON web token, so we will be using the 1st index.
* We will be using the `decoded` variable to check whether the `jwt_secret` matches the token generated. If it is generated by the secret, then that user has to be allowed.
```javascript
const jwt = require('jsonwebtoken')
module.exports = function(req , res , next){
try {
const token = req.headers.authorization.split(' ')[1]
const decoded = jwt.verify(token , process.env.jwt_secret)
req.body.userId = decoded.userId
next()
} catch (error) {
res.status(401).send({ success: false, message: "Invalid token" });
}
}
```
If the user is not verified, an error message is generated.
In our `index.js` file of `apicalls`, we will add the authorization by including-
```javascript
authorization : `Bearer ${localStorage.getItem('token')} `
```
So, when we hit the `/get-current-user` route, it will pass through the middleware `authMiddleware` and when the middleware lets it pass, we will get the user.

View File

@@ -0,0 +1,339 @@
## Protected Route
A protected route is a route that is not available publicly
**So how to access it?**
It can only be accessed after passing ones credentials only after which can one see the actual route.
## Token
**JWT (JSON Web):** Whenever a person logs in, a JWT token is generated which is totally unique and this token is then saved to a local storage of browser user is using.
### How does it work?
If the token is already present in the browser local storage system knows that user is logged in.
```javascript
const token = jwt.sign({userID: user_id}, process.env.jwt_secret, {expires})
```
### Properties Used:
Our objective is to retrieve the userID of the currently signed-in user. Following are the steps:
* We obtain the userID from the database where user information is stored.
* For this specific user, a JSON web token is generated.
* To achieve this, we utilize a **jwt_secret**, which is essentially a unique value responsible for generating these web tokens.
* Additionally, we employ a property that defines the token's lifespan, ensuring that after a user logs in, they will have access for only one day.
* Subsequently, once the token expires, the user will need to log in again.
```javascript
console.log(token)
res.send({
sucess: true,
message: "User logged in",
data: token
})
```
## Implementation of a protected route?
Once the token is generated and sent to the database which will then be used to implement a protected route only after which will a user be able to access the content of a particular route
```javascript
router.get('/get-current-user', authMiddleware, async (req, res) => {
try{
const user = await User.findById(req.body.userId).select('')
res.send({
sucess: true,
message: "User details fetched successfully"
data: user
})
}
catch(error){
res.send({
success: false,
message: error.message,
});
}
})
```
Here `/get-current-user` is a protected route,
`authMiddleware` checks if the user is authorized to acess this route by checking if the user is authenticated or not.
Only if the token is present in the database (generated prior by jwt_secret) only then we will allow the user to access the contents.
```javascript
module.exports = function(req, res, next){
try{
const token = req.headers.authorization.split('')[1]
const decoded = jwt.verify(token, process.env.jwt_secret)
req.body.userId = decoded.userId
next()
}
catch(error){
res.status(401).send({success: false, message: "Invalid"})
}
}
```
If a token is available, we first decode the user ID and then proceed to the next step.
Returning to our earlier code, we retrieve the userID and omit the password as it is unnecessary. Once we locate the user, we display a message indicating that they may proceed.
## Front-end
Now that the backend part is done we need to move onto the front end part of this
Let's see checks needed for login function
```javascript
import React from 'react'
const ProtectedRoute = ({childern}) => {
return {
<div></div>
}
}
export default ProtectedRoute
```
Here,
**childern (home page):** a component that I want to make it protected
### Redux
As we move further manging a state is diffcult which is why we'll be using redux
```javascript
npm install @reduxjs/toolkit
npm install react-redux
```
We're using redux here to basically use a loader whenever a user logs in
Three files can be created as following
```javascript
/redux
- loaderSlice.js
- store.js
- userSlice.js
```
**userSlice.js**
```javascript!
import {createSlice} from '@reduxjs/toolkit';
const loadersSlice = createSlice({
name: "loaders",
initialState: {
loading: false,
},
reducers: {
ShowLoading : (state) => {
state.loading = true;
},
HideLoading : (state) => {
state.loading = false;
}
}
});
export const {ShowLoading, HideLoading} = loaderSlice.actions;
export default loadersSlice.reducer;
```
**Store.js**
```javascript
import { configureStore } from "@reduxjs/toolkit";
import loadersReducer from "./loadersSlice";
import usersReducer from "./usersSlice";
const store = configureStore({
reducer: {
loaders: loadersReducer,
users: usersReducer,
},
});
export default store;
```
**loaderSlice.js**
```javascript
import {createSlice} from '@reduxjs/toolkit';
const loadersSlice = createSlice({
name: 'loaders',
initialState: {
loading : false,
},
reducers: {
ShowLoading : (state) => {
state.loading = true;
},
HideLoading : (state) => {
state.loading = false;
}
}
});
export const {ShowLoading, HideLoading} = loadersSlice.actions;
export default loadersSlice.reducer;
```
Our second step would be to show details if the existing user tries to login again, so the question here is how to get the details of a particular user
We can get user data from the following
```javascript
const { user } = useSelector{(state) => state.users}
```
Thus our final `protectedroute.js` is:
```javascript
import { message } from "antd";
import React, { useEffect} from "react";
import { GetCurrentUser } from "../apicalls/users";
import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { SetUser } from "../redux/usersSlice";
import { HideLoading, ShowLoading } from "../redux/loadersSlice";
function ProtectedRoute({ children }) {
const { user } = useSelector((state) => state.users);
const navigate = useNavigate();
const dispatch = useDispatch();
const getpresentUser = async () => {
try {
dispatch(ShowLoading());
const response = await GetCurrentUser();
dispatch(HideLoading());
if (response.success) {
dispatch(SetUser(response.data));
} else {
dispatch(SetUser(null));
message.error(response.message);
localStorage.removeItem("token");
navigate("/login");
}
} catch (error) {
dispatch(HideLoading());
dispatch(SetUser(null));
message.error(error.message);
}
};
useEffect(() => {
if (localStorage.getItem("token")) {
getpresentUser();
} else {
navigate("/login");
}
}, []);
return (
user &&
(
<div className = "layout p-1">
<div className = "header bg-primary flex justify-between p-2">
<div>
<h1 className = "text-2xl text-white cursor-pointer"
// onClick={() => navigate("/")}
>Book My Show</h1>
</div>
<div className = "bg-white p-1 flex gap-1">
<i className = "ri-shield-user-line text-primary mt-1"></i>
<h1
className = "text-sm underline"
onClick = {() => {
if (user.isAdmin) {
navigate("/admin");
} else {
navigate("/profile");
}
}}
>
{user.name}
</h1>
<i
className = "ri-logout-box-r-line mt-1"
onClick = {() => {
localStorage.removeItem("token");
navigate("/login");
}}
></i>
</div>
</div>
<div className = "content mt-1 p-1">{children}</div>
</div>
)
);
}
export default ProtectedRoute;
```
## Summary so far
To sum up we have the following:
* `userRoute.js` file
* Get current user route
* Middleware
* **API calls**: In order to get the current user
* **Redux**: contains slices and store.js files
* `Protected route.js` file
## Login
In order to prevent the user from going back to login page after logging in we can do the following
```javascript
useEffect{() => {
if(localStorage.getItem("token")){
navigate("/");
}
}, []};
```
You can use CDN remix icon link in your html file to add stylish icons
## Users
We'll have two types of user
### Admin
1. **Add the movies:** By editing updates
2. **Add the theatres**
`isAdmin: true`
### User
1. login/logout
2. see the shows
3. book the tickets
4. generate ticket
5. make payment
`isAdmin: false`
`/admin` If this is present in the url of the app then check for certain permissions by checking if `isAdmin` property is *true*, if yes give the rights and if not then revert to
`/profile`: normal user rights.
## UI - Display
The finished UI should have the following:
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/054/687/original/upload_9010f428767216bb0f328d971b568460.png?1697915994)
![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/054/688/original/upload_a50a1c5e7351b37a4c5c3b8c469e34b5.png?1697916285)
### Display: Form elements
1. **Add button**: It places the text at respective places/variables
2. **Movie Route**: Adds movies using Instance.AddMovies
3. **Table** at UI