├── .gitattributes ├── .gitignore ├── README.md ├── index.js └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test 3 | .git 4 | .gitignore 5 | .gitattributes -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Synopsis 2 | A simple to use Yummly API wrapper for Nodejs. 3 | 4 | ## Installation 5 | 6 | $ npm install ws-yummly 7 | 8 | ## Usage 9 | 10 | ```js 11 | var Yummly = require("ws-yummly"); 12 | 13 | Yummly.config({ 14 | app_id : 'YOUR_APP_ID', 15 | app_key : 'YOUR_APP_KEY', 16 | }); 17 | ``` 18 | ## API 19 | 20 | ### getMeta(type, [display]) 21 | 22 | **type**: holiday, allergy, course, cuisine, diet, ingredient 23 | 24 | **display**: clean, raw (default) 25 | ```js 26 | Yummly.getMeta('diet', 'raw').then(function (meta) { 27 | console.log(meta); 28 | }).catch(function (error) { 29 | console.log(error); 30 | }); 31 | 32 | # output 33 | [ { id: '388', 34 | shortDescription: 'Lacto vegetarian', 35 | longDescription: 'Lacto vegetarian', 36 | searchValue: '388^Lacto vegetarian', 37 | type: 'diet', 38 | localesAvailableIn: [ 'en-US' ] 39 | .... 40 | .... 41 | }] 42 | ``` 43 | 44 | ### maxTotalTimeInSeconds(number) 45 | Maximum time in seconds it may take to prepare the meal 46 | ### maxResults(number) 47 | Maximum results from the API 48 | ### start(number) 49 | Start getting recipes from start number 50 | ### minRating(number) 51 | Minumum rating a recipe can have (1-5) 52 | ### maxRating(number) 53 | Maximum rating a recipe can have (1-5) 54 | 55 | *maxRating value will be ignored when lower then minRating* 56 | ### allowedIngredients(string or array) 57 | for a list of allowed ingredients use: 58 | ```js 59 | Yummly.getMeta('ingredients') 60 | ``` 61 | ### excludedIngredients(string or array) 62 | for a list of allowed ingredients use: 63 | ```js 64 | Yummly.getMeta('ingredients') 65 | ``` 66 | ### allowedAllergies(string or array) 67 | for a list of possible values use: 68 | ```js 69 | Yummly.getMeta('allergy') 70 | ``` 71 | ### excludedAllergies(string or array) 72 | for a list of possible values use: 73 | ```js 74 | Yummly.getMeta('allergy') 75 | ``` 76 | ### allowedDiets(string or array) 77 | for a list of possible values use: 78 | ```js 79 | Yummly.getMeta('diet') 80 | ``` 81 | ### excludedDiets(string or array) 82 | for a list of possible values use: 83 | ```js 84 | Yummly.getMeta('diet') 85 | ``` 86 | ### allowedCuisines(string or array) 87 | for a list of possible values use: 88 | ```js 89 | Yummly.getMeta('cuisine') 90 | ``` 91 | ### excludedCuisines(string or array) 92 | for a list of possible values use: 93 | ```js 94 | Yummly.getMeta('cuisine') 95 | ``` 96 | ### allowedCourses(string or array) 97 | for a list of possible values use: 98 | ```js 99 | Yummly.getMeta('course') 100 | ``` 101 | ### excludedCourses(string or array) 102 | for a list of possible values use: 103 | ```js 104 | Yummly.getMeta('course') 105 | ``` 106 | ### allowedHolidays(string or array) 107 | for a list of possible values use: 108 | ```js 109 | Yummly.getMeta('holiday') 110 | ``` 111 | ### excludedHolidays(string or array) 112 | for a list of possible values use: 113 | ```js 114 | Yummly.getMeta('holiday') 115 | ``` 116 | ### requirePictures(boolean) 117 | Wheter or not the recipes must have a image attached 118 | ### paginate(number) 119 | Paginates the result array into chunks 120 | ```js 121 | Yummly.query('pineapple').maxResults(40).paginate(10).get().then(function(resp){ 122 | console.log(resp.matches); // 4 arrays within each 10 recipes 123 | }); 124 | ``` 125 | ### getURL() 126 | Returns the current generated url 127 | ```js 128 | var url = Yummly.query('pineapple').maxResults(40).paginate(10).excludedHolidays('halloween').getURL(); 129 | 130 | // url: https://api.yummly.com/v1/api/recipes?_app_id=YOUR_APP_ID&_app_key=YOUR_APP_KEY&q=pineapple&maxResult=40&excludedHoliday[]=holiday^holiday-halloween 131 | ``` 132 | ### getWithURL() 133 | Returns the current generated url 134 | ```js 135 | var url = https://api.yummly.com/v1/api/recipes?_app_id=YOUR_APP_ID&_app_key=YOUR_APP_KEY&q=pineapple&maxResult=10; 136 | 137 | Yummly.getWithURL(url).then(function(resp){ 138 | console.log(resp); 139 | })); 140 | 141 | // url: https://api.yummly.com/v1/api/recipes?_app_id=YOUR_APP_ID&_app_key=YOUR_APP_KEY&q=pineapple&maxResult=40&excludedHoliday[]=holiday^holiday-halloween 142 | ``` 143 | ### getSettings() 144 | Returns the current generated setting object 145 | ```js 146 | var settings = Yummly.query('pineapple').maxResults(40).paginate(10).excludedHolidays('halloween').getURL(); 147 | 148 | // settings: { maxResults: 40, paginate: 10, excludedHolidays: [ 'halloween' ] } 149 | ``` 150 | ### getDetails() 151 | Returns details for recipes 152 | ```js 153 | var recipes = [ 154 | 'Apple-Walnut-Cranberry-Salad-898353', 155 | 'Heavenly-Strawberry_s-650499', 156 | 'Chopped-Taco-Mason-Jar-Salad-1266468' 157 | ]; 158 | 159 | Yummly.getDetails(recipes).then(function (resp) { 160 | resp.forEach(function (recipe) { 161 | console.log(recipe.name); 162 | }); 163 | }).catch(function (error) { 164 | console.log(error); 165 | }); 166 | ``` 167 | ### get() 168 | Returns a promise with possible results 169 | ```js 170 | Yummly.query('coconut').get().then(function (resp) { 171 | resp.forEach(function (recipe) { 172 | console.log(recipe.name); 173 | }); 174 | }).catch(function (error) { 175 | console.log(error); 176 | }); 177 | ``` 178 | ## Example 179 | ```js 180 | Yummly.query('pineapple') 181 | .maxTotalTimeInSeconds(1400) 182 | .maxResults(20) 183 | .allowedDiets(['Pescetarian', 'Vegan']) 184 | .allowedCuisines(['asian']) 185 | .minRating(3) 186 | .get() 187 | .then(function(resp){ 188 | resp.matches.forEach(function(recipe){ 189 | console.log(recipe.recipeName); 190 | }); 191 | }); 192 | 193 | # Thai Shrimp and Pineapple Curry 194 | # Thai Pineapple Fried Rice 195 | # Asian Pineapple Sauce 196 | # Thai Pineapple Fried Rice 197 | # Asian Caramelized Pineapple 198 | # Thai Pineapple Fried Rice 199 | # ... 200 | ``` 201 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Needle = require('needle'); 2 | var Q = require('q'); 3 | var fs = require('fs'); 4 | var chunk = require('lodash.chunk'); 5 | 6 | var Config = { 7 | "endpoints" : { 8 | "recipe" : "https://api.yummly.com/v1/api/recipe/", 9 | "recipes" : "https://api.yummly.com/v1/api/recipes", 10 | "meta" : "https://api.yummly.com/v1/api/metadata/" 11 | } 12 | }; 13 | 14 | exports.config = function (input) { 15 | 16 | if(input.app_id && input.app_key){ 17 | 18 | Config.app_id = input.app_id; 19 | Config.app_key = input.app_key; 20 | Config.app_id_query = '?_app_id=' + input.app_id + '&_app_key=' + input.app_key; 21 | 22 | } 23 | } 24 | 25 | const _RECIPE = Config.endpoints.recipe; 26 | const _RECIPES = Config.endpoints.recipes; 27 | 28 | var set_metadata = function(meta, data) { 29 | return data; 30 | }; 31 | 32 | var validMetaRequests = ['holiday', 'allergy', 'course', 'cuisine', 'diet', 'ingredient']; 33 | 34 | function fetchMeta(url, path, type, display) { 35 | 36 | var deferred = Q.defer(); 37 | 38 | // fetch result from yummly 39 | Needle.get(url, function(error, response) { 40 | 41 | if (!error && response.statusCode == 200) { 42 | 43 | var result = eval(response.body); 44 | var items = []; 45 | 46 | if (display == 'raw') { 47 | deferred.resolve(result); 48 | } else { 49 | 50 | if (type != 'allergy') { 51 | var pattern = type + '\\^' + type + '\-'; 52 | for (var i = 0; i < result.length; i++) { 53 | var regex = new RegExp(pattern, 'ig'); 54 | var clean = result[i].searchValue.replace(regex, ''); 55 | items.push(clean); 56 | }; 57 | 58 | deferred.resolve(items.sort()); 59 | } else { 60 | for (var i = 0; i < result.length; i++) { 61 | items.push(result[i].shortDescription); 62 | }; 63 | deferred.resolve(items.sort()); 64 | } 65 | 66 | } 67 | 68 | } else { 69 | console.log(response); 70 | deferred.reject(new Error('Invalid meta request')); 71 | } 72 | 73 | }); 74 | 75 | return deferred.promise; 76 | } 77 | 78 | 79 | exports.query = function(input) { 80 | 81 | var url = Config.endpoints.recipes + Config.app_id_query; 82 | var settings = {}; 83 | 84 | if (input && input.length > 0 && typeof input == 'string') { 85 | url += '&q=' + encodeURIComponent(input); 86 | } 87 | 88 | return { 89 | 90 | maxTotalTimeInSeconds: function(input) { 91 | if (input && typeof input == 'number') { 92 | url += '&maxTotalTimeInSeconds=' + input; 93 | settings.maxTotalTimeInSeconds = input; 94 | } 95 | 96 | return this; 97 | }, 98 | 99 | maxResults: function(input) { 100 | 101 | if (input && typeof input == 'number') { 102 | url += '&maxResult=' + input; 103 | settings.maxResults = input; 104 | } 105 | 106 | return this; 107 | }, 108 | 109 | start: function(input) { 110 | 111 | if (input && typeof input === 'number') { 112 | url += '&start=' + input; 113 | settings.start = input; 114 | } 115 | 116 | return this; 117 | }, 118 | 119 | minRating: function(input) { 120 | 121 | if (typeof input == 'number') { 122 | 123 | if(settings.maxRating && input < settings.maxRating){ 124 | url += '&minRating=' + input; 125 | } 126 | else if(!settings.minRating){ 127 | url += '&minRating=' + input; 128 | settings.minRating = input; 129 | } 130 | 131 | } 132 | 133 | return this; 134 | }, 135 | 136 | maxRating: function(input) { 137 | 138 | if (typeof input == 'number') { 139 | 140 | if(settings.minRating && input > settings.minRating){ 141 | url += '&maxRating=' + input; 142 | settings.maxRating = input; 143 | } 144 | else if(!settings.minRating){ 145 | url += '&maxRating=' + input; 146 | settings.maxRating = input; 147 | } 148 | 149 | 150 | } 151 | 152 | return this; 153 | }, 154 | 155 | allowedIngredients: function(input) { 156 | 157 | if (input && input.length) { 158 | 159 | settings.allowedIngredients = settings.allowedIngredients || []; 160 | 161 | if (input instanceof Array) { 162 | 163 | input.forEach(function(item) { 164 | url += '&allowedIngredient[]=' + encodeURIComponent(item); 165 | settings.allowedIngredients.push(item); 166 | 167 | }); 168 | } else if (typeof input == 'string') { 169 | 170 | url += '&allowedIngredient[]=' + encodeURIComponent(input); 171 | settings.allowedIngredients.push(input); 172 | 173 | } 174 | 175 | } 176 | 177 | return this; 178 | }, 179 | excludedIngredients: function(input) { 180 | 181 | if (input && input.length) { 182 | 183 | settings.excludedIngredients = settings.excludedIngredients || []; 184 | 185 | if (input instanceof Array) { 186 | input.forEach(function(item) { 187 | url += '&excludedIngredient[]=' + encodeURIComponent(item); 188 | settings.excludedIngredients.push(item); 189 | }); 190 | } else if (typeof input == 'string') { 191 | url += '&excludedIngredient[]=' + encodeURIComponent(input); 192 | settings.excludedIngredients.push(input); 193 | } 194 | } 195 | 196 | return this; 197 | }, 198 | allowedAllergies: function(input) { 199 | if (input && input.length) { 200 | 201 | settings.allowedAllergies = settings.allowedAllergies || []; 202 | 203 | if (input instanceof Array) { 204 | input.forEach(function(item) { 205 | url += '&allowedAllergy[]=' + encodeURIComponent(item); 206 | settings.allowedAllergies.push(item); 207 | }); 208 | } else if (typeof input == 'string') { 209 | url += '&allowedAllergy[]=' + encodeURIComponent(input); 210 | settings.allowedAllergies.push(input); 211 | } 212 | } 213 | 214 | return this; 215 | }, 216 | excludedAllergies: function(input) { 217 | if (input && input.length) { 218 | 219 | settings.excludedAllergies = settings.excludedAllergies || []; 220 | 221 | if (input instanceof Array) { 222 | input.forEach(function(item) { 223 | url += '&excludedAllergy[]=' + encodeURIComponent(item); 224 | settings.excludedAllergies.push(input); 225 | }); 226 | } else if (typeof input == 'string') { 227 | url += '&excludedAllergy[]=' + encodeURIComponent(input); 228 | settings.excludedAllergies.push(input); 229 | } 230 | } 231 | 232 | return this; 233 | }, 234 | allowedDiets: function(input) { 235 | 236 | if (input && input.length) { 237 | 238 | settings.allowedDiets = settings.allowedDiets || []; 239 | 240 | if (input instanceof Array) { 241 | input.forEach(function(item) { 242 | url += '&allowedDiet[]=' + encodeURIComponent(item); 243 | settings.allowedDiets.push(item); 244 | }); 245 | } else if (typeof input == 'string') { 246 | url += '&allowedDiet[]=' + encodeURIComponent(input); 247 | settings.allowedDiets.push(input); 248 | } 249 | } 250 | 251 | return this; 252 | }, 253 | excludedDiets: function(input) { 254 | if (input && input.length) { 255 | 256 | settings.excludedDiets = settings.excludedDiets || []; 257 | 258 | if (input instanceof Array) { 259 | input.forEach(function(item) { 260 | url += '&excludedDiet[]=' + encodeURIComponent(item); 261 | settings.excludedDiets.push(item); 262 | }); 263 | } else if (typeof input == 'string') { 264 | url += '&excludedDiet[]=' + encodeURIComponent(input); 265 | settings.excludedDiets.push(input); 266 | } 267 | } 268 | 269 | return this; 270 | }, 271 | allowedCuisines: function(input) { 272 | if (input && input.length) { 273 | settings.allowedCuisines = settings.allowedCuisines || []; 274 | if (input instanceof Array) { 275 | input.forEach(function(item) { 276 | url += '&allowedCuisine[]=cuisine^cuisine-' + encodeURIComponent(item); 277 | settings.allowedCuisines.push(item); 278 | }); 279 | } else if (typeof input == 'string') { 280 | url += '&allowedCuisine[]=cuisine^cuisine-' + encodeURIComponent(input); 281 | settings.allowedCuisines.push(input); 282 | } 283 | } 284 | 285 | return this; 286 | }, 287 | excludedCuisines: function(input) { 288 | if (input && input.length) { 289 | settings.excludedCuisines = settings.excludedCuisines || []; 290 | if (input instanceof Array) { 291 | input.forEach(function(item) { 292 | url += '&excludedCuisine[]=cuisine^cuisine-' + encodeURIComponent(item); 293 | settings.excludedCuisines.push(item); 294 | }); 295 | } else if (typeof input == 'string') { 296 | url += '&excludedCuisine[]=cuisine^cuisine-' + encodeURIComponent(input); 297 | settings.excludedCuisines.push(input); 298 | } 299 | } 300 | 301 | return this; 302 | }, 303 | allowedCourses: function(input) { 304 | if (input && input.length) { 305 | settings.allowedCourses = settings.allowedCourses || []; 306 | if (input instanceof Array) { 307 | input.forEach(function(item) { 308 | url += '&allowedCourse[]=course^course-' + encodeURIComponent(item); 309 | settings.allowedCourses.push(item); 310 | }); 311 | } else if (typeof input == 'string') { 312 | url += '&allowedCourse[]=course^course-' + encodeURIComponent(input); 313 | settings.allowedCourses.push(input); 314 | } 315 | } 316 | 317 | return this; 318 | }, 319 | excludedCourses: function(input) { 320 | if (input && input.length) { 321 | settings.excludedCourses = settings.excludedCourses || []; 322 | if (input instanceof Array) { 323 | input.forEach(function(item) { 324 | url += '&excludedCourse[]=course^course-' + encodeURIComponent(item); 325 | settings.excludedCourses.push(item); 326 | }); 327 | } else if (typeof input == 'string') { 328 | url += '&excludedCourse[]=course^course-' + encodeURIComponent(input); 329 | settings.excludedCourses.push(input); 330 | } 331 | } 332 | 333 | return this; 334 | }, 335 | allowedHolidays: function(input) { 336 | 337 | if (input && input.length) { 338 | 339 | settings.allowedHolidays = settings.allowedHolidays || []; 340 | 341 | if (input instanceof Array) { 342 | 343 | for (var i = 0; i < input.length; i++) { 344 | 345 | 346 | url += '&allowedHoliday[]=holiday^holiday-' + encodeURIComponent(input[i]); 347 | settings.allowedHolidays.push(input[i]); 348 | 349 | 350 | }; 351 | 352 | } else if (typeof input == 'string') { 353 | 354 | url += '&allowedHoliday[]=holiday^holiday-' + encodeURIComponent(input); 355 | settings.allowedHolidays.push(input); 356 | 357 | } 358 | } 359 | 360 | return this; 361 | }, 362 | excludedHolidays: function(input) { 363 | 364 | if (input && input.length) { 365 | 366 | settings.excludedHolidays = settings.excludedHolidays || []; 367 | if (input instanceof Array) { 368 | input.forEach(function(item) { 369 | url += '&excludedHoliday[]=holiday^holiday-' + encodeURIComponent(item); 370 | settings.excludedHolidays.push(item); 371 | }); 372 | } else if (typeof input == 'string') { 373 | url += '&excludedHoliday[]=holiday^holiday-' + encodeURIComponent(input); 374 | settings.excludedHolidays.push(input); 375 | } 376 | 377 | } 378 | 379 | return this; 380 | }, 381 | requirePictures: function(input) { 382 | if (input == true) { 383 | url += '&requirePictures=true' 384 | } 385 | else if(input == false){ 386 | settings.requirePictures = input; 387 | } 388 | 389 | return this; 390 | }, 391 | paginate: function(input) { 392 | 393 | if (input && typeof input == 'number') { 394 | settings.paginate = input; 395 | } 396 | 397 | return this; 398 | }, 399 | getURL: function() { 400 | return url; 401 | }, 402 | getSettings: function() { 403 | return settings; 404 | }, 405 | get: function() { 406 | 407 | var deferred = Q.defer(); 408 | 409 | if(!Config.app_id || !Config.app_id){ 410 | deferred.reject(new Error('Invalid API config settings')); 411 | return deferred.promise; 412 | } 413 | 414 | Needle.get(url, function(error, response) { 415 | 416 | url = Config.endpoints.recipes + Config.app_id_query; 417 | 418 | if (!error && response.statusCode == 200) { 419 | 420 | if (settings.paginate) { 421 | 422 | response.body.matches = chunk(response.body.matches, settings.paginate); 423 | deferred.resolve(response.body); 424 | 425 | } else { 426 | deferred.resolve(response.body); 427 | } 428 | 429 | } else { 430 | deferred.reject(new Error(error)); 431 | } 432 | 433 | }); 434 | 435 | return deferred.promise; 436 | 437 | } 438 | } 439 | 440 | } 441 | 442 | exports.getByURL = function(url) { 443 | 444 | var deferred = Q.defer(); 445 | 446 | if(!Config.app_id || !Config.app_id){ 447 | deferred.reject(new Error('Invalid API config settings')); 448 | return deferred.promise; 449 | } 450 | 451 | Needle.get(url, function(error, response) { 452 | 453 | if (!error && response.statusCode == 200) { 454 | 455 | deferred.resolve(response.body); 456 | 457 | } else { 458 | deferred.reject(new Error(error)); 459 | } 460 | 461 | }); 462 | 463 | return deferred.promise; 464 | 465 | } 466 | 467 | exports.getMeta = function(input, display) { 468 | 469 | var deferred = Q.defer(); 470 | var display = display || 'raw'; 471 | 472 | if(!Config.app_id || !Config.app_id){ 473 | deferred.reject(new Error('Incomplete API settings')); 474 | return deferred.promise; 475 | } 476 | 477 | if(!~validMetaRequests.indexOf(input)){ 478 | deferred.reject(new Error('Invalid meta value')); 479 | return deferred.promise; 480 | } 481 | 482 | // place URL in fetchMeta 483 | var url = Config.endpoints.meta + input + Config.app_id_query; 484 | var path = './meta/' + input + '.json'; 485 | 486 | fs.stat(path, function(err, stat) { 487 | 488 | if (err == null) { 489 | fs.readFile(path, function(err, obj) { 490 | // console.log(obj || err); 491 | deferred.resolve(obj); 492 | }); 493 | } else if (err.code == 'ENOENT') { 494 | fetchMeta(url, path, input, display).then(function(resp) { 495 | // console.log(resp); 496 | deferred.resolve(resp); 497 | }).catch(function(error) { 498 | deferred.reject(error); 499 | }); 500 | } else { 501 | deferred.reject(new Error('Invalid something')); 502 | } 503 | 504 | }); 505 | 506 | return deferred.promise; 507 | 508 | } 509 | 510 | function getDetails(id){ 511 | 512 | var deferred = Q.defer(); 513 | var url = _RECIPE + id + Config.app_id_query; 514 | 515 | Needle.get(url, function(error, response) { 516 | 517 | if (!error && response.statusCode == 200) { 518 | 519 | deferred.resolve(response.body); 520 | 521 | } 522 | 523 | else if(error){ 524 | deferred.reject(new Error(error)); 525 | } 526 | 527 | }); 528 | 529 | return deferred.promise; 530 | } 531 | 532 | exports.getDetails = function (input) { 533 | 534 | var deferred = Q.defer(); 535 | var que = []; 536 | 537 | if(input instanceof Array){ 538 | 539 | for (var i = 0; i < input.length; i++) { 540 | 541 | que.push(getDetails(input[i])); 542 | 543 | }; 544 | 545 | }else if(typeof input == 'string'){ 546 | que.push(getDetails(input)); 547 | } 548 | 549 | return Q.all(que); 550 | 551 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ws-yummly", 3 | "version": "1.0.23", 4 | "description": "A wrapper for the Yummly recipe API.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "keywords": [ 10 | "yummly", 11 | "wrapper", 12 | "recipes", 13 | "api" 14 | ], 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/wstam88/yummly.git" 18 | }, 19 | "author": "Wesley Stam ", 20 | "license": "MIT", 21 | "dependencies": { 22 | "fs-extra": "^0.26.4", 23 | "lodash.chunk": "^4.0.0", 24 | "needle": "^0.11.0", 25 | "q": "^1.4.1" 26 | }, 27 | "devDependencies": { 28 | "chai": "^3.4.1", 29 | "chai-as-promised": "^5.2.0", 30 | "mocha": "^2.3.4", 31 | "validator": "^4.5.1" 32 | } 33 | } 34 | --------------------------------------------------------------------------------