Open In App

Create Newsletter Subscription in Blogs And News Website

Last Updated : 28 Aug, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Adding a newsletter subscription feature to your blogs and news websites can enhance user engagement and keep your audience updated with the latest content. This article will guide you through implementing a newsletter subscription system, covering both backend and frontend aspects.

Why Offer a Newsletter Subscription?

A newsletter subscription feature allows you to build a direct line of communication with your readers. Here are a few key benefits:

  • Direct Engagement: Reach your audience directly in their inbox with the latest updates, articles, and exclusive content.
  • Increased Traffic: Regular newsletters can drive more traffic to your website, as subscribers are likely to revisit new content.
  • Personalization: Design content to subscriber preferences and behaviours, enhancing the user experience.

Approach to Implement Newsletter Subscription

Backend:

  • Create a Mongoose model (NewsletterSubscriber) to store subscriber emails with validation.
  • Implement a controller function (subscribeToNewsletter) to handle subscription logic, checking for duplicates, and saving new subscribers.
  • Define a route (/subscribe) in your Express app to handle POST requests for subscriptions.
  • Connect the route to the controller in your Express router to process subscription requests and respond accordingly.

Frontend:

  • Create State Variables: Manage email, open, message, and severity using React's useState.
  • Handle Subscription: Send a POST request with axios to your API endpoint on button click, and update the UI based on the response.
  • Display Feedback: Use Snackbar and Alert from MUI to show success or error messages.
  • Render Component: Provide an email input field, a subscribe button, and feedback for user interaction.

Backend Code:

JavaScript
// index.js

const express = require("express");
const mongoose = require("mongoose");
const authorRoutes = require("./routes/author");
const articleRoutes = require("./routes/article");
const newsletterRoutes = require("./routes/newsletter");
const cors = require("cors");
const connectDB = require("./db/conn");

connectDB();
const app = express();
app.use(express.json());
app.use(cors({
    origin : [ "http://localhost:5173" ],
    methods : [
        "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"
    ],
    credentials : true
}));

mongoose
    .connect("mongodb://localhost:27017/blog",
             {

             })
    .then(() => console.log("MongoDB connected"))
    .catch(err => console.log(err));
app.use("/api/articles", articleRoutes);
app.use("/api/authors", authorRoutes);
app.use("/api/newsletter", newsletterRoutes);
app.listen(
    5000,
    () => { console.log("Server running on port 5000"); });
JavaScript
// models/NewsletterSubscriber.js
const mongoose = require("mongoose");

const newsletterSchema = new mongoose.Schema({
    email : {
        type : String,
        required : true,
        unique : true,
        lowercase : true,
        trim : true
    },
    subscribedAt : {type : Date, default : Date.now}
});

const NewsletterSubscriber = mongoose.model(
    "NewsletterSubscriber", newsletterSchema);

module.exports = NewsletterSubscriber;
JavaScript
// controllers/newsletterController.js
const NewsletterSubscriber
    = require("../models/NewsletterSubscriber");

// Controller to handle subscribing to the newsletter
const subscribeToNewsletter = async (req, res) => {
    const {email} = req.body;

    if (!email) {
        return res.status(400).json(
            {message : "Email is required"});
    }

    try {
        // Check if the email is already subscribed
        const existingSubscriber
            = await NewsletterSubscriber.findOne({email});
        if (existingSubscriber) {
            return res.status(400).json(
                {message : "Email is already subscribed"});
        }

        // Create a new subscriber
        const newSubscriber
            = new NewsletterSubscriber({email});
        await newSubscriber.save();

        res.status(200).json(
            {message : "Subscription successful"});
    }
    catch (error) {
        console.error("Error subscribing to newsletter:",
                      error);
        res.status(500).json(
            {message : "Internal Server Error"});
    }
};

module.exports = {subscribeToNewsletter};
JavaScript
// routes/newsletter.js
const express = require("express");
const router = express.Router();
const {subscribeToNewsletter} = require("../controllers/NewsletterSubscriber");

// Route to subscribe to the newsletter
router.post("/subscribe", subscribeToNewsletter);

module.exports = router;

Start your server using the below command:

node index.js

Frontend Code:

JavaScript
// src/components/NewsletterSubscription.js
import React, { useState } from 'react';
import { Button, TextField, Typography, Box, Snackbar } from '@mui/material';
import { Alert } from '@mui/material';
import axios from 'axios';

const NewsletterSubscription = () => {
    const [email, setEmail] = useState('');
    const [open, setOpen] = useState(false);
    const [message, setMessage] = useState('');
    const [severity, setSeverity] = useState('success');

    const handleSubscribe = async () => {
        try {
            const response = await axios.post
            ('http://localhost:5000/api/newsletter/subscribe', { email });
            setMessage(response.data.message);
            setSeverity('success');
        } catch (error) {
            setMessage(error.response?.data?.message || 'Something went wrong');
            setSeverity('error');
        }
        setOpen(true);
    };

    return (
        <Box sx={{ mt: 4 }}>
            <Typography variant="h5" gutterBottom>
                Subscribe to Our Newsletter
            </Typography>
            <TextField
                fullWidth
                label="Email Address"
                variant="outlined"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                sx={{ mb: 2 }}
            />
            <Button variant="contained" color="primary" onClick={handleSubscribe}>
                Subscribe
            </Button>
            <Snackbar
                open={open}
                autoHideDuration={6000}
                onClose={() => setOpen(false)}
            >
                <Alert onClose={() => setOpen(false)} severity={severity}>
                    {message}
                </Alert>
            </Snackbar>
        </Box>
    );
};

export default NewsletterSubscription;
JavaScript
// src/pages/ArticleDetail.jsx

import React, { useState, useEffect } from "react";
import axios from "axios";
import { useParams, Link, useLocation } from "react-router-dom";
import {
    Container,
    Typography,
    Box,
    Button,
    Divider,
    Chip,
    TextField,
    IconButton,
    List,
    ListItem,
    ListItemText,
    ListItemSecondaryAction,
    ListItemAvatar,
    Avatar,
} from "@mui/material";
import DeleteIcon from '@mui/icons-material/Delete';
import NewsletterSubscription from "../components/NewsletterSubscription";

const ArticleDetail = () => {
    const { id } = useParams();
    const { search } = useLocation();
    const authorId = new URLSearchParams(search).get("authorId");  
    const [article, setArticle] = useState(null);
    const [newComment, setNewComment] = useState("");
    const [comments, setComments] = useState([]);

    useEffect(() => {
        const fetchArticle = async () => {
            try {
                const response = await axios.get(
                    `http://localhost:5000/api/articles/${id}`,
                    {
                        withCredentials: true,
                    }
                );
                console.log("ssss", response.data.author.name);
                setArticle(response.data);
                setComments(response.data.comments || []);
            } catch (error) {
                console.error("Error fetching article:", error);
            }
        };

        fetchArticle();
    }, [id]);

    const handleAddComment = async () => {
        try {
            await axios.post(
                `http://localhost:5000/api/articles/${id}/comments`,
                { content: newComment, author: authorId },
                { withCredentials: true }
            );
            // Re-fetch the article to get the updated comments with author info
            const updatedArticle = await axios.get(
                `http://localhost:5000/api/articles/${id}`,
                {
                    withCredentials: true,
                }
            );
            console.log(updatedArticle.data);
            setComments(updatedArticle.data.comments || []);
            setNewComment("");
        } catch (error) {
            console.error("Error adding comment:", error);
        }
    };

    const handleDeleteComment = async (commentId) => {
        try {
            await axios.delete(
                `http://localhost:5000/api/articles/${id}/comments/${commentId}`,
                { withCredentials: true }
            );
            setComments((prevComments) =>
                prevComments.filter((comment) => comment._id !== commentId)
            );
        } catch (error) {
            console.error("Error deleting comment:", error);
        }
    };

    if (!article) return <Typography>Loading...</Typography>;

    return (
        <Container maxWidth="md">
            <Typography variant="h3" gutterBottom>
                {article.title}
            </Typography>
            <Typography variant="subtitle1" color="textSecondary" gutterBottom>
                By {article.author.name} on{" "}
                {new Date(article.createdAt).toLocaleDateString()}
            </Typography>
            <Divider sx={{ my: 2 }} />
            <Typography variant="body1" paragraph>
                {article.content}
            </Typography>
            <Box sx={{ mt: 2 }}>
                <Typography variant="h6" gutterBottom>
                    Categories
                </Typography>
                <Box sx={{ mb: 2 }}>
                    {article.categories.map((category, index) => (
                        <Chip key={index} label={category} sx={{ mr: 1, mb: 1 }} />
                    ))}
                </Box>
                <Typography variant="h6" gutterBottom>
                    Tags
                </Typography>
                <Box>
                    {article.tags.map((tag, index) => (
                        <Chip key={index} label={tag} sx={{ mr: 1, mb: 1 }} />
                    ))}
                </Box>
            </Box>
            <Box sx={{ mt: 2 }}>
                <Typography variant="h6" gutterBottom>
                    Comments
                </Typography>
                <List>
                    {comments.map((comment) => (
                        <ListItem key={comment._id}>
                            <ListItemAvatar>
                                <Avatar>
                                    {article.author.name[0]} 
                                </Avatar>
                            </ListItemAvatar>
                            <ListItemText
                                primary={comment.content}
                                secondary={`By ${article.author.name} 
                                on ${new Date(comment.date).toLocaleDateString()}`}
                            />
                            <ListItemSecondaryAction>
                                <IconButton
                                    edge="end"
                                    aria-label="delete"
                                    onClick={() => handleDeleteComment(comment._id)}
                                >
                                    <DeleteIcon />
                                </IconButton>
                            </ListItemSecondaryAction>
                        </ListItem>
                    ))}
                </List>
                <Box sx={{ display: "flex", mt: 2 }}>
                    <TextField
                        fullWidth
                        label="Add a comment"
                        variant="outlined"
                        value={newComment}
                        onChange={(e) => setNewComment(e.target.value)}
                    />
                    <Button
                        variant="contained"
                        color="primary"
                        sx={{ ml: 2 }}
                        onClick={handleAddComment}
                    >
                        Post
                    </Button>
                </Box>
            </Box>
            <Box sx={{ mt: 2 }}>
                <Button
                    variant="contained"
                    color="primary"
                    component={Link}
                    to={`/update-article/${article._id}`}
                >
                    Edit Article
                </Button>
            </Box>
            <NewsletterSubscription/>
        </Container>
    );
};

export default ArticleDetail;

Start your frontend using the below command:

npm start or npm run dev

Output:


Similar Reads