# Node js - day4

## Setting Up the Project

1. **Initialize the project**:
    
    ```c
    mkdir social-media-app
    cd social-media-app
    npm init -y
    npm install express ejs body-parser
    ```
    
2. **Basic Server Setup** (`index.js`):
    
    ```c
    const express = require('express');
    const app = express();
    
    // Middleware
    app.use(bodyParser.urlencoded({ extended: true }));
    app.set('view engine', 'ejs');
    app.set('views', './views');
    
    app.listen(3000, () => console.log('Server is running on http://localhost:3000'));
    ```
    

---

## Step 1: Displaying a User Profile

### Route: `/profile`

A route to display a user's profile.

#### Server Code

```c
app.get('/profile', (req, res) => {
  const user = {
    name: 'Jane Doe',
    bio: 'Love coding and coffee!',
    followers: 120,
    following: 80,
    posts: ['My first post!', 'Hello, world!', 'EJS is awesome!'],
  };
  res.render('profile', { user });
});
```

#### EJS Template (`views/profile.ejs`)

```c
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= user.name %>'s Profile</title>
</head>
<body>
  <h1>Welcome to <%= user.name %>'s Profile</h1>
  <p><strong>Bio:</strong> <%= user.bio %></p>
  <p><strong>Followers:</strong> <%= user.followers %></p>
  <p><strong>Following:</strong> <%= user.following %></p>
  
  <h2>Posts:</h2>
  <ul>
    <% user.posts.forEach(post => { %>
      <li><%= post %></li>
    <% }); %>
  </ul>
</body>
</html>
```

---

## Step 2: Displaying Posts Feed

### Route: `/feed`

A route to show a feed of all user posts.

#### Server Code

```c
app.get('/feed', (req, res) => {
  const posts = [
    { username: 'Jane Doe', content: 'My first post!' },
    { username: 'John Smith', content: 'EJS makes templating easy!' },
    { username: 'Alice Brown', content: 'Check out this cool app!' },
  ];
  res.render('feed', { posts });
});
```

#### EJS Template (`views/feed.ejs`)

```c
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Feed</title>
</head>
<body>
  <h1>Posts Feed</h1>
  <ul>
    <% posts.forEach(post => { %>
      <li>
        <strong><%= post.username %></strong>: <%= post.content %>
      </li>
    <% }); %>
  </ul>
</body>
</html>
```

---

## Step 3: Notifications Section

### Route: `/notifications`

A route to show user notifications with conditional rendering.

#### Server Code

```c
app.get('/notifications', (req, res) => {
  const notifications = [
    { type: 'like', message: 'John liked your post.' },
    { type: 'comment', message: 'Alice commented: "Great post!"' },
    { type: 'follow', message: 'Bob started following you.' },
  ];
  res.render('notifications', { notifications });
});
```

#### EJS Template (`views/notifications.ejs`)

```c
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Notifications</title>
</head>
<body>
  <h1>Your Notifications</h1>
  <% if (notifications.length > 0) { %>
    <ul>
      <% notifications.forEach(notification => { %>
        <li><%= notification.message %></li>
      <% }); %>
    </ul>
  <% } else { %>
    <p>No new notifications!</p>
  <% } %>
</body>
</html>
```

---

## Step 4: Creating a New Post

### Route: `/create-post`

A route for rendering a form and saving a new post.

#### Server Code

```c
app.get('/create-post', (req, res) => {
  res.render('create-post');
});

app.post('/create-post', (req, res) => {
  const { content } = req.body;
  console.log(`New post: ${content}`);
  res.redirect('/feed');
});
```

#### EJS Template (`views/create-post.ejs`)

```c
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Create Post</title>
</head>
<body>
  <h1>Create a New Post</h1>
  <form action="/create-post" method="POST">
    <textarea name="content" rows="4" cols="50" placeholder="Write your post..."></textarea><br>
    <button type="submit">Post</button>
  </form>
</body>
</html>
```

---

## Step 5: Implementing Layouts

### Refactoring Layout with `express-ejs-layouts`

