try and catch not catching all thrown errors

Im trying to learn JS by building a small application/api using expressjs. I have some part of the code that is really confusing me. Here is the code below to explain.

my main.js file

import express from 'express'
import 'dotenv/config'
import helmet from 'helmet'
import AppsLoader from './src/apps/AppsLoader.js'

const app = express()
/* @todo check not sure if needed since im only accepting query params
app.use(express.json())
app.use(express.urlencoded({
    extended: true
}))
*/
app.use(helmet())



app.get('/', (req, res) => {
    res.status(301).json({
        message: "Please request the POST /action endpoint"
    })
})

//all actions are achieved via post request i.e get, patch, delete and post
app.post('/action', (req, res) => {
    try {
        new AppsLoader(req, res)
    } catch (err) {
        res.status(err.customCode || 400).json({
            status: 'error',
            message: err.message
        })
    }

})



app.listen(process.env.SERVER_PORT)

and here is AppsLoader class

import ValidationRules from '../helpers/ValidationRules.js'
import Validator from '../helpers/Validator.js'
import { StatusCodes } from './../helpers/StatusCodes.js'

//import apps
import Trivia from './trivia/index.js' 

export default class AppsLoader {
    constructor(req, res) {
        this._req = req
        this._res = res
        this._params = req.query
        this._init()
    }

    _init() {
        new Validator([
            new ValidationRules('appName', 'App Name', 'string', 5, 20),
            new ValidationRules('actionName', 'Action Name', 'string', 5, 20)], 
            this._params
        )

        const appName = this._params['appName']

        if (!this._getAppNames().includes(appName)) {
            throw new Error('invalid app name', StatusCodes.BAD_REQUEST) 
        }

        switch (appName) {
            case 'trivia':
                new Trivia(this._params, this._res)
                break
            default: 
                throw new Error('no app was executed', StatusCodes.SERVER_ERROR)
        }
    }

    _getAppNames() {
        return [
            'trivia'
        ]
    }
}

and here is the validator

import ValidationRules from './ValidationRules.js'
import { StatusCodes } from './StatusCodes.js'

export default class Validator {
    constructor(validationData, requestBody) {
        if (!Array.isArray(validationData)) {
            throw new Error('expecting argument one to be an array', StatusCodes.BAD_REQUEST)
        }

        for (let i = 0; i < validationData.length; i++) {
            if (!(validationData[i] instanceof ValidationRules)) {
                throw new Error('All validation entries must be instances of ValidationRules class', StatusCodes.SERVER_ERROR)
            }
        }

        this._rules = validationData
        this._data = requestBody
        this._run()
    }

    _run() {
        for (let i = 0; i < this._rules.length; i++) {
            const name = this._rules[i].getName()
            const display = this._rules[i].getDisplayName()
            const max = this._rules[i].getMaxLength()
            const min = this._rules[i].getMinLength()
            const type = this._rules[i].getType()


            let paramData = null

            if (!this._data.hasOwnProperty(name)) {
                throw new Error(`${display} is required but not presented`, StatusCodes.BAD_REQUEST)
            }

            paramData = this._data[name]

            if (type != 'email' && typeof paramData != type) {
                throw new Error(`Expecting data type of ${type}, for param ${display}`, StatusCodes.BAD_REQUEST)
            }

            let checkValueLength = null

            switch (type) {
                case 'string':
                    checkValueLength = paramData.length
                    break
                case 'number':
                    checkValueLength = paramData.toString().length
                    break
                case 'email':
                    checkValueLength = paramData.length
                    break
                default: 
                    throw new Error('failed to set param length', StatusCodes.SERVER_ERROR)
            }

            if (checkValueLength > max) {
                throw new Error(`param ${display} length must be less than ${max}`, StatusCodes.BAD_REQUEST)
            }

            if (checkValueLength < min) {
                throw new Error(`param ${display} length must be more than ${min}`, StatusCodes.BAD_REQUEST)
            }
             
        }
    }
}

and here is the ValidatorRules class

import { StatusCodes } from './StatusCodes.js'

export default class ValidationRules {

    constructor(nameInRequest, outputName, type, minLength, maxLength) {

        if (typeof nameInRequest != 'string' || typeof outputName != 'string' || typeof type != 'string') {
            throw new Error('Expecting string values for name, display and type', StatusCodes.SERVER_ERROR)
        }

        if (typeof minLength != 'number' || typeof maxLength != 'number') {
            throw new Error('min and max need to be numbers', StatusCodes.SERVER_ERROR)
        }

        if (!this._getAllowedTypes().includes(type)) {
            throw new Error('Allowd types are: ', this._getAllowedTypes().toString(), StatusCodes.SERVER_ERROR)
        }

        this._name = nameInRequest
        this._displayName = outputName
        this._type = type
        this._min = minLength
        this._max = maxLength
    }

    _getAllowedTypes() {
        return [
            'string', 'number', 'email'
        ]
    }

    getName() {
        return this._name
    }

    getType() {
        return this._type
    }

