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