Install the package:

```c
npm install express-ejs-layouts
```

#### Update Server Code

```c
const expressLayouts = require('express-ejs-layouts');
app.use(expressLayouts);
app.set('layout', 'layout');
```

#### Create Layout File (`views/layout.ejs`)

```c
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= title || 'Social Media App' %></title>
</head>
<body>
  <header>
    <h1>Social Media App</h1>
    <nav>
      <a href="/profile">Profile</a>
      <a href="/feed">Feed</a>
      <a href="/notifications">Notifications</a>
    </nav>
  </header>
  <main>
    <%- body %>
  </main>
  <footer>
    <p>&copy; 2025 Social Media App</p>
  </footer>
</body>
</html>
```

Now, all pages will use this layout.

### Step 1: Install Tailwind CSS

We'll use a CDN for simplicity in this example.

#### Add Tailwind CSS via CDN

Update the `<head>` section of the layout file to include Tailwind CSS:

#### Updated `layout.ejs`

```c
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= title || 'Social Media App' %></title>
  <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 text-gray-800">
  <header class="bg-blue-600 text-white p-4">
    <div class="container mx-auto flex justify-between items-center">
      <h1 class="text-xl font-bold">Social Media App</h1>
      <nav>
        <a href="/profile" class="px-3 py-2 hover:bg-blue-500 rounded">Profile</a>
        <a href="/feed" class="px-3 py-2 hover:bg-blue-500 rounded">Feed</a>
        <a href="/notifications" class="px-3 py-2 hover:bg-blue-500 rounded">Notifications</a>
        <a href="/create-post" class="px-3 py-2 hover:bg-blue-500 rounded">Create Post</a>
      </nav>
    </div>
  </header>

  <main class="container mx-auto mt-6 p-4 bg-white rounded shadow">
    <%- body %>
  </main>

  <footer class="bg-blue-600 text-white p-4 mt-6">
    <div class="container mx-auto text-center">
      <p>&copy; 2025 Social Media App</p>
    </div>
  </footer>
</body>
</html>
```

---

### Step 2: Update Views with Tailwind CSS Classes

#### 1\. **Profile View (**`views/profile.ejs`)

```c

<h1 class="text-2xl font-bold mb-4">Welcome to <%= user.name %>'s Profile</h1>
<p class="mb-2"><strong>Bio:</strong> <%= user.bio %></p>
<p class="mb-2"><strong>Followers:</strong> <%= user.followers %></p>
<p class="mb-2"><strong>Following:</strong> <%= user.following %></p>

<h2 class="text-xl font-semibold mt-6">Posts:</h2>
<ul class="list-disc pl-6">
  <% user.posts.forEach(post => { %>
    <li class="mb-2"><%= post %></li>
  <% }); %>
</ul>
```

---

#### 2\. **Feed View (**`views/feed.ejs`)

```c

<h1 class="text-2xl font-bold mb-4">Posts Feed</h1>
<ul class="space-y-4">
  <% posts.forEach(post => { %>
    <li class="p-4 border rounded bg-gray-50 shadow">
      <strong class="text-blue-600"><%= post.username %></strong>: <%= post.content %>
    </li>
  <% }); %>
</ul>
```

---

#### 3\. **Notifications View (**`views/notifications.ejs`)

```c

<h1 class="text-2xl font-bold mb-4">Your Notifications</h1>
<% if (notifications.length > 0) { %>
  <ul class="space-y-4">
    <% notifications.forEach(notification => { %>
      <li class="p-4 border rounded bg-green-50 shadow">
        <%= notification.message %>
      </li>
    <% }); %>
  </ul>
<% } else { %>
  <p class="text-gray-500">No new notifications!</p>
<% } %>
```

---

#### 4\. **Create Post View (**`views/create-post.ejs`)

```c

<h1 class="text-2xl font-bold mb-4">Create a New Post</h1>
<form action="/create-post" method="POST" class="space-y-4">
  <textarea 
    name="content" 
    rows="4" 
    class="w-full p-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-400" 
    placeholder="Write your post..."
  ></textarea>
  <button 
    type="submit" 
    class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
  >
    Post
  </button>
</form>
```

