javascriptnode.jsmongodbmongooseejs

EJS Rendering Error: "allChats is not defined" Despite Data in MongoDB


I'm working on a messaging application using Node.js, Express, MongoDB, and EJS. The goal is to retrieve chat messages from MongoDB and render them using EJS. However, I'm encountering an issue where allChats is reported as undefined in my EJS template, even though there is data in the MongoDB collection. Below are the relevant parts of my code:

Controller (controllers/chat.js):

const Message = require("../models/chat.js");
const User = require("../models/user.js");
const axios = require("axios");
require("dotenv").config();
const accessToken = process.env.ACCESS_TOKEN; // Add your access token here
const myToken = process.env.VERIFY_TOKEN;

module.exports.getChats = async (req, res) => {
  try {
    const allChats = await Message.find({}).sort({ timestamp: 1 });
    res.json(allChats);
    res.render("./chats/showMessages.ejs", { allChats });
  } catch (error) {
    console.error("Error retrieving chats:", error);
    res.sendStatus(500);
  }
};

Routes (routes/chat.js):

const express = require("express");
const router = express.Router();
const chatController = require("../controllers/chat.js");

router.get("/chats", chatController.getChats);
router.post("/send", chatController.sendMessage);
router.post("/webhook", chatController.webhook);
router.get("/webhook", chatController.verifyWebhook);

module.exports = router;

EJS Template (views/showMessages.ejs):

<% layout("./layouts/boilerplate") %>
<div class="row conatiner p-2">
  <div class="sidebar col-4 vh-100">
    <form class="d-flex" role="search">
      <input
        class="form-control me-2"
        type="search"
        placeholder="Search"
        aria-label="Search"
      />
      <button class="btn btn-outline-success" type="submit">Search</button>
    </form>
    <% for(let allChat of allChats) { %>
    <div class="chatItem d-flex align-items-center">
      <i class="fa-solid fa-user usrIcon mb-2 me-3"></i>
      <div class="chatDetails mb-2 flex-grow-1">
        <div class="chatHeader d-flex justify-content-between">
          <span><%= allChat.from%></span>
          <span>12:00</span>
        </div>
        <div class="chatMsg">
          A snippet of a recent message sent by an user will show here...
        </div>
      </div>
    </div>
    <% } %>
  </div>
  <div class="chat-area col-8 vh-100">
    <p>Chat messages will show here</p>
  </div>
</div>

Model (models/chat.js):

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const chatSchema = new Schema(
  {
    from: {
      type: Number,
    },
    to: {
      type: Number,
    },
    message: {
      type: String,
      required: true, // Adding required to ensure messages are stored correctly
    },
    contact: {
      type: Schema.Types.ObjectId,
      ref: "User",
    },
    msg_is: String,
  },
  { timestamps: true }
); // This will automatically add `created_at` and `updated_at` fields

const Chat = mongoose.model("Chat", chatSchema);
module.exports = Chat;

Model (models/user.js):

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const userSchema = new Schema({
  waId: { type: Number, unique: true },
  name: String,
});

module.exports = mongoose.model("User", userSchema);

App Initialization (app.js):

const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const chatRoutes = require("./routes/chat.js");
const path = require("path");
const ejsMate = require("ejs-mate");
require("dotenv").config();

const app = express();
const port = process.env.PORT || 3000;
app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));
app.engine("ejs", ejsMate);
app.use(express.static(path.join(__dirname, "./public")));

// Connect to MongoDB
mongoose
  .connect(process.env.MONGODB_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => {
    console.log("Connected to MongoDB");
  })
  .catch((error) => {
    console.error("Error connecting to MongoDB:", error);
  });
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get("/", (req, res) => {
  res.render("./chats/showMessages.ejs");
});

app.get("/chats/new", (req, res) => {
  res.render("./chats/sendMessage.ejs");
});

app.use("/api", chatRoutes);
//working
app.listen(port, "0.0.0.0", () => {
  console.log(`Server is listening on port ${port}`);
});

Problem

When I navigate to the root URL /, which should render the showMessage.ejs template with allChats, I get an error saying allChats is not defined. Despite having data in my MongoDB, it seems allChats is not being passed correctly to the EJS template.

What I've Tried

  1. Logging Data: Added console logs to check if allChats is populated (it shows the correct data).
  2. Ensured MongoDB Data: Verified that the Message collection has documents.
  3. Correct Routing: Ensured the routes are set up correctly to call getChats.

Expected Outcome

I expect to see a list of chat messages rendered in the showMessage.ejs template.

Actual Outcome

The server throws an error stating allChats is not defined when rendering the EJS template.

Request

Can someone help me identify what might be going wrong? How can I ensure allChats is correctly passed to the EJS template for rendering?


Solution

  • The problem is you're using the same template on / and /chats route, but on former you're not passing allChats, hence the error.

    A quick-and-dirty fix would be to check if the variable is defined in the template, like so (a better one would be to reorganize your code and use different templates, change logic etc.):

    <% if(typeof allChats !== "undefined") { %>
        
        <% for(let allChat of allChats) { %>
        ...
        <% } %>
    
    <% } %>
    

    Next, remove res.json(allChats); in chat router, because you can end the response only once, res.render... on the next line will throw headers already sent error.