Building a Newsletter Subscription System with Node.js and MongoDB
Tags: webdev, javascript, beginners, programming
Published: 2024-01-17 07:15:00

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:

```env
MONGODB_USER=your_username
MONGODB_PASSWORD=your_password
```

## Running the Application

Clone the repository:

   ```bash
   git clone https://github.com/manthanank/newsletter-nodejs.git
   ```

Install dependencies:

   ```bash
   cd newsletter-nodejs
   npm install
   ```

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

   ```env
   EMAIL_USER=your_email@gmail.com
   EMAIL_PASSWORD=your_email_password
   ```

Start the application:

   ```bash
   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`, 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).

```jsx
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.

- **Newsletter Function:**
  - Export a function `sendNewsletter` that takes `email`, `subject`, and `content` as parameters.
  - Configure email options and send the newsletter using the transporter.

```jsx
// 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'.

```jsx
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`.

```jsx
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 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.

```jsx
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`, and `dotenv`.
  
- **Scripts:**
  - Provides scripts for running tests (`npm test`) and starting the server (`npm start`).

```jsx
{
  "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

```jsx
# .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](https://github.com/manthanank/newsletter-nodejs) 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.