---

### Step 3: Test the Application

1. **Profile Route**: `/profile`
    
    * Styled profile page with user details and a list of posts.
        
2. **Feed Route**: `/feed`
    
    * A clean, visually appealing list of posts.
        
3. **Notifications Route**: `/notifications`
    
    * Notifications with highlighted sections for messages.
        
4. **Create Post Route**: `/create-post`
    
    * A form styled with rounded edges and focus effects.
        

---

### **What is Middleware in Express.js?**

Middleware in Express.js refers to functions that execute during the lifecycle of an HTTP request to the server. These functions sit between the incoming request and the server's response, performing operations such as:

* Modifying the request and response objects.
    
* Logging information about the request.
    
* Authentication and authorization checks.
    
* Handling errors.
    
* Passing control to the next middleware in the stack.
    

Middleware is a **key feature** in Express.js, enabling modular and reusable code.

---

### **How Middleware Works**

Middleware functions have access to:

1. `req`: The request object.
    
2. `res`: The response object.
    
3. `next`: A function to pass control to the next middleware in the stack.
    

#### Basic Syntax

```c
app.use((req, res, next) => {
  console.log('Middleware executed');
  next(); // Pass control to the next middleware
});
```

If `next()` is not called, the request-response cycle ends, and no further middleware or routes are executed.

---

### **Built-in Middleware in Express.js**

Express.js includes several built-in middleware functions to handle common tasks.

#### 1\. `express.json`

* **Purpose**: Parses incoming requests with JSON payloads.
    
* **Usage**: Automatically populates `req.body` with parsed data.
    

Example:

```c
app.use(express.json());

app.post('/data', (req, res) => {
  console.log(req.body); // Access parsed JSON data
  res.send('Data received');
});
```

---

#### 2\. `express.urlencoded`

* **Purpose**: Parses incoming requests with URL-encoded payloads (e.g., from HTML forms).
    
* **Options**:
    
    * `extended: true`: Supports rich objects and arrays using the `qs` library.
        
    * `extended: false`: Uses the `querystring` library for simpler payloads.
        

Example:

```c
app.use(express.urlencoded({ extended: true }));

app.post('/form', (req, res) => {
  console.log(req.body); // Access form data
  res.send('Form data received');
});
```

---

#### 3\. `express.static`

* **Purpose**: Serves static files such as CSS, images, and JavaScript files.
    
* **Usage**: Configures a directory from which static files can be served.
    

Example:

```c
app.use(express.static('public'));
// Files in the 'public' folder will be accessible at the root of the app
```

---

#### 4\. `express.Router`

* **Purpose**: Used to create modular route handlers.
    
* **Benefit**: Enables grouping of related routes for better code organization.
    

Example:

```c
const router = express.Router();

router.get('/home', (req, res) => {
  res.send('Welcome to Home!');
});

app.use(router);
```

---

### **Why Use Built-in Middleware?**

1. **Ease of Use**: Simplifies common tasks like parsing JSON or handling form submissions.
    
2. **Efficiency**: Provides optimized and well-tested solutions for repetitive tasks.
    
3. **Modularity**: Separates concerns, allowing for cleaner code and better maintainability.
    

By leveraging built-in middleware, you can significantly enhance the functionality and structure of your Express.js applications without reinventing the wheel.

### **What is MongoDB?**

MongoDB is a **NoSQL database** designed for modern applications. Unlike traditional relational databases (like MySQL or PostgreSQL), which store data in tables with rows and columns, MongoDB stores data in a **document-oriented format**. These documents are organized in collections, and each document can have different fields and structures.

MongoDB is widely used for handling large amounts of unstructured data, such as JSON-like documents, and is known for its flexibility, scalability, and high performance.

### **Key Features of MongoDB:**

1. **Document-Oriented Storage**:
    
    * MongoDB stores data as documents in **BSON** (Binary JSON) format. These documents are similar to JSON objects, containing fields with values, which could be arrays, nested objects, or other types of data.
        
