I am working with nodejs, express, and express-validator to create a type safe rest api.
What I want is that when the user passes in a request that contains one or many extra keys that the validation fails.
These are my validator rules:
const prepaidValidationRules = () => {
return [
body('accountHolder').exists().isString(),
body('amount').exists().isFloat()
]
}
Now when some sends the following request body:
{
"amount": 20.0,
"accountHolder": "Claude"
}
Then the validation succeedes as expected, but when I send the following request body:
{
"amount": 20.0,
"accountHolder": "Claude",
"extrakey": "someValue"
}
Then I want the validation to fail.
Is there anyway to do this using express-validaiton.
Thank you very much for any help :)
You can disallow unknow fields with express-validator, but you need to do an extra function.
The middleware function:*
import { validationResult } from 'express-validator'
export function validate (validators, allowExtraFields = false) {
return async (req, res, next) => {
// You can meka the validation optional
if (!allowExtraFields) {
// Fields validation
const extraFields = checkIfExtraFields(validators, req)
if (extraFields) { res.status(400).json({ error: true, message: 'Bad request' }) }
}
// Type validation
await Promise.all(validators.map((validator) => validator.run(req)))
const errors = validationResult(req)
if (errors.isEmpty()) {
return next()
}
logger.error(`${req.ip} try to make a invalid request`)
return res.status(400).json({ error: true, errors: errors.array() })
}
}
function checkIfExtraFields (validators, req) {
const allowedFields = validators.reduce((fields, rule) => {
return [...fields, ...rule.builder.fields]
}, []).sort()
// Check for all common request inputs
const requestInput = { ...req.query, ...req.params, ...req.body }
const requestFields = Object.keys(requestInput).sort()
if (JSON.stringify(allowedFields) === JSON.stringify(requestFields)) {
return false
}
logger.error(`${req.ip} try to make a invalid request`)
return true
}
Usage
import { body, query } from 'express-validator'
const validator = [
body('email').isEmail().exist(),
query('new').isString(),
]
router.post(
'/user',
validate(validator ), // By default all extrange fields are rejected
handler.post
)
This one works to me
const { body } = require("express-validator");
...
...
body().custom((body) => {
const allowedKeys = [
"name",
"age",
"bio",
];
for (const key of Object.keys(body)) {
if (!allowedKeys.includes(key)) {
throw new Error(`Unknown property: ${key}`);
}
}
return true;
}),
I will leave this here if someone wants to achieve the same.
I simply used currying to achieve what I wanted:
app.post("/someRoute", validationRule(), validate(2), doSomething());
validate function
function validate(nmbOfExpectedKeys) {
return (req, res, next) => {
if (Object.keys(req.requestBody) > nmbOfExpectedKeys)
return res.status(422);
}
}
Related
I am working to validate data input from an API call using express-validator version 6.11.1 and every time I validate using either check or body, I get the error below:
TypeError: body(...).not(...).IsEmpty is not a function
I created a helper called validator.js with the code below
const { body, validationResult } = require('express-validator')
const bcrypt = require('bcrypt')
const signupValidation = () => {
return [
body('firstname')
.not().IsEmpty().withMessage('Firstname field is required'),
body('lastname')
.not().IsEmpty().withMessage('Lastname field is required')
]
}
const validate = (req, res, next) => {
const errors = validationResult(req)
if (errors.isEmpty()) {
return next()
}
const extractedErrors = []
errors.array().map(err => extractedErrors.push({ msg: err.msg }))
res.status(200).json({
statusCode: 422,
error: extractedErrors
})
}
module.exports = {
signupValidation,
validate
}
The route where I am calling it looks like
const { signupValidation, validate } = require('../../helpers/validator')
//Endpoint to create new merchant
router.post('/account/create-merchant', signupValidation(), validate, async (req, res) => {
res.status(200).json({
statusCode: 201,
message: req.body
})
})
Sample Data from the API
{
"firstname": "",
"lastname": "Jon",
"phone" : "*****",
"email" : "oayayayaya",
"password": "******"
}
Can you please guide me on what to do to solve the error message (TypeError: body(...).not(...).IsEmpty is not a function)
I think it should be isEmpty() instead of IsEmpty(), try this:
const signupValidation = () => {
return [
body('firstname')
.not().isEmpty().withMessage('Firstname field is required'),
body('lastname')
.not().isEmpty().withMessage('Lastname field is required')
]
}
Check the doc here
Im using Router classes to manage all my Routes:
const router = express.Router();
/**
* User Sign up Route at /api/auth/register
*/
router.post(
"/register",
checkBodyParameters(['username', 'email', 'password']),
verifyRegister.ensurePasswordStrength,
verifyRegister.checkUsernameAndEmail,
AuthController.register
);
export = router;
I want to check the x-www-form-urlencoded body parameters. To see if either the key is not what it should be, or the value is empty.
I wrote a middleware function to check that:
import { Request, Response } from "express";
export default function checkBodyParameters(
bodyParams: Array<string>,
req: Request,
res: Response,
next
) {
let requestBodyParams: Array<string> = [];
requestBodyParams.push(req.body.username, req.body.email, req.body.password);
requestBodyParams.forEach((requestBodyParam) => {
if (bodyParams.includes(requestBodyParam)) {
if (requestBodyParam !== "") {
next();
} else {
res.status(400).json({
message: "Paremeter cant be empty",
value: requestBodyParam,
});
}
} else {
res
.status(400)
.json({ message: "Paremeter not specified", value: requestBodyParam });
}
});
}
But it seems like it doesnt like me passing Arguments to the middleware function in
checkBodyParameters(['username', 'email', 'password'])
My Question is how do i create a middleware function which acceppts more values than req, res and next? And how to use this function correctly with the router instance.
Any Feedback is appreciated
You are calling the function instead of returning a function as a middleware.
Instead, use:
const checkBodyParameters = (
bodyParams: Array<string>
) => (
req: Request,
res: Response,
next
) => {
let requestBodyParams: Array<string> = [];
requestBodyParams.push(req.body.username, req.body.email, req.body.password);
requestBodyParams.forEach((requestBodyParam) => {
if (bodyParams.includes(requestBodyParam)) {
if (requestBodyParam !== "") {
next();
} else {
res.status(400).json({
message: "Paremeter cant be empty",
value: requestBodyParam,
});
}
} else {
res
.status(400)
.json({ message: "Paremeter not specified", value: requestBodyParam });
}
});
}
export default checkBodyParameters
Any advice on using async/await in an apollo plugin? I'm attempting to await a twilio service promise and running into the Can not use keyword 'await' outside an async function babel parser error and unsure how to convert the parent function to be async. Here's the basic layout:
export const twilioVerification = async () => {
return {
requestDidStart () {
return {
willSendResponse ({ operationName, response, context }) {
if (['UpdateUser', 'CreateUser', 'SignInByPhone'].includes(operationName)) {
const user = response.data[operationName];
if (user != null) {
await sendVerificationText(user.phoneNumber);
}
}
}
}
},
}
};
The above code throws the BABEL_PARSE_ERROR. I've attempted a variety of ways to add async to willSendResponse and/or requestDidStart with mixed unsuccessful results. For reference, here's how I am instantiating ApolloServer as well:
const server = new ApolloServer({
context: { driver, neo4jDatabase: process.env.NEO4J_DATABASE },
schema: schema,
introspection: process.env.APOLLO_SERVER_INTROSPECTION,
playground: process.env.APOLLO_SERVER_PLAYGROUND,
plugins: [
pushNotifications(firebase),
twilioVerification(),
]
})
The function that isn't async is your function. Just add async on your willSendResponse. Here is one way to do that:
export const twilioVerification = async () => {
return {
requestDidStart () {
return {
willSendResponse: async ({ operationName, response, context }) => {
if (['UpdateUser', 'CreateUser', 'SignInByPhone'].includes(operationName)) {
const user = response.data[operationName];
if (user != null) {
await sendVerificationText(user.phoneNumber);
}
}
}
}
},
}
};
this is my react js code and I want to connect with my node js API but I don't understand how to that ...!
import React, { useState } from "react";
import Poll from "react-polls";
// import "./styles.css";
/**
* https://stackoverflow.com/questions/65896319/react-js-class-poll-convert-into-react-hooks-poll
*/
// Declaring poll question and answers
const pollQuestion = "Youtube is the best place to learn ?";
const answers = [
{ option: "Yes", votes: 7 },
{ option: "No", votes: 2 },
{ option: "don't know", votes: 1 },
];
const Fakepolls = () => {
// Setting answers to state to reload the component with each vote
const [pollAnswers, setPollAnswers] = useState([...answers]);
// Handling user vote
// Increments the votes count of answer when the user votes
const handleVote = (voteAnswer) => {
setPollAnswers((pollAnswers) =>
pollAnswers.map((answer) =>
answer.option === voteAnswer
? {
...answer,
votes: answer.votes + 1,
}
: answer
)
);
};
return (
<div>
<Poll
noStorage
question={pollQuestion}
answers={pollAnswers}
onVote={handleVote}
/>
</div>
);
};
export default function App() {
return (
<div className="App">
<Fakepolls />
</div>
);
}
It work's fine with
// Declaring poll question and answers
const pollQuestion = "Youtube is the best place to learn ?";
const answers = [
{ option: "Yes", votes: 7 },
{ option: "No", votes: 2 },
{ option: "don't know", votes: 1 },
];
but I want to connect this poll with my API instead of Declaring it ..! this is my api- to get data -> ( router.get("/poll/:pollId", getPoll); //)
exports.getPoll = async (req, res, next) => {
try {
const { pollId } = req.params;
const polls = await Poll.findById(pollId);
if (!polls) throw new Error("no polls found");
res.status(200).json(polls);
} catch (error) {
error.status = 400;
next(error);
}
};
This is a postman image -
and this API for POST data- and my node js code -
exports.votes = async (req, res, next) => {
try {
/**
* 1. get the poll from db
* 2. check if the user already exists in any option
* 3. if user has already selected any option do nothing
* 4. if user has selected any other option remove from that option
* 5. if user does not exist in any option, insert his user id to selected option
*/
const { pollId } = req.params;
let { userId, answer } = req.body;
// get selected poll from db
const poll = await Poll.findById(pollId);
if (answer && poll) {
answer = answer.toLowerCase();
///Finf the Poll
let existingVote = null;
Object.keys(poll.options).forEach((option) => {
// loop on all options, check if the user already exists in any option
if (poll.options[option].includes(userId)) {
existingVote = option;
}
});
if (existingVote == null) {
// if there is no existing vote save it to db
try {
const push = {};
push[`options.${answer}`] = userId;
const update = await Poll.findByIdAndUpdate(
pollId,
{ $push: push },
{ upsert: true }
);
res.status(201).json(update);
} catch (err) {
error.status = 400;
next(error);
}
} else if (existingVote && existingVote.length > 0) {
// check if answer is same as previous, if yes send not modified
if (existingVote.toLowerCase() === answer.toLowerCase()) {
res.status(304).send("Response already saved");
} else {
// delete the previous response and save it in new
if (
Array.isArray(poll.options[existingVote]) &&
poll.options[existingVote].length > 0
) {
// TODO: filtering this is not returning array but 1
poll.options[existingVote] = poll.options[existingVote].filter(
(vote) => vote != userId
);
poll.options[answer] = poll.options[answer].push(userId);
const update = await Poll.findByIdAndUpdate(pollId, {
$set: { options: poll.options },
});
res.status(201).json(update);
}
}
} else {
error = {
status: 500,
message: "Something went wrong",
};
next(error);
}
} else {
error = {
status: 404,
message: "Poll not found",
};
next(error);
}
} catch (error) {
error.status = 400;
next(error);
}
};
this is a POSTMAN image using POST to store data --- >
how can I connect API with react poll
What you'd do is make a fetch() to your /api/polls endpoint inside your Fakepolls component, the URL being exactly as you show in your Postman screenshot. More info on fetch here at the MDN docs.
With the response you get from the endpoint, populate the answers array you component uses. From what I see, it would require a bit of transformation as your answer object is not quite the same as what Poll needs.
Next, upon user action, as well as updating the votes in the UI, you need to make another fetch to your vote endpoint.
Here's your component again with these adjustments. Keep in mind it's untested and the URLs are obviously not real:
import React, { useState, useEffect } from "react";
import Poll from "react-polls";
// import "./styles.css";
/**
* https://stackoverflow.com/questions/65896319/react-js-class-poll-convert-into-react-hooks-poll
*/
const Fakepolls = () => {
// Setting answers to state to reload the component with each vote
const [pollQuestion, setPollQuestion] = useState('');
const [pollAnswers, setPollAnswers] = useState([]);
// Query the actual poll info from the server
useEffect(() => {
fetch('http://your-server/api/polls/you-poll-id')
.then((response) => response.json()) //parse response as json
.then((pollObject) => {
let answerCountDictionary = Object.keys(pollObject.options)
.map(oKey => {
return {
option: oKey,
anwers: pollObject.options[oKey].length
}
}); //iterate over the 'options' properties' keys to get the names and the current votes
setPollAnswers(answerCountDictionary);
setPollQuestion(pollObject.question)
})
.catch((error) => {
console.error('Error:', error);
});
},[]) //adding empty array of dependencies to prevent multiple calls on state change
// Handling user vote
// Increments the votes count of answer when the user votes
const handleVote = (voteAnswer) => {
setPollAnswers((pollAnswers) =>
pollAnswers.map((answer) =>
answer.option === voteAnswer
? {
...answer,
votes: answer.votes + 1,
}
: answer
)
);
//also submit the backend
fetch('http://your-server/api/vote/poll-id', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: {
"userId": "the-logged-in-user",
"answer": voteAnswer
},
})
.then(data => {
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
};
return (
<div>
<Poll
noStorage
question={pollQuestion}
answers={pollAnswers}
onVote={handleVote}
/>
</div>
);
};
export default function App() {
return (
<div className="App">
<Fakepolls />
</div>
);
}
I had a question that doesn't seem to be answered anywhere.
I am running tests from within my Express.js api. I set up a page that has a button and a field to enter a keyword intended to be used during a testcafe test. My endpoint I set up is /testcafe. But after sending a post request to /testcafe, there is a long delay while test runs and so my question is what is the best next step besides hanging?
Also, can my post request body, which contains the keyword, be directly used in a test like this? Keep in mind it's this pattern:
frontend -> POST request -> Express server -> /testcafe endpoint - test
My problem is after it reaches test, I currently have it attempting to call fetch from within the request logger. Is this right?
import { ClientFunction, Selector } from 'testcafe';
import { RequestLogger, RequestHook } from 'testcafe';
import zlib from 'zlib';
import fetch from 'isomorphic-unfetch';
const url = 'https://www.mysitetesturl.com/page';
class MyRequestHook extends RequestHook {
constructor (requestFilterRules, responseEventConfigureOpts) {
super(requestFilterRules, responseEventConfigureOpts);
}
onRequest (e) {
console.log('in onRequest!')
console.log('========================')
console.log('Request Body')
let buf = e._requestContext.reqBody
console.log(buf.toLocaleString())
}
onResponse (e) {
let buf = Buffer(e.body)
let unzippedBody = Buffer(zlib.gunzipSync(buf))
let payload = unzippedBody.toLocaleString()
fetch('http://myapiipaddress/api/testcafe',
method: 'PUT',
body: JSON.stringify(payload)
)
.then((err, doc) => {
if(err) {
console.log(err)
} else {
console.log(doc)
}
})
}
}
const myRequestHook = new MyRequestHook({
url: url,
method:'get'},
{
includeHeaders: true,
includeBody: true
}
);
fetch('http://myapiipaddress/api/testcafe',
method: 'GET'
)
.then((err, doc) => {
if(err) {
console.log(err)
} else {
fixture`myfixture`
.page(doc.url)
.requestHooks(myRequestHook);
test(`mytest`, async t => {
const inputField = Selector('input');
await t
await t
.wait(5000)
.typeText(inputField, doc.text)
.wait(5000)
}
);
}
})
According to your scheme, you need to organize your code in a different way:
const createTestCafe = require('testcafe');
....
// Choose the necessary body parser for express application
// https://github.com/expressjs/body-parser
app.use(bodyParser.urlencoded({ extended: true }));
...
app.post('/', function (req, res) {
createTestCafe('localhost', 1337, 1338, void 0, true)
.then(testcafe => {
const runner = testcafe.createRunner();
return runner
.src('/tests')
.browsers('chrome')
.run();
})
.then(failedCount => {
testcafe.close();
res.end();
});
})