Nodejs - Express - Best practice to handle optional query-string parameters in API - javascript

I want to make a API which have 5 optional query parameters, I want to know if there is a better way to handle this, right now I check each one of them with if conditions, which is kind of dirty! is there any way that I can handle all scenarios without using lot's of if conditions?
let songName = req.query.songName
let singerName = req.query.singerName
let albumName = req.query.albumName
let publishDate = req.query.publishDate
if(songName && singerName && albumName && publishDate) {
const response = songs.filter(c => {
return c.songName === songName && c.singerName === singerName && c.albumName === albumName && c.publishDate === publishDate
}
res.send({
"Data" : response
})
}
if(songName && singerName && albumName && !publishDate) {
const response = songs.filter(c => {
return c.songName === songName && c.singerName === singerName && c.albumName === albumName
}
res.send({
"Data" : response
})
}
if(songName && singerName && !albumName && publishDate) {
const response = songs.filter(c => {
return c.songName === songName && c.singerName === singerName && c.publishDate === publishDate
}
res.send({
"Data" : response
})
}
if(songName && !singerName && albumName && publishDate) {
const response = songs.filter(c => {
return c.songName === songName && c.albumName === albumName && c.publishDate === publishDate
}
res.send({
"Data" : response
})
}
if(!songName && singerName && albumName && publishDate) {
const response = songs.filter(c => {
return c.singerName === singerName && c.albumName === albumName && c.publishDate === publishDate
}
res.send({
"Data" : response
})
}
.
.
.

You could use the ternary operator to do this all in one query. If the parameter is defined you check for equality and else you just return true. This could look like this:
const response = songs.filter(c => {
return (songName ? (c.songName === songName) : true) &&
(singerName ? (c.singerName === singerName) : true) &&
(albumName ? (c.albumName === albumName) : true);
});
res.send({
"Data": response
})

I may find Lodash to be useful for this one:
const response = songs.filter(song => {
return _.isEqual(req.query, _.pick(song, Object.keys(req.query)))
})

I suggest you to use Joi
It is very powerful library for javascript validations. You can make even conditional validations using it. See the complete docs.
I created basic schema for your scenario here.
// validation
const schema = Joi.object().keys({
songName: Joi.string()
singerName: Joi.string()
albumName: Joi.string()
publishDate: Joi.date()
});
const { error, value } = Joi.validate(req.query, schema, { abortEarly: false, allowUnknown: false });
if (error !== null) return res.send(400, { code: 400, message: "validation error", error: error.details });
It is easier to read and understand for other developers too. You can standardized the validations in the overall project.

Related

Empty string handling in nodeJs or Javascript

I write below code for handling empty string and get the data
DB image
In this image we need to show data behalf of Program column. If Project column and Slug(from project) column have empty data then it will show Program's field 'CIL' and 'OSI'.
I was write below code
//this is database code
// file db.js
const findNews = async () =>
new Promise((resolve, reject) => {
const news = [];
if (!base) {
base = new Airtable().base(process.env.AIRTABLE_BASE);
}
base(tables.news)
.select({})
.eachPage(
(records, fetchNextPage) => {
records.forEach(record => {
const slugField = record.get('Slug (from Project)') || "";
news.push({
description: record.get('Description'),
date: record.get('Date') || "",
project: slugField[0] || "",
program: record.get('Program'),
link: record.get('External Link') || "",
});
});
fetchNextPage();
},
err => {
if (err) {
reject(new Error(err)); // Airtable returns an object here (vs an Error)
} else {
resolve(news);
}
}
);
});
Controllers code
async news(ctx) {
const { role, displayCatalog } = strapi.cache.users[ctx.state.user.email];
const allNews = strapi.cache.news.filter(fields => {
return (
(fields.program === null ||
fields.project === null ||
fields.link === null ||
fields.description === null ||
fields.date === null) &&
(fields !== null || fields !== undefined) &&
fields.program.includes(programFromRole(role))
);
});
const news = strapi.cache.news.filter(
neww => neww.program.includes(programFromRole(role)) && displayCatalog.includes(neww.project)
);
const newsData = news.concat(allNews);
return buildResponse(newsData);
},
This code runnable but in db.js file I used for sending empty string as "" but this approach is not good.
could you please help me out of problem?

Error in uploading marks Expected string but received array

I have a school management project, and in the "UploadMarks" tab, when I click on the button to send the form with the students attendance, the error "Error in uploading marks Expected string but received array", I've heard that it's a problem with lib validator for validations, but I canĀ“t resolve this error.
uploadMarks:
const Validator = require('validator');
const isEmpty = require('./is-empty');
const validateFacultyUploadMarks = (data) => {
let errors = {}
data.subjectCode = !isEmpty(data.subjectCode) ? data.subjectCode : '';
data.exam = !isEmpty(data.exam) ? data.exam : '';
data.totalMarks = !isEmpty(data.totalMarks) ? data.totalMarks : '';
if (Validator.isEmpty(data.subjectCode)) {
errors.subjectCode = 'Subject Code field is required';
}
if (Validator.isEmpty(data.exam)) {
errors.exam = 'Exam field is required';
}
if (Validator.isEmpty(data.totalMarks)) {
errors.totalMarks = 'Total marks field is required';
}
return {
errors,
isValid: isEmpty(errors)
};
}
module.exports = validateFacultyUploadMarks
teacherController:
uploadMarks: async (req, res, next) => {
try {
const { errors, isValid } = validateFacultyUploadMarks(req.body);
// Check Validation
if (!isValid) {
return res.status(400).json(errors);
}
const {exam, totalMarks, marks, department, year } = req.body
const isAlready = await Mark.find({ exam, department})
if (isAlready.length !== 0) {
errors.exam = "You have already uploaded marks of given exam"
return res.status(400).json(errors);
}
for (var i = 0; i < marks.length; i++) {
const newMarks = await new Mark({
student: marks[i]._id,
exam,
department,
marks: marks[i].value,
totalMarks
})
await newMarks.save()
}
res.status(200).json({message:"Marks uploaded successfully"})
}
catch (err) {
console.log("Error in uploading marks",err.message)
}
},
is-Empty:
const isEmpty = value =>
value === undefined ||
value === null ||
(typeof value === 'object' && Object.keys(value).length === 0) ||
(typeof value === 'string' && value.trim().length === 0);
module.exports = isEmpty;

Refactor this code into something modular

I have the following JS code
addSelectorLabel = (params) => {
let valEntidad = params.find(tipo => { if (tipo.name === "type") return tipo })?.value || ""
let valCNAE = params.find(tipo => { if (tipo.name === "cnae") return tipo })?.value || ""
let valProvince = params.find(tipo => { if (tipo.name === "province") return tipo })?.value || ""
let valVia = params.find(tipo => { if (tipo.name === "address_prefix") return tipo })?.value || ""
if (valEntidad !== "") {
let labelSelector = EntidadArr.find(tipo => {
if (tipo.cod === valEntidad) {
return tipo
}
})?.val || ""
if (labelSelector !== "") {
params.push(
{
name: "typeLabel", value: labelSelector, hasError: false
}
)
}
}
if (valCNAE !== "") {
let labelSelector = CNAE.find(tipo => {
if (tipo.cod === valCNAE) {
return tipo
}
})?.val || ""
if (labelSelector !== "") {
params.push(
{
name: "cnaeLabel", value: labelSelector, hasError: false
}
)
}
}
if (valProvince !== "") {
let labelSelector = ProvinciaArr.find(tipo => {
if (tipo.cod === valProvince) {
return tipo
}
})?.val || ""
if (labelSelector !== "") {
params.push(
{
name: "provinceLabel", value: labelSelector, hasError: false
}
)
}
}
if (valVia !== "") {
let labelSelector = ViaArr.find(tipo => {
if (tipo.cod === valVia) {
return tipo
}
})?.val || ""
if (labelSelector !== "") {
params.push(
{
name: "viaLabel", value: labelSelector, hasError: false
}
)
}
}
return params
}
The same process is repeated 4 times, so im trying to create a modular function to make it shorter.
But im not really sure where to start
Im looking to create something like for the if sentences
export const selectLabel = async (jsonToCrawl, valueToFind, params) => {
let labelSelector = jsonToCrawl.find(tipo => {
if (tipo.cod === valueToFind) {
return tipo
}
})?.val || ""
if (labelSelector !== "") {
params.push(
{
name: "typeLabel", value: labelSelector, hasError: false
}
)
return params
}
}
//call it like --> params = selectLabel(EntidadArr , valEntidad, params)
But im wondering. what happens with the params.push ? Do i have to return it? and reset params?
Im not sure how to apprach this
When you're doing something like this, what you want to look for is what changes in the different copies of the code. Then you make a function that accepts the things that change as parameters, and uses those parameter values to do the work. This called parameterizing the code.
For instance, let's look just at the initial part:
let valEntidad = params.find(tipo => { if (tipo.name === "type") return tipo })?.value || ""
let valCNAE = params.find(tipo => { if (tipo.name === "cnae") return tipo })?.value || ""
let valProvince = params.find(tipo => { if (tipo.name === "province") return tipo })?.value || ""
let valVia = params.find(tipo => { if (tipo.name === "address_prefix") return tipo })?.value || ""
All four find calls above do the same thing, the only thing that varies is the name that the code checks against.
That means you can write a function accepting that name and have it do the work:
function findTypeValueByName(types, name) {
return types.find(tipo => { if (tipo.name === name) return tipo })?.value || "";
}
Then you use that function:
let valEntidad = findTypeValueByName(params, "type");
let valCNAE = findTypeValueByName(params, "cnae");
let valProvince = findTypeValueByName(params, "province");
let valVia = findTypeValueByName(params, "address_prefix");
You can apply the same process to the four if statements. They have more things that vary, so there will be more than one parameter to use, but the same approach will work for them as well.
Side note: The callback for find is supposed to return a flag (true/false) for whether the entry is the one you want. So instead of:
function findTypeValueByName(types, name) {
return types.find(tipo => { if (tipo.name === name) return tipo })?.value || "";
}
just return the result of the comparison:
function findTypeValueByName(types, name) {
return types.find(tipo => tipo.name === name)?.value || "";
}
That does the same thing. Your original code does work, but the reason it works is a by-product of the fact that your callback wasn't returning anything (so implicitly returns undefined) in the false case, and was returning an object in the true case. That happens to work, but it's best to clearly return a flag instead.

undefined is not an object (evaluating 'message.message_contacts.filter')

Hey this is a react native code with redux saga.. The code returns a list.map with "filterUnreadMessages = list"...
But for some reason the code return this especific error:
undefined is not an object (evaluating 'message.message_contacts.filter')
const messageIds = list.map(message => message.id);
const filterUnreadMessages = list
.map(message =>
message.message_contacts.filter(
contact => contact.contact_id === userId && contact.readed_at === null,
),
It means that message.message_contacts is undefined, so you can't call .filter on it. Alternatively, message itself is undefined.
You can do a check first:
const filterUnreadMessages = list
.map(message => {
if (message !== undefined && Array.isArray(message.message_contacts)) { // <-- check here
return message.message_contacts.filter(
contact => contact.contact_id === userId && contact.readed_at === null,
)
} else {
return [];
}
}

Add data to firebase and redirect to other page simultaneously

I am creating a website with login/signUp feature with Firebase, the problem is that when User Creates a new Account I want to redirect him to the homepage and put his name in firebase database simultaneously. The User gets redirected to the homepage and I cannot add data to firebase database
Please Check my Code
signUp.addEventListener('click',() => {
const emailValue = emailField.value;
const passValue = passField.value;
if(emailValue === "" && passValue === "" || emailValue !== "" && passValue == "" || emailValue == "" && passValue !== "")
{
alert('Please fill all fields')
}
else {
const auth = firebase.auth();
const promise = auth.createUserWithEmailAndPassword(emailValue,passValue);
promise
.then(() => {
auth.signInWithEmailAndPassword(emailValue,passValue);
let userId = auth.currentUser.uid;
let dataRef = firebase.database().ref('users/' + userId);
dataRef.set({
name: nameField.value
});
console.log('User Signed In');
return './homepage.html';
})
.then((address) => {
window.location.href = address;
})
.catch(e => {
console.log(e.message);
})
}
});
Thanks In Advance :)
createUserWithEmailAndPassword returns an user object so there is no need to to re-validate credentials. Please try this:
signUp.addEventListener('click', () => {
const emailValue = emailField.value;
const passValue = passField.value;
if (emailValue === "" && passValue === "" || emailValue !== "" && passValue == "" || emailValue == "" && passValue !== "") {
alert('Please fill all fields')
} else {
firebase.auth().createUserWithEmailAndPassword(emailValue, passValue).then(function (user) {
const user_data = {
wo: 0
};
const promise = firebase.app().database().ref(`/users/${user.uid}`).set(user_data);
promise.then(() => {
setTimeout(1000);
window.location.href = './homepage.html';
});
}, function (error) {
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorMessage);
});
}
});

Categories