2. **Schema-less**:
    
    * Unlike relational databases, MongoDB is **schema-less**, meaning documents within the same collection do not need to have the same structure. This provides flexibility when storing diverse data types.
        
3. **Scalability**:
    
    * MongoDB is designed to scale horizontally, meaning it can handle large amounts of data by distributing it across multiple servers (sharding).
        
4. **High Availability**:
    
    * MongoDB supports **replication**, where data is duplicated across different servers (called replicas) to ensure high availability and fault tolerance.
        
5. **Indexing**:
    
    * MongoDB allows the creation of indexes to improve the performance of queries. You can index fields to make searches faster.
        
6. **Aggregation**:
    
    * MongoDB provides powerful aggregation features for data processing, including filters, projections, groupings, and sorting, similar to SQL queries but more flexible.
        

---

### **What is BSON?**

**BSON** (Binary JSON) is the binary format in which MongoDB stores documents. It is similar to JSON (JavaScript Object Notation) but with additional features, such as:

* **Binary format**: BSON is more efficient for storage and transmission than JSON because it's binary and can store data in a more compact form.
    
* **Extended data types**: BSON supports additional data types not present in JSON, like `ObjectId`, `Date`, `Binary`, and `Regular Expressions`, among others.
    
* **Rich Data Types**: BSON allows nested arrays and objects, making it a versatile format for complex data storage.
    

Example of BSON document:

```c
{
  "_id": ObjectId("60d0fe4f5311236168a109ca"),
  "name": "John Doe",
  "age": 30,
  "email": "john.doe@example.com",
  "address": {
    "street": "123 Main St",
    "city": "Somewhere",
    "postalCode": "12345"
  }
}
```

In this example, the `_id` field is automatically generated by MongoDB and is an instance of the `ObjectId` type, which is unique and used to identify documents in a collection.

---

### **MongoDB vs. Relational Databases**

| Feature | MongoDB (NoSQL) | Relational Databases (SQL) |
| --- | --- | --- |
| Data Storage Format | JSON-like documents (BSON) | Tables with rows and columns |
| Schema | Schema-less (flexible) | Fixed schema (predefined tables) |
| Scaling | Horizontal scaling (sharding) | Vertical scaling (adding more power to the server) |
| Query Language | MongoDB Query Language (MQL) | SQL (Structured Query Language) |
| ACID Transactions | Supported in replica sets and sharded clusters in later versions | ACID-compliant (Atomic, Consistent, Isolated, Durable) |
| Relationships Between Data | Not directly supported (though referenced via ObjectIds) | Strong relationships (JOINs) |

---

### **What is MongoDB Atlas?**

**MongoDB Atlas** is MongoDB’s fully-managed cloud database service. It provides a platform to host, manage, and scale MongoDB databases in the cloud without having to manage the infrastructure yourself. MongoDB Atlas offers several benefits:

#### **Key Features of MongoDB Atlas:**

1. **Managed Hosting**:
    
    * MongoDB Atlas handles the operational aspects of MongoDB, such as backups, monitoring, and scaling, freeing developers from the complexity of managing servers and databases.
        
2. **Global Clusters**:
    
    * MongoDB Atlas allows you to deploy your MongoDB database across multiple regions for better performance and availability.
        
3. **Automated Backups**:
    
    * MongoDB Atlas offers continuous backups and the ability to restore from any point in time, ensuring data safety.
        
4. **Security**:
    
    * Built-in encryption for data at rest and in transit, role-based access control (RBAC), IP whitelisting, and other features to ensure your database is secure.
        
5. **Scaling**:
    
    * MongoDB Atlas makes it easy to scale vertically or horizontally with a few clicks, adjusting to the demands of your application.
        
6. **Fully Managed**:
    
    * MongoDB Atlas automates database operations such as patching, upgrading, and monitoring, making it easier for developers to focus on building applications.
        
7. **Integration with Other Tools**:
    
    * Atlas can integrate with a wide range of services like AWS, Google Cloud, and Microsoft Azure for seamless cloud deployment.
        

#### **Example Use Case**:

* You can sign up for a free tier in MongoDB Atlas that offers a limited, free version of the database with 512MB storage for lightweight applications and development work.
    

---

### **Creating a Mongoose Object with Attributes (Schema Definition)**

In Mongoose, you define an object structure using **schemas**. Each schema defines the structure of documents in a collection (similar to defining a table in SQL) and includes the attributes (fields) that the documents will have, along with validation, types, default values, and more.

Here’s a detailed explanation of how to create a Mongoose object with various attributes and options like `min`, `max`, `timestamps`, `type`, and others:

---

### **1\. Setting up the Mongoose Schema**

First, you need to install Mongoose in your project if you haven't already:

```c
npm install mongoose
```

Then, let's create a **User schema** for a social media application, where we can define attributes such as `username`, `email`, `password`, `profilePicture`, etc.

#### **Connecting to the Database**

To interact with MongoDB, you need to establish a connection using **Mongoose**. Below is a function (`dbconnect`) that connects to MongoDB using the Mongoose library and a **MongoDB Atlas** connection string.

```c
// db.js
const mongoose = require('mongoose');

const dbconnect = async () => {
  try {
    // Connect to MongoDB Atlas
    await mongoose.connect(
      'mongodb+srv://adhikaribisesh1590:biratnagar123@cluster0.fyhsv.mongodb.net/socialMediaApp', 
      {
        useNewUrlParser: true, 
        useUnifiedTopology: true,
      }
    );
    console.log('DB Connected');
  } catch (err) {
    console.log('Error connecting to database:', err);
  }
};

module.exports = dbconnect;
```

### **Explanation**:

* `mongoose.connect()`: This method connects your Node.js app to a MongoDB database. Here, we’re using MongoDB Atlas (a cloud-hosted MongoDB service) with a connection string that includes the username, password, and database name (`socialMediaApp`).
    
* **useNewUrlParser**: This option ensures compatibility with the latest MongoDB URI connection string format.
    
* **useUnifiedTopology**: This option provides better handling of MongoDB’s internal topology and server discovery.
    

---

### **2\. Mongoose Schema Creation Example**

```c
// models/User.js
const mongoose = require('mongoose');

// Define the schema for the User model
const userSchema = new mongoose.Schema(
  {
    username: {
      type: String, // Attribute type is String
      required: true, // This field is mandatory
      unique: true, // Ensures no duplicate usernames
      minlength: [3, 'Username should be at least 3 characters'], // Minimum length constraint
      maxlength: [30, 'Username should not exceed 30 characters'], // Maximum length constraint
    },
    email: {
      type: String,
      required: true,
      unique: true,
      lowercase: true, // Store email in lowercase to avoid case sensitivity
      match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email address'], // Regex for email validation
    },
    password: {
      type: String,
      required: true, // Password is mandatory
      minlength: [6, 'Password should be at least 6 characters long'], // Minimum password length
    },
    profilePicture: {
      type: String,
      default: 'https://example.com/default-profile-picture.png', // Default profile picture URL
    },
    bio: {
      type: String,
      maxlength: [160, 'Bio cannot exceed 160 characters'], // Maximum bio length
    },
    followersCount: {
      type: Number,
      default: 0, // Default value for followers count
    },
    followingCount: {
      type: Number,
      default: 0, // Default value for following count
    },
    createdAt: {
      type: Date,
      default: Date.now, // Default to current date and time
    },
    updatedAt: {
      type: Date,
      default: Date.now, // Default to current date and time
    },
  },
  {
    timestamps: true, // Automatically adds `createdAt` and `updatedAt` fields
  }
);

// Create the Mongoose model
const User = mongoose.model('User', userSchema);

module.exports = User;
```

---

### **Explanation of the Schema Fields:**

