'NodeJS Express - Is it a bad practice to re-map SQL row results to JSON in backend?

Hi I'm currently working on a project wherein my superior suggested to use stored procedures and avoid heavily relying in ORM libraries so we can use the full potential of database.

so in order to follow the instruction I created stored procs to list, save and delete the model. eg.
model.save({...all params, active }, {user_id}) -> CALL sp_save_model(...:all params, :active, :user_id);
model.delete({id, permanent}, {user_id}) -> CALL sp_delete_model(:id, :permanent, :user_id);
model.list({last_id, limit}, {user_id}) -> CALL sp_model_list(:id=NULL, etc.. filters, :last_id, :limit, :user_id);
model.findById({id}, {user_id}) -> CALL sp_model_list(:id, etc.. filters, :limit=1, :user_id);

next is to translate the SQL table row results to JSON when calling sp_model_list.
the company practice is to avoid using JSON_OBJECT and JSON_ARRAYAGG because it makes the result of sp less readable when debugging in backend.

so I ended up using a mapper function like

// file: ./mapping/job_application_mapper.js
/**
 * JobApplication object mapper
 * @param {Object} company require ./mapping/company_mapper
 * @param {Object} applicant require ./mapping/applicant_mapper
 * @param {Object} application_status require ./mapping/job_application_status_mapper
 * @param {Object} previous_work_experience require ./mapping/work_experience_mapper
 * @returns JobApplication frozen json model
 */
 module.exports = function JobApplicationMapper({
  id,
  job_listing_id,
  created_by_id,
  date_created,
  date_modified,
  company,
  applicant,
  application_status,
  previous_work_experience,
 }) {

  return Object.freeze({
    id,
    job_listing_id,
    created_by_id,
    date_created,
    date_modified,
    company,
    applicant,
    application_status,
    previous_work_experience,
  });
}

in DAL it looks like

async function getList({
  job_listing_id,
  job_position_id,
  application_status_id,
  applicant_id,
  gender_id,
  is_willing_to_travel,
  is_willing_to_relocate,
  is_no_experience,
  min_year_experience,
  max_year_experience,
  seek_last_application_id,
  limit,
  is_summary,
  user_id
}, transaction) {
  let cmd = sql_dialect === "mysql"
    ? `call sp_job_applicant_list(:job_listing_id, :job_position_id, :application_status_id, :applicant_id, :gender_id, :is_willing_to_travel, :is_willing_to_relocate, :is_no_experience, :min_year_experience, :max_year_experience, :seek_last_application_id, :limit, :is_summary, :user_id);`
    : `EXEC sp_job_applicant_list :job_listing_id, :job_position_id, :application_status_id, :applicant_id, :gender_id, :is_willing_to_travel, :is_willing_to_relocate, :is_no_experience, :min_year_experience, :max_year_experience, :seek_last_application_id, :limit, :is_summary, :user_id`;

  debug(cmd, transaction);
  let results = await DB.query(cmd, {
    replacements: {
      job_listing_id: undefToNull(job_listing_id),
      job_position_id: undefToNull(job_position_id),
      application_status_id: undefToNull(application_status_id),
      applicant_id: undefToNull(applicant_id),
      gender_id: undefToNull(gender_id),
      is_willing_to_travel: undefToNull(is_willing_to_travel),
      is_willing_to_relocate: undefToNull(is_willing_to_relocate),
      is_no_experience: undefToNull(is_no_experience),
      min_year_experience: undefToNull(min_year_experience),
      max_year_experience: undefToNull(max_year_experience),
      seek_last_application_id: undefToNull(seek_last_application_id),
      limit: undefToNull(limit),
      is_summary: undefToNull(is_summary),
      user_id: undefToNull(user_id),
    },
    transaction
  });

  if (is_summary) {
    return results.map((row) => job_application_status_summary_mapper({
      total: row['total'],
      application_status: job_application_status_mapper({
        id: row['application_status.id'],
        name: row['application_status.name'],
        is_filter_default_visible: row['application_status.is_filter_default_visible'],
        is_require_appointment: row['application_status.is_require_appointment'],
        sort_index: row['application_status.sort_index'],
      })
    }));
  } else {
    return results.map(row => job_application_mapper({
      id: row['id'],
      job_listing_id: row['job_listing_id'],
      created_by_id: row['created_by_id'],
      date_created: row['date_created'],
      date_modified: row['date_modified'],
      company: company_mapper({
        id: row['company.id'],
        name: row['company.name'],
        logo: row['company.logo'],
      }),
      applicant: !row['applicant.id'] ? null : applicant_mapper({
        id: row['applicant.id'],
        user_id: row['applicant.user_id'],
        date_last_active: row['applicant.date_last_active'],
        date_of_birth: row['applicant.date_of_birth'],
        applicant_name: row['applicant.applicant_name'],
        expected_salary: row['applicant.expected_salary'],
        is_willing_to_travel: row['applicant.is_willing_to_travel'],
        is_willing_to_relocated: row['applicant.is_willing_to_relocated'],
        is_smoker: row['applicant.is_smoker'],
        pronoun: !row['applicant.pronoun.id'] ? null : pronoun_mapper({
          id: row['applicant.pronoun.id'],
          name: row['applicant.pronoun.name']
        }),
        gender: !row['applicant.gender.id'] ? null : gender_mapper({
          id: row['applicant.gender.id'],
          name: row['applicant.gender.name']
        }),
      }),
      application_status: !row['application_status.id'] ? null : job_application_status_mapper({
        id: row['application_status.id'],
        name: row['application_status.name'],
        sort_index: row['application_status.sort_index'],
        is_filter_default_visible: row['application_status.is_filter_default_visible'],
        is_require_appointment: row['application_status.is_require_appointment'],
      }),
      previous_work_experience: !row['previous_work_experience.date_hired'] ? null : work_experience_mapper({
        company_name: row['previous_work_experience.company_name'],
        date_hired: row['previous_work_experience.date_hired'],
        date_separated: row['previous_work_experience.date_separated'],
        position: row['previous_work_experience.position'],
      })
    }));
  }
}

I need help, what is the best practice for this?
please suggest any resource/links for nodejs backend api architecure/patterns I can review,,

Thanks in advance.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source