Scoping and asynchronous function
Direction

With latest version of Ecmascript we get async/await that allow us writing asynchronous code in synchronous style.

Consider an example where function f(...) returns you Promise&lt;F&gt;, and g(...) returns Promise&lt;G&gt;. We can use the following constructions then:

try {
  const resultF = await f();
  const resultG = await g(resultF);
  // keep working with as if we were writing "regular" synchronous code
} catch (anyError) {
  console.error(anyError);
}


This code has to be declared in an async function or arrow function to work. Otherwise, it's still a perfectly asynchronous code...



Sample

In your case, the transformed solution could look similar to this. Please understand this is not 100% working code, because I am not familiar with the particular API of ClassSection that your code deal with. This is more of a sketch to show the direction.

router.post('/teacher-schedule', async (req, res) =&gt; {
  const records = [];

  try {
    const schedules = await Schedule.find({school_id : req.body.schoolId});
    schedules.forEach(schedule =&gt; {
      ClassSection.findOne(
        { _id: schedule.class_section_id},
        (classSectionSearchError, classSection) =&gt; {
          if (err)
            throw new Error(classSectionSearchError);

            schedule.entry.forEach(entryFound =&gt; {
            if (entryFound.teacher_id === req.body.teacher_id) {
              const tempo = {
                class_section: classSection.class_title,
                class_section_code: classSection.code,
                schedule_id: schedules._id ,
                class_section_id: schedule._id,
                subject_id: entryFound.subject_id,
              };

              Subject.findOne(
                { _id: entryFound.subject_id},
                (subjectSearchError, subject) =&gt;
                {
                  if (subjectSearchError)
                    throw new Error(subjectSearchError);
                  tempo.subject_title = subject.title;
                }
              );

              records.push(tempo);
            }
          })
      })
    });
    res.status(200).json({ records: records });
  } catch (errorCaught) {
    console.log(errorCaught);
    res.status(500).json({ details: "error occurred: " + JSON.stringify(errorCaught) });
  }
});




Bonus

There's whole lot more potential improvements unrelated to asyncronousity, though.


Use === instead of ==, unless you have a strong reason not to follow this advice.
Please setup eslint tool -- it's really hard to read the code which is formatted like that.
Use const for variable declarations. If something needs to get reassigned, use let. Do NOT use var unless it is necessary (which is super rare the case).
If you're using node.js consider using arrow functions.
In case of errors set status code to HTTP 500 Internal Server Error, whereas for success use 2XX (they vary depending on scenario).
Use proper names. array is too generic. records is actually what's being collected.
Use proper names. cla name means nothing to a reader...
Logging an object right after creation, but before population is meaningless:

tempo = {};
console.log("tempo fade" + tempo)

Do NOT declare all the variables before actually using them. Always try to achieve joint declaration and assignment: const recordsFound = [].