Node js - day4
Student | Tech enthusiast | Aspiring software developer
Setting Up the Project
Initialize the project:
mkdir social-media-app cd social-media-app npm init -y npm install express ejs body-parserBasic Server Setup (
index.js):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
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)
<!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
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)
<!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
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)
<!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
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)
<!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:
npm install express-ejs-layouts
Update Server Code
const expressLayouts = require('express-ejs-layouts');
app.use(expressLayouts);
app.set('layout', 'layout');
Create Layout File (views/layout.ejs)
<!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>© 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
<!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>© 2025 Social Media App</p>
</div>
</footer>
</body>
</html>
Step 2: Update Views with Tailwind CSS Classes
1. Profile View (views/profile.ejs)
<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)
<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)
<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)
<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
Profile Route:
/profile- Styled profile page with user details and a list of posts.
Feed Route:
/feed- A clean, visually appealing list of posts.
Notifications Route:
/notifications- Notifications with highlighted sections for messages.
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:
req: The request object.res: The response object.next: A function to pass control to the next middleware in the stack.
Basic Syntax
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.bodywith parsed data.
Example:
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 theqslibrary.extended: false: Uses thequerystringlibrary for simpler payloads.
Example:
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:
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:
const router = express.Router();
router.get('/home', (req, res) => {
res.send('Welcome to Home!');
});
app.use(router);
Why Use Built-in Middleware?
Ease of Use: Simplifies common tasks like parsing JSON or handling form submissions.
Efficiency: Provides optimized and well-tested solutions for repetitive tasks.
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:
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.
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.
Scalability:
- MongoDB is designed to scale horizontally, meaning it can handle large amounts of data by distributing it across multiple servers (sharding).
High Availability:
- MongoDB supports replication, where data is duplicated across different servers (called replicas) to ensure high availability and fault tolerance.
Indexing:
- MongoDB allows the creation of indexes to improve the performance of queries. You can index fields to make searches faster.
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, andRegular 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:
{
"_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:
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.
Global Clusters:
- MongoDB Atlas allows you to deploy your MongoDB database across multiple regions for better performance and availability.
Automated Backups:
- MongoDB Atlas offers continuous backups and the ability to restore from any point in time, ensuring data safety.
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.
Scaling:
- MongoDB Atlas makes it easy to scale vertically or horizontally with a few clicks, adjusting to the demands of your application.
Fully Managed:
- MongoDB Atlas automates database operations such as patching, upgrading, and monitoring, making it easier for developers to focus on building applications.
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:
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.
// 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
// 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.
followersCountandfollowingCount: 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.
createdAtandupdatedAt: 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:
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:
// 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(): 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 addscreatedAtandupdatedAtfields 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.