    getDisplayName() {
        return this._displayName
    }

    getMaxLength() {
        return this._max
    }

    getMinLength() {
        return this._min
    }


}

and here is the Trivia class that is being called by AppsLoader

import MySqlConnection from './../../db/MySqlConnection.js' 
import ValidationRules from './../../helpers/ValidationRules.js'
import Validator from './../../helpers/Validator.js'
import { StatusCodes } from './../../helpers/StatusCodes.js'
import AppResponser from './../../helpers/AppResponser.js'
import Hasher from './../../helpers/Hasher.js'


export default class Trivia extends AppResponser {

    constructor(params, res) {
        super(res, new MySqlConnection(
            process.env.TRIVIA_DB_HOST,
            process.env.TRIVIA_DB_USER,
            process.env.TRIVIA_DB_PASS,
            process.env.TRIVIA_DB_DATABASE
        ))
        
        this._params = params
        this._action = params.actionName
        this._validate()
        this._run()
    }

    _validate() {
        if (!this._allowedAppActions().includes(this._action)) {
            throw new Error('Current action not allowed', StatusCodes.UNAUTHORIZED)
        }
        //execute validations based on actions
    }

    _login() {
        //validate required params
    }

    async _register() {

        new Validator([
            new ValidationRules('email', 'Email', 'email', 10, 100),
            new ValidationRules('pass', 'Password', 'string', 6, 30)
        ], this._params)

        const email = this._params.email
        const pass = this._params.pass


        const emailCount = await this._db.countResults('SELECT COUNT(*) AS resultsCount FROM users WHERE email = ?', [email])


        if (emailCount >= 1) {
            throw new Error('Email already exists', StatusCodes.BAD_REQUEST)
        }

        const hashedPass = new Hasher(pass)
        const hash = hashedPass.getHash()


        const addUserResults = await this._db.query(
            'INSERT INTO users (email, pass) VALUES (?,?)',
            [email, hash]
        )

        const insertId = addUserResults.insertId

        if (typeof insertId != 'number') {
            throw new Error('Cant add user', StatusCodes.SERVER_ERROR)
        }


        const activationToken = await this._db.generateRandomToken('user_activation', insertId)

        await this.send({
            message: 'You have registered successfully, please click the link in your email to activate your account',
            registered: true
        })

    }

    
    _allowedAppActions() {
        return [
            'register',
            'login',
            'activateAccount',
            'closeAccount',
            'requestPasswordReset',
            'updatePasswordWithToken'
        ]
    }

    _run() {
        switch(this._action) {
            case 'login':
                this._login()
                break
            case 'register':
                this._register()
                break
            
        }
    }
}

so the problem happens in the Validator class, the validator seems to fail in the Trivia class

for exmaple calling this url, ommiting the appName or renaming query param to appName2 instead of appName returns the correct json

url1: http://localhost:3000/action?appName2=trivia&actionName=register&[email protected]&pass=test123

url1 results:

{
    "status": "error",
    "message": "App Name is required but not presented"
}

now in url2 im requesting another url but removing the email param from query

url2: http://localhost:3000/action?appName=trivia&actionName=register&[email protected]&pass=test123

but this results in app crash and the following output in terminal

file:///home/dever10/Documents/nodeapi/src/helpers/Validator.js:33
                throw new Error(`${display} is required but not presented`, StatusCodes.BAD_REQUEST)
                      ^

Error: Email is required but not presented
    at Validator._run (file:///home/dever10/Documents/nodeapi/src/helpers/Validator.js:33:11)
    at new Validator (file:///home/dever10/Documents/nodeapi/src/helpers/Validator.js:18:8)
    at Trivia._register (file:///home/dever10/Documents/nodeapi/src/apps/trivia/index.js:38:3)
    at Trivia._run (file:///home/dever10/Documents/nodeapi/src/apps/trivia/index.js:110:10)
    at new Trivia (file:///home/dever10/Documents/nodeapi/src/apps/trivia/index.js:22:8)
    at AppsLoader._init (file:///home/dever10/Documents/nodeapi/src/apps/AppsLoader.js:31:5)
    at new AppsLoader (file:///home/dever10/Documents/nodeapi/src/apps/AppsLoader.js:13:8)
    at file:///home/dever10/Documents/nodeapi/main.js:26:3
    at Layer.handle [as handle_request] (/home/dever10/Documents/nodeapi/node_modules/express/lib/router/layer.js:95:5)
    at next (/home/dever10/Documents/nodeapi/node_modules/express/lib/router/route.js:149:13)

Node.js v21.4.0
[nodemon] app crashed - waiting for file changes before starting...

which is being thrown due to line below in the validator class

if (!this._data.hasOwnProperty(name)) {
                throw new Error(`${display} is required but not presented`, StatusCodes.BAD_REQUEST)
            }

my question is why is this thrown error not being caught by the main try and catch blow in the main.js file, like the rest of the errors thrown?

Sorry for the long question, but im new to this and any help is appreciated. Thanks in advance

lol i explained it all above