In this blog post, we'll guide you through the process of creating a simple newsletter subscription system using Node.js, Express, MongoDB, and Nodemailer. This system allows users to subscribe to a newsletter and receive a welcome email.
Prerequisites
Before getting started, make sure you have the following installed:
- Node.js
- MongoDB
- Nodemailer
Project Structure
Let's first take a look at the project structure:
index.js: The main entry point for the application. It sets up the Express server, connects to the MongoDB database, and defines routes.
utils/newsletter.js: Contains a function for sending newsletters using Nodemailer.
routes/subscribers.js: Defines the API routes related to newsletter subscriptions.
models/subscribers.js: Defines the MongoDB schema for storing subscriber emails.
controllers/subscribers.js: Handles the logic for creating new newsletter subscriptions.
package.json: Includes project metadata and dependencies.
.env: File with placeholder values for sensitive information.
Setting up MongoDB
Ensure you have a MongoDB cluster set up. The connection string is stored in the .env
file, following the format:
MONGODB_USER=your_username
MONGODB_PASSWORD=your_password
Running the Application
Clone the repository:
git clone https://github.com/manthanank/newsletter-nodejs.git
Install dependencies:
cd newsletter-nodejs
npm install
Create a .env
file in the project root and add the following:
EMAIL_USER=your_email@gmail.com
EMAIL_PASSWORD=your_email_password
Start the application:
npm start
The server will be running at http://localhost:3000
.
Certainly! Let's go through the code step by step to understand its functionality.
1. index.js:
-
Express Setup:
- Import necessary modules like
express
,body-parser
,cors
, andmongoose
. - Load environment variables using
dotenv
. - Create an Express app (
app
).
- Import necessary modules like
-
Database Connection:
- Connect to MongoDB using the connection string from the environment variables.
-
Middleware:
- Use CORS middleware to handle cross-origin resource sharing.
- Use
body-parser
to parse incoming request bodies. - Serve static files from the 'public' directory.
- Parse incoming JSON requests.
-
Routes:
- Define a route to serve the HTML file at the root path.
- Use the "/api" route prefix for subscriber-related routes.
-
Server Start:
- Start the server on the specified port (default is 3000).
const express = require('express');
const bodyParser = require('body-parser');
const cors = require("cors");
const app = express();
const mongoose = require('mongoose');
require("dotenv").config();
const dbUser = process.env.MONGODB_USER;
const dbPassword = process.env.MONGODB_PASSWORD;
mongoose
.connect(
`mongodb+srv://${dbUser}:${dbPassword}@cluster0.re3ha3x.mongodb.net/subscribers`
)
.then(() => {
console.log("Connected to MongoDB database!");
})
.catch(() => {
console.log("Connection failed!");
});
app.use(
cors({
origin: "*",
})
);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.use(express.json());
app.get('/', (req, res) => {
res.sendFile(__dirname + '/public/index.html');
});
app.use("/api", require("./routes/subscribers"));
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
2. utils/newsletter.js:
-
Nodemailer Setup:
- Import the
nodemailer
module. - Create a
transporter
using Gmail SMTP and provided email credentials.
- Import the
-
Newsletter Function:
- Export a function
sendNewsletter
that takesemail
,subject
, andcontent
as parameters. - Configure email options and send the newsletter using the transporter.
- Export a function
// src/newsletter.js
const nodemailer = require('nodemailer');
require('dotenv').config();
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
});
const sendNewsletter = (email, subject, content) => {
const mailOptions = {
from: process.env.EMAIL_USER,
to: email,
subject: subject,
html: content,
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.error(error);
} else {
console.log('Newsletter sent: ' + info.response);
}
});
};
module.exports = { sendNewsletter };
3. routes/subscribers.js:
-
Express Router:
- Create an Express router for handling subscriber-related routes.
-
POST /subscribe:
- Import the
subscribersController
. - Define a route that calls the
createSubscribers
function when a POST request is made to '/subscribe'.
- Import the
const express = require('express');
const router = express.Router();
const subscribersController = require('../controllers/subscribers');
router.post('/subscribe', subscribersController.createSubscribers);
module.exports = router;
4. models/subscribers.js:
-
MongoDB Schema:
- Define a MongoDB schema for subscribers with a single field:
email
.
- Define a MongoDB schema for subscribers with a single field:
const mongoose = require('mongoose');
const Subscribers = mongoose.model('subscribers', {
email: { type: String }
});
module.exports = Subscribers;
5. controllers/subscribers.js:
-
Controller Functions:
- Import the
Subscribers
model and thesendNewsletter
function. - Define an asynchronous function
createSubscribers
to handle new subscriber creation. - Check if the provided email already exists in the database.
- If not, save the new subscriber to the database and send a welcome newsletter.
- Handle errors and respond accordingly.
- Import the
const Subscribers = require('../models/subscribers.js');
const { sendNewsletter } = require('../utils/newsletter.js');
exports.createSubscribers = async (req, res, next) => {
// console.log(req.body);
const email = req.body.email;
try {
// Check if the email already exists in the database
const existingSubscriber = await Subscribers.findOne({ email });
if (existingSubscriber) {
// Email already exists, send a message
return res.send('Email already subscribed. Check your email for the welcome newsletter.');
}
// Email doesn't exist, save to the database
const newSubscriber = new Subscribers({ email });
const savedSubscriber = await newSubscriber.save();
// console.log('Subscription saved to the database:', savedSubscriber);
// For simplicity, let's just print it to the console
console.log(`New subscription: ${email}`);
// Send a welcome newsletter
const welcomeSubject = 'Welcome to Our Newsletter!';
const welcomeContent = '<p>Thank you for subscribing to our newsletter!</p>';
sendNewsletter(email, welcomeSubject, welcomeContent);
res.send('Subscription successful! Check your email for a welcome newsletter.');
} catch (error) {
// Handle database or other errors
console.error('Error creating subscription:', error);
next(error);
}
};
6. package.json:
-
Dependencies:
- Lists the project dependencies, including
express
,body-parser
,cors
,mongoose
,nodemailer
, anddotenv
.
- Lists the project dependencies, including
-
Scripts:
- Provides scripts for running tests (
npm test
) and starting the server (npm start
).
- Provides scripts for running tests (
{
"name": "newsletter-nodejs",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"mongoose": "^8.0.4",
"nodemailer": "^6.9.8",
"nodemon": "^3.0.2"
}
}
7. .env:
Before running this application, ensure you have the necessary environment variables set in a .env file
# .env
# Gmail credentials for sending newsletters
EMAIL_USER="your_email@gmail.com"
EMAIL_PASSWORD="your_email_password"
# Port for the Node.js server (default is 3000)
PORT=3000
# MongoDB credentials for database connection
MONGODB_USER="username"
MONGODB_PASSWORD="password"
Exploring the Code
Visit the GitHub repository to explore the code in detail.
Conclusion
Congratulations! You've successfully set up a basic newsletter subscription system using Node.js, Express, MongoDB, and Nodemailer. Feel free to customize and expand upon this foundation to meet your specific requirements.
Feel free to customize the blog according to your preferences and provide more details or explanations where needed.