* `username`: A string field for the user’s username.
    
    * `required`: This makes sure the field is mandatory.
        
    * `unique`: Ensures that no two users can have the same username.
        
    * `minlength`: Specifies the minimum length of the username.
        
    * `maxlength`: Specifies the maximum length of the username.
        
* `email`: A string field for the user's email address.
    
    * `required`: This ensures that the field must have a value.
        
    * `unique`: Ensures that emails are unique.
        
    * `lowercase`: Converts the email value to lowercase automatically when saving.
        
    * `match`: Validates the email format using a regular expression.
        
* `password`: A string field for the user's password.
    
    * `required`: This ensures that the password is mandatory.
        
    * `minlength`: Ensures that the password has at least 6 characters.
        
* `profilePicture`: A string field that holds the URL of the user's profile picture.
    
    * `default`: This provides a default profile picture URL if none is provided.
        
* `bio`: A string field for the user’s bio.
    
    * `maxlength`: This limits the length of the bio to 160 characters.
        
* `followersCount` and `followingCount`: Number fields that track the number of followers and the number of users the current user is following.
    
    * `default`: Both fields are initialized to 0 when the user is created.
        
* `createdAt` and `updatedAt`: Date fields that store when the user was created and last updated.
    
    * `default`: Both are set to the current date and time by default.
        

---

### **3\. Timestamps**

In Mongoose, the `timestamps` option automatically adds two fields to your schema: `createdAt` and `updatedAt`. These fields are automatically managed by Mongoose and will be updated when you create or modify documents.

For example, when you create a user, `createdAt` is automatically set to the current time, and `updatedAt` is the same. When the user document is updated later, Mongoose updates the `updatedAt` field.

---

### **4.** `min` and `max` Validation

You can use `min` and `max` to set boundaries for numbers, like age or price, or even length constraints on strings, as shown above with `username` and `bio`.

Here’s an example of how to use `min` and `max` on a number:

```c
age: {
  type: Number,
  min: [18, 'Age must be at least 18'], // Minimum value constraint
  max: [120, 'Age cannot exceed 120'], // Maximum value constraint
}
```

This ensures that a user’s age is between 18 and 120.

---

### **5\. Adding a Mongoose Model**

Once you define the schema, you create a **model** using `mongoose.model()`. The model represents the **User** collection in the MongoDB database and provides methods to interact with that collection (e.g., `save()`, `find()`, `update()`, etc.).

### **6\. Example of Creating a New Mongoose Document (User)**

Here’s how to create a new user document with the attributes defined in the schema:

```c
// Create a new user instance
const newUser = new User({
  username: 'john_doe',
  email: 'john@example.com',
  password: 'securePassword123',
  profilePicture: 'https://example.com/profile.jpg',
  bio: 'Hello, I am John, a software developer!',
});

// Save the user to the database
newUser.save()
  .then(() => {
    console.log('User saved successfully!');
  })
  .catch((err) => {
    console.log('Error saving user:', err);
  });
```

### **Explanation**:

* `new User({...})`: Creates a new document with the data you provided.
    
* [`newUser.save`](http://newUser.save)`()`: Saves the new user document to the MongoDB database.
    

---

### **7\. Summary of Schema Options**

* `type`: Specifies the data type for the field (e.g., `String`, `Number`, `Date`, `Boolean`).
    
* `required`: Ensures that a field must be provided before saving.
    
* `unique`: Ensures that no two documents can have the same value for this field.
    
* `default`: Provides a default value if the field is not explicitly set.
    
* `min`, `max`: Used for validating number ranges or string lengths.
    
* `timestamps`: Automatically adds `createdAt` and `updatedAt` fields to the document.
    
* `match`: Validates the field’s value against a regular expression.
    

---

### **Conclusion**

In Mongoose, the schema allows you to define the structure and behavior of the documents in a collection, including specifying field types, validation rules, default values, and more. Using **timestamps**, **min/max**, and other built-in attributes, you can ensure your data follows business rules and maintain data integrity.

This approach provides an organized and flexible way to work with MongoDB in your Node.js applications, especially for projects like social media apps where user data, content, and interactions need to be structured and validated properly.
