简体   繁体   中英

NodeJS RESTful API - How to handle 'undefined' request variables properly?

I am developing a RESTful API using NodeJS and Express.
I noticed that incoming requests sometimes lack of some expected variables, which cause the program to crash, saying it couldn't set the value of a variable, to an 'undefined' value - as no value arrived with the request.
Example:
The application is expecting variableY, but instead variableX is being sent:

 formData: { variableX: 'valueX' }

The program is expecting to receive variableY, with the following code:

const checkVariables = Joi.validate({ 
    variableY: req.body.variableY,
}, schema);

The application crashes with the following error:

TypeError: Cannot read property 'variableY' of undefined

I thought about a few ways to handle that, including declaration of variables upon application initiation and using them along, using try-catch .
Another way will be to use if-else , if-chaining , or case-switch , but as you understood of course I am looking for the cleanest way to achieve that.
Any ideas?

Thank you.

** EDIT **
Progressed and managed to achieve the result using the object only. Once trying to reach any of it's inner fields the error will be thrown anyway, example:
if(req.body.variableY == undefined){console.log('The expected variable is undefined');} //true

When the validation addresses a field inside the 'undefined' object:
if(req.body.variableY.dataId == undefined){console.log('The expected variable is undefined');} //crashes
The following error is being thrown again:
TypeError: Cannot read property 'variableX' of undefined

After doing some more digging around, found this Stackoverflow thread:
How to check if object property exists with a variable holding the property name?
Tried using hasOwnProperty, but the same kind of error is being thrown:
TypeError: Cannot read property 'hasOwnProperty' of undefined

Tried wrapping variable declaration using try-catch , still didn't work:

try{
    var variableX = req.body.variableX
    var variableXDataId = req.body.variableX.dataId
}
catch(e){
    res.status(400).send('Wrong request error: Please check your request variables and try again');
}

As this is a really basic validation that should be addressed by most of the RESTful APIs (validating that you get the expected incoming variables inside the request, so the program won't crash by having errors it can't handle - what is the common solution for such problems (expected / unexpected request validation)?

Thank you.

You can take another approach, check req.body before you reach checkVariables :

let body = req.body;

// data - your req.body
// requiredKeys - is an array of strings , [ key1, key2 ... keyN]  | string[]

     const setKeys = ( data, requiredKeys )=>{

         if( !typeof requiredKeys.length ){
            requiredKeys = [];
         }

         if(requiredKeys.length) requiredKeys.forEach( k =>{

             k = k.replace(/\+/g,'/');

             let keysList = [];

             if( /\/+/g.test(k)){
               keysList = k.split('/');
             }else{
              keysList = [k];
             }

             let [firstKey, ...rest] = keysList;

             if( typeof data[firstKey] === 'undefined' ){
               data[firstKey] = {};
             }

             if( rest.length ){

                data[firstKey] = setKeys(data[firstKey], [rest.join('/')] );

             }

         })

         return data;

      }

let checkedData= setKeys(body, ['variableT','variableP/noname/emptyObj','custom/object/does/not/exist/but/it/will/be/created/here']);

const checkVariables = Joi.validate(checkedData, schema);

UPDATE

Below you will find an working example on how things should work during a / (let's say /usersStatus/:id ) request:

 const express = require('express') const app = express() const port = 3000 const setKeys = (data, requiredKeys) => { if (!typeof requiredKeys.length) { requiredKeys = []; } if (requiredKeys.length) requiredKeys.forEach(k => { k = k.replace(/\\+/g, '/'); let keysList = []; if (/\\/+/g.test(k)) { keysList = k.split('/'); } else { keysList = [k]; } let [firstKey, ...rest] = keysList; if (typeof data[firstKey] === 'undefined') { data[firstKey] = {}; } if (rest.length) { data[firstKey] = setKeys(data[firstKey], [rest.join('/')]); } }) return data; } /** * Mock some data */ const getUserData = (req, res, next) => { if (typeof req.body === 'undefined') { req.body = {}; } req.body = { variableY: { someName: 23 }, variableZ: { name: 3, type: { id: 5, typeName: 'something', tags: ['a', 'b', 'c'] } } }; console.log('Middleware 1 getUserData'); next(); } /** * 1. Setup our middleware for checking keys * "requiredKeys" is an array of strings */ const middlewareSetKeys = (requiredKeys, wrappedMiddleware) => { return (req, res, next) => { console.log('Middleware 2 middlewareSetKeys'); if (typeof req.body === "undefined") { console.log('Leaving Middleware 2 since we don\\'t have req.body'); next(); } /** * Update "req.body" with keys that we want to have available * in our next middleware */ req.body = setKeys(req.body, requiredKeys); if (typeof wrappedMiddleware === 'function') { return wrappedMiddleware.call(this, req, res, next); } else { next(); } } } /** * 2. Let's assume a "user status" situation * 2.1. We need userInfo from database * 2.2. Some info won't be retrieved, unless the user accesed some parts of the website to trigger some mechanisms that allows those fields to be exposed, therefore the lack of keys * 2.3. But we know those keys/objects, and we still want to be present so our code won't crash. */ // lets call our getUserData app.get( '/', // this path is for some userInfo getUserData, // this returns userInfo and appends it to `req.data` middlewareSetKeys([ 'userActivity/daily/jobs', // these won't exist in getUserData because the user is lazy and he didn't apply for any JOBS 'userStatus/active/two-weeks-ago', // these won't exist in getUserData because the user joined two days ago. BUT WE STILL NEED IT coz reazons. ]), // We set our desired-later-to-use keys (req, res, next) => { /** * 3. Now our req.body will have our keys * even if they didn't exist in the getUserData middleware */ console.log('Middleware 3 Your middleware'); console.log(req.body); res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify(req.body, null, 2)) }) app.listen(port, () => console.log(`Example app listening on port ${port}!`)) 

you can use express validator https://www.npmjs.com/package/express-validator to validate incoming request.Then add this to your controller where a,b,c ,d are parameters you want to valaidate

 const nonEmptyFields = ['a', 'b', 'c', 'd']; nonEmptyFields.forEach(field => req.assert(field, `${field} cannot be blank`).notEmpty()); const errors = req.validationErrors(); if (errors) { return res.status(400).send(errors); } 

for validating a field inside a field you can try doing this

typeof(req.body && req.body.name !== undefined)

A solution will be to set a default empty object to replace undefined at a parent level:

// checking for body.variableX.variableZ with object destructuring ES6
const {body = {}} = request;
const {variableX = {}, variableY} = body;
const {variableZ} = variableX.variableZ;

// or prior ES6
var body = request.body || {};
var variableX = body.variableX || {};
var variableY = variableX.variableY;

// or in a statement
var variableY = request.body && request.body.variableX ? request.body.variableX.variableY : undefined;

Based on that you can create your own function like getValue(request, 'body.variableX.variableY') to return null if any parent or the end value is undefined:

// asumes the value in the path is either object or undefined
function getValue(rootObj, path = '') {
    const parts = key.split('.');
    let value = rootObj || {};
    let part;
    while ((part = parts.shift()) && value !== null) {
        value = value[part] || null;
    }
    return value;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM