Building a Newsletter Subscription System with Node.js and MongoDB

Manthan Ankolekar - Jan 17 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

Running the Application

Clone the repository:

   git clone https://github.com/manthanank/newsletter-nodejs.git
Enter fullscreen mode Exit fullscreen mode

Install dependencies:

   cd newsletter-nodejs
   npm install
Enter fullscreen mode Exit fullscreen mode

Create a .env file in the project root and add the following:

   EMAIL_USER=your_email@gmail.com
   EMAIL_PASSWORD=your_email_password
Enter fullscreen mode Exit fullscreen mode

Start the application:

   npm start
Enter fullscreen mode Exit fullscreen mode

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, and mongoose.
    • Load environment variables using dotenv.
    • Create an Express app (app).
  • 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}`);
});
Enter fullscreen mode Exit fullscreen mode

2. utils/newsletter.js:

  • Nodemailer Setup:

    • Import the nodemailer module.
    • Create a transporter using Gmail SMTP and provided email credentials.
  • Newsletter Function:

    • Export a function sendNewsletter that takes email, subject, and content as parameters.
    • Configure email options and send the newsletter using the transporter.
// 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 };
Enter fullscreen mode Exit fullscreen mode

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'.
const express = require('express');
const router = express.Router();
const subscribersController = require('../controllers/subscribers');

router.post('/subscribe', subscribersController.createSubscribers);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

4. models/subscribers.js:

  • MongoDB Schema:
    • Define a MongoDB schema for subscribers with a single field: email.
const mongoose = require('mongoose');

const Subscribers = mongoose.model('subscribers', {
    email: { type: String }
});

module.exports = Subscribers;
Enter fullscreen mode Exit fullscreen mode

5. controllers/subscribers.js:

  • Controller Functions:
    • Import the Subscribers model and the sendNewsletter 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.
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);
    }
};
Enter fullscreen mode Exit fullscreen mode

6. package.json:

  • Dependencies:

    • Lists the project dependencies, including express, body-parser, cors, mongoose, nodemailer, and dotenv.
  • Scripts:

    • Provides scripts for running tests (npm test) and starting the server (npm start).
{
  "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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .