[{"data":1,"prerenderedAt":2027},["ShallowReactive",2],{"author-leo-martin":3,"author-articles-leo-martin":22,"authors":1711},{"id":4,"title":5,"body":6,"description":10,"extension":13,"meta":14,"name":15,"navigation":16,"path":17,"readingTime":18,"seo":19,"stem":20,"__hash__":21},"authors\u002Fauthors\u002Fleo-martin.md","Software Engineer",{"type":7,"value":8,"toc":9},"minimark",[],{"title":10,"searchDepth":11,"depth":11,"links":12},"",2,[],"md",{},"Léo Martin",true,"\u002Fauthors\u002Fleo-martin",1,{"title":5,"description":10},"authors\u002Fleo-martin","eYxCHkRgbGDV7shKdTA9s7Tu0zGV4yDGFoKR5MHQntY",[23,633],{"id":24,"title":25,"author":26,"body":27,"date":623,"description":624,"extension":13,"lang":625,"meta":626,"navigation":16,"path":627,"published":16,"readingTime":149,"seo":628,"stem":629,"tags":630,"__hash__":632},"articles\u002Farticles\u002F2022-11-17-nodejs-test-runner.md","NodeJS Test Runner","leo-martin",{"type":7,"value":28,"toc":619},[29,37,44,50,55,61,153,159,388,402,408,435,531,542,551,557,570,582,586,592,595,615],[30,31,32,33],"p",{},"Avec NodeJS v18, il est possible d’écrire ses tests sans librairie externe grâce au module\n",[34,35,36],"code",{},"node:test",[30,38,39,40,43],{},"Ce nouveau module permet de définir un jeu de test, que l’on pourra jouer via la commande\n",[34,41,42],{},"[node --test](https:\u002F\u002Fnodejs.org\u002Fapi\u002Ftest.html#running-tests-from-the-command-line)",".",[30,45,46,47,43],{},"Toujours dans l’idée d’utiliser le moins de dépendances externes, un bon module pour faire des\nassertions est ",[34,48,49],{},"[node:assert\u002Fstrict](https:\u002F\u002Fnodejs.org\u002Fapi\u002Fassert.html#strict-assertion-mode)",[51,52,54],"h2",{"id":53},"en-prenant-un-exemple","En prenant un exemple",[30,56,57,58],{},"Dans un fichier ",[34,59,60],{},"index.js",[62,63,67],"pre",{"className":64,"code":65,"language":66,"meta":10,"style":10},"language-ts shiki shiki-themes github-light github-dark","export function canRegister({ age, country, job }) {\n  return age >= 18 && country === \"US\" && job === \"engineer\";\n}\n","ts",[34,68,69,106,147],{"__ignoreMap":10},[70,71,73,77,80,84,88,92,95,98,100,103],"span",{"class":72,"line":18},"line",[70,74,76],{"class":75},"szBVR","export",[70,78,79],{"class":75}," function",[70,81,83],{"class":82},"sScJk"," canRegister",[70,85,87],{"class":86},"sVt8B","({ ",[70,89,91],{"class":90},"s4XuR","age",[70,93,94],{"class":86},", ",[70,96,97],{"class":90},"country",[70,99,94],{"class":86},[70,101,102],{"class":90},"job",[70,104,105],{"class":86}," }) {\n",[70,107,108,111,114,117,121,124,127,130,134,136,139,141,144],{"class":72,"line":11},[70,109,110],{"class":75},"  return",[70,112,113],{"class":86}," age ",[70,115,116],{"class":75},">=",[70,118,120],{"class":119},"sj4cs"," 18",[70,122,123],{"class":75}," &&",[70,125,126],{"class":86}," country ",[70,128,129],{"class":75},"===",[70,131,133],{"class":132},"sZZnC"," \"US\"",[70,135,123],{"class":75},[70,137,138],{"class":86}," job ",[70,140,129],{"class":75},[70,142,143],{"class":132}," \"engineer\"",[70,145,146],{"class":86},";\n",[70,148,150],{"class":72,"line":149},3,[70,151,152],{"class":86},"}\n",[30,154,155,156],{},"En utilisant le test runner de Node, on peut écrire le test suivant dans le fichier ",[34,157,158],{},"index.test.js",[62,160,162],{"className":64,"code":161,"language":66,"meta":10,"style":10},"import assert from \"node:assert\u002Fstrict\";\nimport { describe, it } from \"node:test\";\n\nimport { canRegister } from \".\u002Findex.js\";\n\ndescribe(\"canRegister\", () => {\n  it(\"returns true when age is 18, country is US, and job is engineer\", () => {\n    const result = canRegister({ age: 18, country: \"US\", job: \"engineer\" });\n    assert.strictEqual(result, true);\n  });\n\n  it(\"returns false when age is 17, country is US, and job is engineer\", () => {\n    const result = canRegister({ age: 17, country: \"US\", job: \"engineer\" });\n    assert.strictEqual(result, false);\n  });\n});\n",[34,163,164,180,194,199,214,219,240,257,292,310,316,321,337,363,377,382],{"__ignoreMap":10},[70,165,166,169,172,175,178],{"class":72,"line":18},[70,167,168],{"class":75},"import",[70,170,171],{"class":86}," assert ",[70,173,174],{"class":75},"from",[70,176,177],{"class":132}," \"node:assert\u002Fstrict\"",[70,179,146],{"class":86},[70,181,182,184,187,189,192],{"class":72,"line":11},[70,183,168],{"class":75},[70,185,186],{"class":86}," { describe, it } ",[70,188,174],{"class":75},[70,190,191],{"class":132}," \"node:test\"",[70,193,146],{"class":86},[70,195,196],{"class":72,"line":149},[70,197,198],{"emptyLinePlaceholder":16},"\n",[70,200,202,204,207,209,212],{"class":72,"line":201},4,[70,203,168],{"class":75},[70,205,206],{"class":86}," { canRegister } ",[70,208,174],{"class":75},[70,210,211],{"class":132}," \".\u002Findex.js\"",[70,213,146],{"class":86},[70,215,217],{"class":72,"line":216},5,[70,218,198],{"emptyLinePlaceholder":16},[70,220,222,225,228,231,234,237],{"class":72,"line":221},6,[70,223,224],{"class":82},"describe",[70,226,227],{"class":86},"(",[70,229,230],{"class":132},"\"canRegister\"",[70,232,233],{"class":86},", () ",[70,235,236],{"class":75},"=>",[70,238,239],{"class":86}," {\n",[70,241,243,246,248,251,253,255],{"class":72,"line":242},7,[70,244,245],{"class":82},"  it",[70,247,227],{"class":86},[70,249,250],{"class":132},"\"returns true when age is 18, country is US, and job is engineer\"",[70,252,233],{"class":86},[70,254,236],{"class":75},[70,256,239],{"class":86},[70,258,260,263,266,269,271,274,277,280,283,286,289],{"class":72,"line":259},8,[70,261,262],{"class":75},"    const",[70,264,265],{"class":119}," result",[70,267,268],{"class":75}," =",[70,270,83],{"class":82},[70,272,273],{"class":86},"({ age: ",[70,275,276],{"class":119},"18",[70,278,279],{"class":86},", country: ",[70,281,282],{"class":132},"\"US\"",[70,284,285],{"class":86},", job: ",[70,287,288],{"class":132},"\"engineer\"",[70,290,291],{"class":86}," });\n",[70,293,295,298,301,304,307],{"class":72,"line":294},9,[70,296,297],{"class":86},"    assert.",[70,299,300],{"class":82},"strictEqual",[70,302,303],{"class":86},"(result, ",[70,305,306],{"class":119},"true",[70,308,309],{"class":86},");\n",[70,311,313],{"class":72,"line":312},10,[70,314,315],{"class":86},"  });\n",[70,317,319],{"class":72,"line":318},11,[70,320,198],{"emptyLinePlaceholder":16},[70,322,324,326,328,331,333,335],{"class":72,"line":323},12,[70,325,245],{"class":82},[70,327,227],{"class":86},[70,329,330],{"class":132},"\"returns false when age is 17, country is US, and job is engineer\"",[70,332,233],{"class":86},[70,334,236],{"class":75},[70,336,239],{"class":86},[70,338,340,342,344,346,348,350,353,355,357,359,361],{"class":72,"line":339},13,[70,341,262],{"class":75},[70,343,265],{"class":119},[70,345,268],{"class":75},[70,347,83],{"class":82},[70,349,273],{"class":86},[70,351,352],{"class":119},"17",[70,354,279],{"class":86},[70,356,282],{"class":132},[70,358,285],{"class":86},[70,360,288],{"class":132},[70,362,291],{"class":86},[70,364,366,368,370,372,375],{"class":72,"line":365},14,[70,367,297],{"class":86},[70,369,300],{"class":82},[70,371,303],{"class":86},[70,373,374],{"class":119},"false",[70,376,309],{"class":86},[70,378,380],{"class":72,"line":379},15,[70,381,315],{"class":86},[70,383,385],{"class":72,"line":384},16,[70,386,387],{"class":86},"});\n",[389,390,391],"blockquote",{},[30,392,393,394,401],{},"À noter qu’il est possible de skip un test avec la syntaxe suivante\n",[395,396,400],"a",{"href":397,"rel":398},"https:\u002F\u002Fnodejs.org\u002Fapi\u002Ftest.html#testname-options-fn",[399],"nofollow","it([name], { skip: true }[, fn])"," ou en\najoutant it.skip",[30,403,404,405],{},"Et on exécute le test avec la commande ",[34,406,407],{},"node --test",[389,409,410],{},[30,411,412,414,415,418,419,422,423,426,427,432,433,43],{},[34,413,407],{}," va chercher récursivement les fichiers dont le nom est ",[34,416,417],{},"test.js"," ,\n",[34,420,421],{},"monfichier.test.js"," ou encore ",[34,424,425],{},"test-monfichier.js","\n(",[395,428,431],{"href":429,"rel":430},"https:\u002F\u002Fnodejs.org\u002Fapi\u002Ftest.html#test-runner-execution-model",[399],"plus de détail dans la doc","). Il\nest aussi possible de passer un fichier en argument à la commande ",[34,434,407],{},[62,436,440],{"className":437,"code":438,"language":439,"meta":10,"style":10},"language-sh shiki shiki-themes github-light github-dark","TAP version 13\n# Subtest: \u002Fnode-test-runner\u002Findex.test.js\nok 1 - \u002Fnode-test-runner\u002Findex.test.js\n  ---\n  duration_ms: 44.544125\n  ...\n1..1\n# tests 1\n# pass 1\n# fail 0\n# cancelled 0\n# skipped 0\n# todo 0\n# duration_ms 47.909458\n\n","sh",[34,441,442,453,459,473,478,486,491,496,501,506,511,516,521,526],{"__ignoreMap":10},[70,443,444,447,450],{"class":72,"line":18},[70,445,446],{"class":82},"TAP",[70,448,449],{"class":132}," version",[70,451,452],{"class":119}," 13\n",[70,454,455],{"class":72,"line":11},[70,456,458],{"class":457},"sJ8bj","# Subtest: \u002Fnode-test-runner\u002Findex.test.js\n",[70,460,461,464,467,470],{"class":72,"line":149},[70,462,463],{"class":82},"ok",[70,465,466],{"class":119}," 1",[70,468,469],{"class":132}," -",[70,471,472],{"class":132}," \u002Fnode-test-runner\u002Findex.test.js\n",[70,474,475],{"class":72,"line":201},[70,476,477],{"class":82},"  ---\n",[70,479,480,483],{"class":72,"line":216},[70,481,482],{"class":82},"  duration_ms:",[70,484,485],{"class":119}," 44.544125\n",[70,487,488],{"class":72,"line":221},[70,489,490],{"class":119},"  ...\n",[70,492,493],{"class":72,"line":242},[70,494,495],{"class":82},"1..1\n",[70,497,498],{"class":72,"line":259},[70,499,500],{"class":457},"# tests 1\n",[70,502,503],{"class":72,"line":294},[70,504,505],{"class":457},"# pass 1\n",[70,507,508],{"class":72,"line":312},[70,509,510],{"class":457},"# fail 0\n",[70,512,513],{"class":72,"line":318},[70,514,515],{"class":457},"# cancelled 0\n",[70,517,518],{"class":72,"line":323},[70,519,520],{"class":457},"# skipped 0\n",[70,522,523],{"class":72,"line":339},[70,524,525],{"class":457},"# todo 0\n",[70,527,528],{"class":72,"line":365},[70,529,530],{"class":457},"# duration_ms 47.909458\n",[30,532,533,534,538,541],{},"On obtient un retour au format TAP (Test Anything Protocol)\n",[395,535],{"href":536,"rel":537},"https:\u002F\u002Ftestanything.org\u002Ftap-specification.html",[399],[395,539,536],{"href":536,"rel":540},[399],"\nce qui nous permettra de pouvoir utiliser des reporters compatibles avec ce standard.",[30,543,544,545,550],{},"Par exemple, le module ",[395,546,549],{"href":547,"rel":548},"https:\u002F\u002Fgithub.com\u002Fscottcorgan\u002Ftap-spec",[399],"tap-spec"," permet d’obtenir le rendu\nsuivant :",[30,552,553],{},[554,555],"img",{"alt":10,"src":556},"\u002Fimages\u002FUntitled.png",[30,558,559,560,562,563,566,567],{},"Il suffit de piper le résultat de ",[34,561,407],{}," dans ",[34,564,565],{},"tape-spec"," : ",[34,568,569],{},"node --test | tap-spec",[389,571,572],{},[30,573,574,575,579],{},"Une liste de reporters est disponible ici\n",[395,576],{"href":577,"rel":578},"https:\u002F\u002Fgithub.com\u002Fljharb\u002Ftape#pretty-reporters",[399],[395,580,577],{"href":577,"rel":581},[399],[51,583,585],{"id":584},"spy-stub-et-mocks","Spy, stub et mocks",[30,587,588,589,591],{},"Contrairement à jest, ",[34,590,36],{}," ne permet pas de faire des spy ou mocks de modules.",[30,593,594],{},"Il est possible d’utiliser les librairies suivantes :",[596,597,598,607],"ul",{},[599,600,601,602],"li",{},"spying et stubbing avec ",[395,603,606],{"href":604,"rel":605},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fsinon",[399],"sinon",[599,608,609,610],{},"snapshots avec ",[395,611,614],{"href":612,"rel":613},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fsnapshot-assertion",[399],"snapshot-assertion",[616,617,618],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":10,"searchDepth":11,"depth":11,"links":620},[621,622],{"id":53,"depth":11,"text":54},{"id":584,"depth":11,"text":585},"2022-11-17","Avec NodeJS v18, il est possible d’écrire ses tests sans librairie externe grâce au module node:test","fr",{},"\u002Farticles\u002F2022-11-17-nodejs-test-runner",{"title":25,"description":624},"articles\u002F2022-11-17-nodejs-test-runner",[631],"Tech","9CiEcnMi-bMA7h-X2bZN4le8Jlu8bCKzAkpBl3bzIos",{"id":634,"title":635,"author":26,"body":636,"date":1703,"description":1704,"extension":13,"lang":625,"meta":1705,"navigation":16,"path":1706,"published":16,"readingTime":294,"seo":1707,"stem":1708,"tags":1709,"__hash__":1710},"articles\u002Farticles\u002F2022-02-24-comment-se-passer-de-mongoose.md","Comment se passer de Mongoose ?",{"type":7,"value":637,"toc":1690},[638,642,647,685,689,730,740,744,747,823,826,869,877,884,888,897,1064,1071,1075,1078,1144,1153,1159,1163,1171,1176,1180,1186,1193,1266,1269,1272,1279,1307,1313,1378,1385,1400,1414,1417,1479,1486,1491,1494,1551,1558,1615,1618,1655,1672,1682,1687],[51,639,641],{"id":640},"avantages-inconvénients-de-mongoose","Avantages \u002F inconvénients de Mongoose",[643,644,646],"h3",{"id":645},"avantages","Avantages",[596,648,649,652,665,668,682],{},[599,650,651],{},"Library utilisée de facto par la communauté NodeJS pour MongoDb. On retrouve donc beaucoup de\ndocumentation.",[599,653,654,655,426,659,664],{},"Permet de définir des models de données et d’enforcer leur validation, ce que ne permettait pas\n",[656,657,658],"strong",{},"MongoDb \u003C 3.2",[395,660,663],{"href":661,"rel":662},"https:\u002F\u002Fdocs.mongodb.com\u002Fmanual\u002Fcore\u002Fschema-validation\u002F",[399],"Schema Validation — MongoDB Manual",")",[599,666,667],{},"La librairie est stable et maintenue depuis de nombreuses années",[599,669,670,671,674,675,678,679,43],{},"Propose quelques méthodes haut niveau comme ",[34,672,673],{},"populate"," pour simplifier un ",[34,676,677],{},"aggregate"," et un\n",[34,680,681],{},"$lookup",[599,683,684],{},"Propose un système de hooks permettant d’exécuter des actions avant \u002F après un type de query (par\nexemple rajouter un log après un insert en base)",[643,686,688],{"id":687},"inconvénients","Inconvénients",[596,690,691,694,715,723],{},[599,692,693],{},"La validation des schémas se fait au niveau applicatif, il n’y a donc pas de validation lorsqu’on\ninteragit avec la base de données depuis des clients autres (mongo shell, Compass, …) ce qui\nlaisse la place à des erreurs.",[599,695,696,697,700,701,704,705,710,711,714],{},"Mongoose ne retourne pas de POJO mais des objets qui lui sont propres. On peut donc les muter puis\nles sauvegarder en base grâce à l’utilisation de ",[34,698,699],{},"model.prototype.save()",". Attention alors quand\non utilise ces objets et qu’on les fait circuler dans notre chaine d’appels, il y a un risque\nd’effet de bord. Il est possible de rajouter un appel à ",[34,702,703],{},"lean()"," à nos queries pour que Mongoose\nretourne un POJO\n(",[395,706,709],{"href":707,"rel":708},"https:\u002F\u002Fmongoosejs.com\u002Fdocs\u002Fapi.html#query_Query-lean",[399],"Mongoose v6.1.1: API docs",") :\n",[34,712,713],{},"Users.find({ first_name: 'Léo' }).lean()",";",[599,716,717,718,664],{},"Les performances sont moindres comparé au driver natif\n(",[395,719,722],{"href":720,"rel":721},"https:\u002F\u002Fblog.jscrambler.com\u002Fmongodb-native-driver-vs-mongoose-performance-benchmarks",[399],"MongoDB Native Driver vs Mongoose: Performance Benchmarks | Jscrambler Blog",[599,724,725,726,729],{},"L’API de Mongoose diffère parfois légèrement de celle de Mongodb qu’il faut connaitre pour éviter\ndes comportements inattendus (par exemple les méthodes pour muter les données sont wrappées dans\nun ",[34,727,728],{},"$set"," de manière transparente).",[389,731,732],{},[30,733,734,735],{},"L’équipe de MongoDB a fait un comparatif assez complet ici :\n",[395,736,739],{"href":737,"rel":738},"https:\u002F\u002Fwww.mongodb.com\u002Fdeveloper\u002Farticle\u002Fmongoose-versus-nodejs-driver\u002F",[399],"MongoDB & Mongoose: Compatibility and Comparison",[51,741,743],{"id":742},"validation-des-données-json-schema-vs-mongoose","Validation des données : Json Schema vs mongoose",[30,745,746],{},"Mongoose :",[62,748,752],{"className":749,"code":750,"language":751,"meta":10,"style":10},"language-js shiki shiki-themes github-light github-dark","new Schema({\n  firstName: { type: String, required: true, min: 2 },\n  lastName: { type: String, required: true, min: 2 },\n  phone: {\n    type: String,\n    validate: {\n      validator: function (v) {\n        return \u002F\\\\\\\\d{3}-\\\\\\\\d{3}-\\\\\\\\d{4}\u002F.test(v);\n      },\n      message: (props) => `${props.value} is not a valid phone number!`,\n    },\n    required: [true, \"User phone number required\"],\n  },\n});\n","js",[34,753,754,759,764,769,774,779,784,789,794,799,804,809,814,819],{"__ignoreMap":10},[70,755,756],{"class":72,"line":18},[70,757,758],{},"new Schema({\n",[70,760,761],{"class":72,"line":11},[70,762,763],{},"  firstName: { type: String, required: true, min: 2 },\n",[70,765,766],{"class":72,"line":149},[70,767,768],{},"  lastName: { type: String, required: true, min: 2 },\n",[70,770,771],{"class":72,"line":201},[70,772,773],{},"  phone: {\n",[70,775,776],{"class":72,"line":216},[70,777,778],{},"    type: String,\n",[70,780,781],{"class":72,"line":221},[70,782,783],{},"    validate: {\n",[70,785,786],{"class":72,"line":242},[70,787,788],{},"      validator: function (v) {\n",[70,790,791],{"class":72,"line":259},[70,792,793],{},"        return \u002F\\\\\\\\d{3}-\\\\\\\\d{3}-\\\\\\\\d{4}\u002F.test(v);\n",[70,795,796],{"class":72,"line":294},[70,797,798],{},"      },\n",[70,800,801],{"class":72,"line":312},[70,802,803],{},"      message: (props) => `${props.value} is not a valid phone number!`,\n",[70,805,806],{"class":72,"line":318},[70,807,808],{},"    },\n",[70,810,811],{"class":72,"line":323},[70,812,813],{},"    required: [true, \"User phone number required\"],\n",[70,815,816],{"class":72,"line":339},[70,817,818],{},"  },\n",[70,820,821],{"class":72,"line":365},[70,822,387],{},[30,824,825],{},"Json Schema :",[62,827,829],{"className":749,"code":828,"language":751,"meta":10,"style":10},"{\n    properties: {\n        firstName: { type: 'string', minLength: 2 },\n        lastName: { type: 'string', minLength: 2 },\n        phone: { type: 'string' },\n    },\n    required: ['firstName', 'lastName'],\n}\n",[34,830,831,836,841,846,851,856,860,865],{"__ignoreMap":10},[70,832,833],{"class":72,"line":18},[70,834,835],{},"{\n",[70,837,838],{"class":72,"line":11},[70,839,840],{},"    properties: {\n",[70,842,843],{"class":72,"line":149},[70,844,845],{},"        firstName: { type: 'string', minLength: 2 },\n",[70,847,848],{"class":72,"line":201},[70,849,850],{},"        lastName: { type: 'string', minLength: 2 },\n",[70,852,853],{"class":72,"line":216},[70,854,855],{},"        phone: { type: 'string' },\n",[70,857,858],{"class":72,"line":221},[70,859,808],{},[70,861,862],{"class":72,"line":242},[70,863,864],{},"    required: ['firstName', 'lastName'],\n",[70,866,867],{"class":72,"line":259},[70,868,152],{},[30,870,871,872,876],{},"On voit ici que JSON Schema ne permet pas d’implémenter des logiques de validations personnalisées\npuisque que ce sont des schemas statiques. Ils ont ainsi l’avantage d’être ",[873,874,875],"em",{},"language agnostic"," ou\nencore de pouvoir être retourné par une API à un client qui pourra l’exploiter.",[30,878,879,880,883],{},"Ici il faudra alors valider le format du champ ",[34,881,882],{},"phone"," au niveau du code métier de l’application.",[51,885,887],{"id":886},"utiliser-la-validation-mongodb-json-schema","Utiliser la validation MongoDB Json Schema",[30,889,890,891,896],{},"Depuis la version 3.2, il est possible d’enforcer la validation d’un document MongoDB à partir d’un\nJSON Schema (",[395,892,895],{"href":893,"rel":894},"https:\u002F\u002Fjson-schema.org\u002Fspecification.html",[399],"Specification | JSON Schema","), bien que ce\nne soit vraiment utilisable que depuis la v5 qui rajoute la raison pour laquelle une validation a\nété rejetée.",[62,898,900],{"className":749,"code":899,"language":751,"meta":10,"style":10},"{\n   \"code\": 121,\n   \"errmsg\": \"Document failed validation\",\n   \"errInfo\": {\n       \"failingDocumentId\": ObjectId(\"5fe0eb9642c10f01eeca66a9\"),\n       \"details\": {\n           \"operatorName\": \"$jsonSchema\",\n           \"schemaRulesNotSatisfied\": [\n               {\n                   \"operatorName\": \"properties\",\n                   \"propertiesNotSatisfied\": [\n                       {\n                           \"propertyName\": \"price\",\n                           \"details\": [\n                               {\n                                   \"operatorName\": \"minimum\",\n                                   \"specifiedAs\": {\n                                       \"minimum\": 0\n                                   },\n                                   \"reason\": \"comparison failed\",\n                                   \"consideredValue\": -2\n                               }\n                           ]\n                       }\n                   ]\n               }\n           ]\n       }\n   }\n}\n",[34,901,902,906,911,916,921,926,931,936,941,946,951,956,961,966,971,976,981,987,993,999,1005,1011,1017,1023,1029,1035,1041,1047,1053,1059],{"__ignoreMap":10},[70,903,904],{"class":72,"line":18},[70,905,835],{},[70,907,908],{"class":72,"line":11},[70,909,910],{},"   \"code\": 121,\n",[70,912,913],{"class":72,"line":149},[70,914,915],{},"   \"errmsg\": \"Document failed validation\",\n",[70,917,918],{"class":72,"line":201},[70,919,920],{},"   \"errInfo\": {\n",[70,922,923],{"class":72,"line":216},[70,924,925],{},"       \"failingDocumentId\": ObjectId(\"5fe0eb9642c10f01eeca66a9\"),\n",[70,927,928],{"class":72,"line":221},[70,929,930],{},"       \"details\": {\n",[70,932,933],{"class":72,"line":242},[70,934,935],{},"           \"operatorName\": \"$jsonSchema\",\n",[70,937,938],{"class":72,"line":259},[70,939,940],{},"           \"schemaRulesNotSatisfied\": [\n",[70,942,943],{"class":72,"line":294},[70,944,945],{},"               {\n",[70,947,948],{"class":72,"line":312},[70,949,950],{},"                   \"operatorName\": \"properties\",\n",[70,952,953],{"class":72,"line":318},[70,954,955],{},"                   \"propertiesNotSatisfied\": [\n",[70,957,958],{"class":72,"line":323},[70,959,960],{},"                       {\n",[70,962,963],{"class":72,"line":339},[70,964,965],{},"                           \"propertyName\": \"price\",\n",[70,967,968],{"class":72,"line":365},[70,969,970],{},"                           \"details\": [\n",[70,972,973],{"class":72,"line":379},[70,974,975],{},"                               {\n",[70,977,978],{"class":72,"line":384},[70,979,980],{},"                                   \"operatorName\": \"minimum\",\n",[70,982,984],{"class":72,"line":983},17,[70,985,986],{},"                                   \"specifiedAs\": {\n",[70,988,990],{"class":72,"line":989},18,[70,991,992],{},"                                       \"minimum\": 0\n",[70,994,996],{"class":72,"line":995},19,[70,997,998],{},"                                   },\n",[70,1000,1002],{"class":72,"line":1001},20,[70,1003,1004],{},"                                   \"reason\": \"comparison failed\",\n",[70,1006,1008],{"class":72,"line":1007},21,[70,1009,1010],{},"                                   \"consideredValue\": -2\n",[70,1012,1014],{"class":72,"line":1013},22,[70,1015,1016],{},"                               }\n",[70,1018,1020],{"class":72,"line":1019},23,[70,1021,1022],{},"                           ]\n",[70,1024,1026],{"class":72,"line":1025},24,[70,1027,1028],{},"                       }\n",[70,1030,1032],{"class":72,"line":1031},25,[70,1033,1034],{},"                   ]\n",[70,1036,1038],{"class":72,"line":1037},26,[70,1039,1040],{},"               }\n",[70,1042,1044],{"class":72,"line":1043},27,[70,1045,1046],{},"           ]\n",[70,1048,1050],{"class":72,"line":1049},28,[70,1051,1052],{},"       }\n",[70,1054,1056],{"class":72,"line":1055},29,[70,1057,1058],{},"   }\n",[70,1060,1062],{"class":72,"line":1061},30,[70,1063,152],{},[30,1065,1066,1067,1070],{},"Dans l’exemple ci-dessus on peut voir que la validation a échoué puisque le champ ",[34,1068,1069],{},"price"," ne pouvait\npas avoir une valeur inférieure à 0.",[643,1072,1074],{"id":1073},"ajouter-un-json-schema-à-une-collection-mongodb","Ajouter un Json Schema à une collection MongoDB",[30,1076,1077],{},"Il est possible d’ajouter une validation Json Schema a une collection existante :",[62,1079,1081],{"className":749,"code":1080,"language":751,"meta":10,"style":10},"db.runCommand({\n  collMod: \"users\",\n  validator: {\n    $jsonSchema: {\n      bsonType: \"object\",\n      required: [\"first_name\", \"last_name\"],\n      properties: {\n        first_name: { bsonType: \"string\", minLength: 2 },\n        last_name: { bsonType: \"string\", minLength: 2 },\n      },\n    },\n  },\n});\n",[34,1082,1083,1088,1093,1098,1103,1108,1113,1118,1123,1128,1132,1136,1140],{"__ignoreMap":10},[70,1084,1085],{"class":72,"line":18},[70,1086,1087],{},"db.runCommand({\n",[70,1089,1090],{"class":72,"line":11},[70,1091,1092],{},"  collMod: \"users\",\n",[70,1094,1095],{"class":72,"line":149},[70,1096,1097],{},"  validator: {\n",[70,1099,1100],{"class":72,"line":201},[70,1101,1102],{},"    $jsonSchema: {\n",[70,1104,1105],{"class":72,"line":216},[70,1106,1107],{},"      bsonType: \"object\",\n",[70,1109,1110],{"class":72,"line":221},[70,1111,1112],{},"      required: [\"first_name\", \"last_name\"],\n",[70,1114,1115],{"class":72,"line":242},[70,1116,1117],{},"      properties: {\n",[70,1119,1120],{"class":72,"line":259},[70,1121,1122],{},"        first_name: { bsonType: \"string\", minLength: 2 },\n",[70,1124,1125],{"class":72,"line":294},[70,1126,1127],{},"        last_name: { bsonType: \"string\", minLength: 2 },\n",[70,1129,1130],{"class":72,"line":312},[70,1131,798],{},[70,1133,1134],{"class":72,"line":318},[70,1135,808],{},[70,1137,1138],{"class":72,"line":323},[70,1139,818],{},[70,1141,1142],{"class":72,"line":339},[70,1143,387],{},[389,1145,1146],{},[30,1147,1148,1149,1152],{},"Par défaut, MongoDB applique la validation sur TOUS les documents existants, incluant ceux qui ne\nrespectent pas le schema de validation (si votre model a évolué au fil du temps par exemple,\nattention à ce que le schema ne soit pas trop restrictif). Il est possible de demander à MongoDB\nde n’enforcer la validation que sur les documents existants qui respectent les règles de\nvalidation en rajoutant ",[34,1150,1151],{},"validationLevel: ‘moderate’"," à la query (par défaut sa valeur est\nstrict).",[30,1154,1155,1156],{},"Plus de détails dans la documentation de MongoDB :\n",[395,1157,663],{"href":661,"rel":1158},[399],[51,1160,1162],{"id":1161},"intégration-dans-une-application-nodejs","Intégration dans une application NodeJS",[30,1164,1165,1166],{},"En prenant cet exemple d’application Node ExpressJs qui utilise Mongoose :\n",[395,1167,1170],{"href":1168,"rel":1169},"https:\u002F\u002Fstackblitz.com\u002Fedit\u002Fnode-nrnczl?embed=1&file=src\u002Findex.js&hideNavigation=1&view=editor",[399],"Node.js (forked) - StackBlitz",[30,1172,1173],{},[554,1174],{"alt":10,"src":1175},"\u002Fimages\u002Fcapture-decc81cran-2022-02-24-acc80-13.52.05.png",[643,1177,1179],{"id":1178},"intégrer-le-client-mongodb","Intégrer le client MongoDB",[30,1181,1182,1183,43],{},"Installer le client : ",[34,1184,1185],{},"npm i —save mongodb",[30,1187,1188,1189,1192],{},"On vient mettre à jour ",[34,1190,1191],{},"initDatabase"," pour utiliser le driver :",[62,1194,1196],{"className":749,"code":1195,"language":751,"meta":10,"style":10},"async function initDatabase() {\n  const client = new MongoClient(connectionString, {\n    serverSelectionTimeoutMS: 1000,\n  });\n\n  await client.connect();\n\n  const db = client.db(config.dbName);\n\n  await createCollections({ db, models }); \u002F\u002F une première étape qui permet de créer les collections en base si nécessaire\n\n  await registerJsonSchemaValidators({ db, models }); \u002F\u002F une deuxième étape dans laquelle on vient enregistrer les json schemas de validation pour chaque collection\n\n  loadDriverCollections({ db, models }); \u002F\u002F et une dernière étape qui va nous servir à garder en mémoire l’instance de chaque db.collection du client MongoDB (ce sera plus clair par la suite).\n}\n",[34,1197,1198,1203,1208,1213,1217,1221,1226,1230,1235,1239,1244,1248,1253,1257,1262],{"__ignoreMap":10},[70,1199,1200],{"class":72,"line":18},[70,1201,1202],{},"async function initDatabase() {\n",[70,1204,1205],{"class":72,"line":11},[70,1206,1207],{},"  const client = new MongoClient(connectionString, {\n",[70,1209,1210],{"class":72,"line":149},[70,1211,1212],{},"    serverSelectionTimeoutMS: 1000,\n",[70,1214,1215],{"class":72,"line":201},[70,1216,315],{},[70,1218,1219],{"class":72,"line":216},[70,1220,198],{"emptyLinePlaceholder":16},[70,1222,1223],{"class":72,"line":221},[70,1224,1225],{},"  await client.connect();\n",[70,1227,1228],{"class":72,"line":242},[70,1229,198],{"emptyLinePlaceholder":16},[70,1231,1232],{"class":72,"line":259},[70,1233,1234],{},"  const db = client.db(config.dbName);\n",[70,1236,1237],{"class":72,"line":294},[70,1238,198],{"emptyLinePlaceholder":16},[70,1240,1241],{"class":72,"line":312},[70,1242,1243],{},"  await createCollections({ db, models }); \u002F\u002F une première étape qui permet de créer les collections en base si nécessaire\n",[70,1245,1246],{"class":72,"line":318},[70,1247,198],{"emptyLinePlaceholder":16},[70,1249,1250],{"class":72,"line":323},[70,1251,1252],{},"  await registerJsonSchemaValidators({ db, models }); \u002F\u002F une deuxième étape dans laquelle on vient enregistrer les json schemas de validation pour chaque collection\n",[70,1254,1255],{"class":72,"line":339},[70,1256,198],{"emptyLinePlaceholder":16},[70,1258,1259],{"class":72,"line":365},[70,1260,1261],{},"  loadDriverCollections({ db, models }); \u002F\u002F et une dernière étape qui va nous servir à garder en mémoire l’instance de chaque db.collection du client MongoDB (ce sera plus clair par la suite).\n",[70,1263,1264],{"class":72,"line":379},[70,1265,152],{},[30,1267,1268],{},"Ici on rajoute trois étapes en plus de la connexion à la base",[30,1270,1271],{},"Comme l’idée ici est de pouvoir migrer facilement de Mongoose au MongoDB driver, il va falloir faire\nen sorte de minimiser les différences d’utilisation entre notre librairie et Mongoose, notamment\npour ce qui est d’utiliser les models.",[30,1273,1274,1275,1278],{},"Nous allons donc exposer une fonction ",[34,1276,1277],{},"registerModel"," qui aura à la fois comme but de créer le model\nen interne, mais aussi d’en récupérer une instance pour pouvoir l’utiliser directement, comme le\npropose mongoose :",[62,1280,1282],{"className":749,"code":1281,"language":751,"meta":10,"style":10},"const UserModel = require(\"..\u002Fmodels\u002FUser\");\n\nfunction findOneUserById({ userId }) {\n  return findOne({ _id: userId });\n}\n",[34,1283,1284,1289,1293,1298,1303],{"__ignoreMap":10},[70,1285,1286],{"class":72,"line":18},[70,1287,1288],{},"const UserModel = require(\"..\u002Fmodels\u002FUser\");\n",[70,1290,1291],{"class":72,"line":11},[70,1292,198],{"emptyLinePlaceholder":16},[70,1294,1295],{"class":72,"line":149},[70,1296,1297],{},"function findOneUserById({ userId }) {\n",[70,1299,1300],{"class":72,"line":201},[70,1301,1302],{},"  return findOne({ _id: userId });\n",[70,1304,1305],{"class":72,"line":216},[70,1306,152],{},[30,1308,1309,1310,1312],{},"L’implémentation de la fonction ",[34,1311,1277],{}," :",[62,1314,1316],{"className":749,"code":1315,"language":751,"meta":10,"style":10},"const models = {};\n\nfunction registerModel(name, { schema }) {\n  Object.assign(models, {\n    [name]: {\n      name,\n      schema,\n      collection: {}, \u002F\u002F mongodb driver collection instance\n    },\n  });\n\n  return models[name].collection;\n}\n",[34,1317,1318,1323,1327,1332,1337,1342,1347,1352,1357,1361,1365,1369,1374],{"__ignoreMap":10},[70,1319,1320],{"class":72,"line":18},[70,1321,1322],{},"const models = {};\n",[70,1324,1325],{"class":72,"line":11},[70,1326,198],{"emptyLinePlaceholder":16},[70,1328,1329],{"class":72,"line":149},[70,1330,1331],{},"function registerModel(name, { schema }) {\n",[70,1333,1334],{"class":72,"line":201},[70,1335,1336],{},"  Object.assign(models, {\n",[70,1338,1339],{"class":72,"line":216},[70,1340,1341],{},"    [name]: {\n",[70,1343,1344],{"class":72,"line":221},[70,1345,1346],{},"      name,\n",[70,1348,1349],{"class":72,"line":242},[70,1350,1351],{},"      schema,\n",[70,1353,1354],{"class":72,"line":259},[70,1355,1356],{},"      collection: {}, \u002F\u002F mongodb driver collection instance\n",[70,1358,1359],{"class":72,"line":294},[70,1360,808],{},[70,1362,1363],{"class":72,"line":312},[70,1364,315],{},[70,1366,1367],{"class":72,"line":318},[70,1368,198],{"emptyLinePlaceholder":16},[70,1370,1371],{"class":72,"line":323},[70,1372,1373],{},"  return models[name].collection;\n",[70,1375,1376],{"class":72,"line":339},[70,1377,152],{},[30,1379,1380,1381,1384],{},"Premièrement, on vient rajouter un object ",[34,1382,1383],{},"models"," qui nous permettra de garder en mémoire au sein\ndu module les models enregistrés par l’application.",[30,1386,1387,1388,1391,1392,1395,1396,1399],{},"Pour l’exemple, le format d’un model est très simple puisqu’on voit qu’il ne se compose que d’un\n",[34,1389,1390],{},"name"," et d’un ",[34,1393,1394],{},"schema"," (le JSON schema). On pourrait très bien imaginer rajouter d’autres\npropriétés telles que ",[34,1397,1398],{},"indexes"," nous permettant de créer des index…",[30,1401,1402,1403,1405,1406,1409,1410,1413],{},"On initialise un model vide à l’appel de ",[34,1404,1277],{}," grâce à ",[34,1407,1408],{},"Object.assign",". La propriété\n",[34,1411,1412],{},"collection"," est un object vide (qui contiendra une fois la connexion avec MongoDB établie\nl’instance de la collection du driver MongoDB) ce qui nous permettra de le modifier par référence un\npeu plus tard.",[30,1415,1416],{},"On peut maintenant définir un model comme suit :",[62,1418,1420],{"className":749,"code":1419,"language":751,"meta":10,"style":10},"const userSchema = {\n  bsonType: \"object\",\n  properties: {\n    email: { type: \"string\", format: \"email\" },\n    password: { type: \"string\", minLength: 8 },\n  },\n  required: [\"email\", \"password\"],\n};\n\nmodule.exports = registerModel(\"user\", {\n  schema: userSchema,\n});\n",[34,1421,1422,1427,1432,1437,1442,1447,1451,1456,1461,1465,1470,1475],{"__ignoreMap":10},[70,1423,1424],{"class":72,"line":18},[70,1425,1426],{},"const userSchema = {\n",[70,1428,1429],{"class":72,"line":11},[70,1430,1431],{},"  bsonType: \"object\",\n",[70,1433,1434],{"class":72,"line":149},[70,1435,1436],{},"  properties: {\n",[70,1438,1439],{"class":72,"line":201},[70,1440,1441],{},"    email: { type: \"string\", format: \"email\" },\n",[70,1443,1444],{"class":72,"line":216},[70,1445,1446],{},"    password: { type: \"string\", minLength: 8 },\n",[70,1448,1449],{"class":72,"line":221},[70,1450,818],{},[70,1452,1453],{"class":72,"line":242},[70,1454,1455],{},"  required: [\"email\", \"password\"],\n",[70,1457,1458],{"class":72,"line":259},[70,1459,1460],{},"};\n",[70,1462,1463],{"class":72,"line":294},[70,1464,198],{"emptyLinePlaceholder":16},[70,1466,1467],{"class":72,"line":312},[70,1468,1469],{},"module.exports = registerModel(\"user\", {\n",[70,1471,1472],{"class":72,"line":318},[70,1473,1474],{},"  schema: userSchema,\n",[70,1476,1477],{"class":72,"line":323},[70,1478,387],{},[30,1480,1481,1482,1485],{},"On voit ici que le format ",[34,1483,1484],{},"email"," est supporté par JSON Schema ce qui est plutôt pratique.",[389,1487,1488],{},[30,1489,1490],{},"La liste des types supportés par JSON Schema est disponible ici JSON Schema Reference —\nUnderstanding JSON Schema 2020-12 documentation",[30,1492,1493],{},"Il ne manque plus qu’à créer la collection en base (si nécessaire, on ne créer que celles qui\nn'existent pas) et à lui assigner un JSON Schema.",[62,1495,1497],{"className":749,"code":1496,"language":751,"meta":10,"style":10},"async function createCollections({ db, models }) {\n  const existingCollections = await db.listCollections({}, { nameOnly: true }).toArray();\n  const modelsCollectionsNames = _.map(models, \"name\");\n  const collectionsToCreate = _.difference(existingCollections, modelsCollectionsNames);\n\n  const createCollectionsPromises = _.map(collectionsToCreate, (collectionName) =>\n    db.createCollection(collectionName),\n  );\n\n  return Promise.all(createCollectionsPromises);\n}\n",[34,1498,1499,1504,1509,1514,1519,1523,1528,1533,1538,1542,1547],{"__ignoreMap":10},[70,1500,1501],{"class":72,"line":18},[70,1502,1503],{},"async function createCollections({ db, models }) {\n",[70,1505,1506],{"class":72,"line":11},[70,1507,1508],{},"  const existingCollections = await db.listCollections({}, { nameOnly: true }).toArray();\n",[70,1510,1511],{"class":72,"line":149},[70,1512,1513],{},"  const modelsCollectionsNames = _.map(models, \"name\");\n",[70,1515,1516],{"class":72,"line":201},[70,1517,1518],{},"  const collectionsToCreate = _.difference(existingCollections, modelsCollectionsNames);\n",[70,1520,1521],{"class":72,"line":216},[70,1522,198],{"emptyLinePlaceholder":16},[70,1524,1525],{"class":72,"line":221},[70,1526,1527],{},"  const createCollectionsPromises = _.map(collectionsToCreate, (collectionName) =>\n",[70,1529,1530],{"class":72,"line":242},[70,1531,1532],{},"    db.createCollection(collectionName),\n",[70,1534,1535],{"class":72,"line":259},[70,1536,1537],{},"  );\n",[70,1539,1540],{"class":72,"line":294},[70,1541,198],{"emptyLinePlaceholder":16},[70,1543,1544],{"class":72,"line":312},[70,1545,1546],{},"  return Promise.all(createCollectionsPromises);\n",[70,1548,1549],{"class":72,"line":318},[70,1550,152],{},[30,1552,1553,1554,1557],{},"On récupère tout d’abord la liste de noms des collections existantes en base\n(",[34,1555,1556],{},"await db.listCollections({}, { nameOnly: true }).toArray()",") puis on vient récupérer celles qui\nn’existent pas pour finalement les créer.",[62,1559,1561],{"className":749,"code":1560,"language":751,"meta":10,"style":10},"function registerJsonSchemaValidators({ db, models }) {\n  const registerValidatorsPromises = _.map(models, ({ schema, name }) =>\n    db.command({\n      collMod: name,\n      validator: { $jsonSchema: schema },\n      validationLevel: \"strict\",\n    }),\n  );\n\n  return Promise.all(registerValidatorsPromises);\n}\n",[34,1562,1563,1568,1573,1578,1583,1588,1593,1598,1602,1606,1611],{"__ignoreMap":10},[70,1564,1565],{"class":72,"line":18},[70,1566,1567],{},"function registerJsonSchemaValidators({ db, models }) {\n",[70,1569,1570],{"class":72,"line":11},[70,1571,1572],{},"  const registerValidatorsPromises = _.map(models, ({ schema, name }) =>\n",[70,1574,1575],{"class":72,"line":149},[70,1576,1577],{},"    db.command({\n",[70,1579,1580],{"class":72,"line":201},[70,1581,1582],{},"      collMod: name,\n",[70,1584,1585],{"class":72,"line":216},[70,1586,1587],{},"      validator: { $jsonSchema: schema },\n",[70,1589,1590],{"class":72,"line":221},[70,1591,1592],{},"      validationLevel: \"strict\",\n",[70,1594,1595],{"class":72,"line":242},[70,1596,1597],{},"    }),\n",[70,1599,1600],{"class":72,"line":259},[70,1601,1537],{},[70,1603,1604],{"class":72,"line":294},[70,1605,198],{"emptyLinePlaceholder":16},[70,1607,1608],{"class":72,"line":312},[70,1609,1610],{},"  return Promise.all(registerValidatorsPromises);\n",[70,1612,1613],{"class":72,"line":318},[70,1614,152],{},[30,1616,1617],{},"Ensuite on vient mettre à jour notre référence à l’objet collection du MongoDB driver pour NodeJS :",[62,1619,1621],{"className":749,"code":1620,"language":751,"meta":10,"style":10},"function loadDriverCollections({ db, models }) {\n  _.forEach(models, (model) => {\n    const collection = db.collection(model.name);\n\n    Object.assign(model.collection, collection);\n  });\n}\n",[34,1622,1623,1628,1633,1638,1642,1647,1651],{"__ignoreMap":10},[70,1624,1625],{"class":72,"line":18},[70,1626,1627],{},"function loadDriverCollections({ db, models }) {\n",[70,1629,1630],{"class":72,"line":11},[70,1631,1632],{},"  _.forEach(models, (model) => {\n",[70,1634,1635],{"class":72,"line":149},[70,1636,1637],{},"    const collection = db.collection(model.name);\n",[70,1639,1640],{"class":72,"line":201},[70,1641,198],{"emptyLinePlaceholder":16},[70,1643,1644],{"class":72,"line":216},[70,1645,1646],{},"    Object.assign(model.collection, collection);\n",[70,1648,1649],{"class":72,"line":221},[70,1650,315],{},[70,1652,1653],{"class":72,"line":242},[70,1654,152],{},[30,1656,1657,1658,1660,1661,1664,1665,1668,1669,1671],{},"En terme d’usage, c'est très proche de ce que peut proposer Mongoose bien qu’il manque certains\nsucres syntaxiques tels que ",[34,1659,673],{}," pour faire des aggregations ou encore ",[34,1662,1663],{},"sort"," et ",[34,1666,1667],{},"paginate",".\nIl faudra plutôt utiliser un ",[34,1670,677],{}," MongoDB pour les remplacer, ce qui a aussi l’avantage\nd’écrire des queries utilisables à la fois dans notre application que dans un Mongoshell ou autre\nclient MongoDB.",[389,1673,1674],{},[30,1675,1676,1677],{},"Projet d’exemple :\n",[395,1678,1681],{"href":1679,"rel":1680},"https:\u002F\u002Fstackblitz.com\u002Fedit\u002Fnode-pn1eja?file=src\u002Fdatabase.js",[399],"Mongodb driver - StackBlitz",[30,1683,1684],{},[554,1685],{"alt":10,"src":1686},"\u002Fimages\u002Fcapture-decc81cran-2022-02-24-acc80-13.52.32.png",[616,1688,1689],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":10,"searchDepth":11,"depth":11,"links":1691},[1692,1696,1697,1700],{"id":640,"depth":11,"text":641,"children":1693},[1694,1695],{"id":645,"depth":149,"text":646},{"id":687,"depth":149,"text":688},{"id":742,"depth":11,"text":743},{"id":886,"depth":11,"text":887,"children":1698},[1699],{"id":1073,"depth":149,"text":1074},{"id":1161,"depth":11,"text":1162,"children":1701},[1702],{"id":1178,"depth":149,"text":1179},"2022-02-24","Une analyse détaillée des avantages et des limites de Mongoose, l’ORM le plus populaire pour MongoDB dans l’écosystème Node.js.",{},"\u002Farticles\u002F2022-02-24-comment-se-passer-de-mongoose",{"title":635,"description":1704},"articles\u002F2022-02-24-comment-se-passer-de-mongoose",[631],"vgA_sLlu2jH8NzgrQkSYcg0a16xsWwRnWUqtLhfE1yE",[1712,1724,1736,1749,1762,1774,1786,1799,1812,1825,1837,1849,1862,1874,1881,1893,1905,1917,1929,1941,1954,1966,1978,1991,2003,2015],{"id":1713,"title":5,"body":1714,"description":10,"extension":13,"meta":1718,"name":1719,"navigation":16,"path":1720,"readingTime":18,"seo":1721,"stem":1722,"__hash__":1723},"authors\u002Fauthors\u002Falexandre-guillon.md",{"type":7,"value":1715,"toc":1716},[],{"title":10,"searchDepth":11,"depth":11,"links":1717},[],{},"Alexandre Guillon","\u002Fauthors\u002Falexandre-guillon",{"title":5,"description":10},"authors\u002Falexandre-guillon","4tf48mjyjFNqItOHaulICbrjeCyMag1o6801uHeTz98",{"id":1725,"title":5,"body":1726,"description":10,"extension":13,"meta":1730,"name":1731,"navigation":16,"path":1732,"readingTime":18,"seo":1733,"stem":1734,"__hash__":1735},"authors\u002Fauthors\u002Falexis-ablain.md",{"type":7,"value":1727,"toc":1728},[],{"title":10,"searchDepth":11,"depth":11,"links":1729},[],{},"Alexis Ablain","\u002Fauthors\u002Falexis-ablain",{"title":5,"description":10},"authors\u002Falexis-ablain","_SIAtB7f-39e5t3GiJof81NP47s6MGo2n4gaHkTy1uQ",{"id":1737,"title":1738,"body":1739,"description":10,"extension":13,"meta":1743,"name":1744,"navigation":16,"path":1745,"readingTime":18,"seo":1746,"stem":1747,"__hash__":1748},"authors\u002Fauthors\u002Faxel-shaita.md","Engineering Manager",{"type":7,"value":1740,"toc":1741},[],{"title":10,"searchDepth":11,"depth":11,"links":1742},[],{},"Axel Shaïta","\u002Fauthors\u002Faxel-shaita",{"title":1738,"description":10},"authors\u002Faxel-shaita","fK0argUhsBkWLjpTAhY13oYLVzQthcEYkCEdtHWmIgE",{"id":1750,"title":1751,"body":1752,"description":10,"extension":13,"meta":1756,"name":1757,"navigation":16,"path":1758,"readingTime":18,"seo":1759,"stem":1760,"__hash__":1761},"authors\u002Fauthors\u002Fbaptiste-faure.md","Head of Talent Acquisition",{"type":7,"value":1753,"toc":1754},[],{"title":10,"searchDepth":11,"depth":11,"links":1755},[],{},"Baptiste Faure","\u002Fauthors\u002Fbaptiste-faure",{"title":1751,"description":10},"authors\u002Fbaptiste-faure","ELisToYtcgHmgdVWZkCclTPV6exZtfyXqhpx1jjbJHs",{"id":1763,"title":5,"body":1764,"description":10,"extension":13,"meta":1768,"name":1769,"navigation":16,"path":1770,"readingTime":18,"seo":1771,"stem":1772,"__hash__":1773},"authors\u002Fauthors\u002Fbenjamin-bouillot.md",{"type":7,"value":1765,"toc":1766},[],{"title":10,"searchDepth":11,"depth":11,"links":1767},[],{},"Benjamin Bouillot","\u002Fauthors\u002Fbenjamin-bouillot",{"title":5,"description":10},"authors\u002Fbenjamin-bouillot","tbhCFZyfTt7ZM5b5YgqQ2nhgnSTl8BweaQQryc87fHo",{"id":1775,"title":1738,"body":1776,"description":10,"extension":13,"meta":1780,"name":1781,"navigation":16,"path":1782,"readingTime":18,"seo":1783,"stem":1784,"__hash__":1785},"authors\u002Fauthors\u002Fcedric-nicoloso.md",{"type":7,"value":1777,"toc":1778},[],{"title":10,"searchDepth":11,"depth":11,"links":1779},[],{},"Cédric Nicoloso","\u002Fauthors\u002Fcedric-nicoloso",{"title":1738,"description":10},"authors\u002Fcedric-nicoloso","ibSoh4VZYiWYTuLOnZTedaAfcnvet1Q9H7ogW0LgorY",{"id":1787,"title":1788,"body":1789,"description":10,"extension":13,"meta":1793,"name":1794,"navigation":16,"path":1795,"readingTime":18,"seo":1796,"stem":1797,"__hash__":1798},"authors\u002Fauthors\u002Fdavid-touzet.md","Staff Engineer",{"type":7,"value":1790,"toc":1791},[],{"title":10,"searchDepth":11,"depth":11,"links":1792},[],{},"David Touzet","\u002Fauthors\u002Fdavid-touzet",{"title":1788,"description":10},"authors\u002Fdavid-touzet","dHWwnQxb1Ubt-WwXWEODGEo9AFoq1cJUhfg3kdnYSBM",{"id":1800,"title":1801,"body":1802,"description":10,"extension":13,"meta":1806,"name":1807,"navigation":16,"path":1808,"readingTime":18,"seo":1809,"stem":1810,"__hash__":1811},"authors\u002Fauthors\u002Feloise-chizat.md","Data Engineer",{"type":7,"value":1803,"toc":1804},[],{"title":10,"searchDepth":11,"depth":11,"links":1805},[],{},"Eloïse Chizat","\u002Fauthors\u002Feloise-chizat",{"title":1801,"description":10},"authors\u002Feloise-chizat","Utd72Vm9qT4hh2ZbFi6a2_nXw5Wb494Ed_HL1ra5yw8",{"id":1813,"title":1814,"body":1815,"description":10,"extension":13,"meta":1819,"name":1820,"navigation":16,"path":1821,"readingTime":18,"seo":1822,"stem":1823,"__hash__":1824},"authors\u002Fauthors\u002Femmanuel-auclair.md","Staff engineer",{"type":7,"value":1816,"toc":1817},[],{"title":10,"searchDepth":11,"depth":11,"links":1818},[],{},"Emmanuel Auclair","\u002Fauthors\u002Femmanuel-auclair",{"title":1814,"description":10},"authors\u002Femmanuel-auclair","MtsA8THNLEn0dTtYEIQaGwDuf7MjQL55IOeei5gugEg",{"id":1826,"title":5,"body":1827,"description":10,"extension":13,"meta":1831,"name":1832,"navigation":16,"path":1833,"readingTime":18,"seo":1834,"stem":1835,"__hash__":1836},"authors\u002Fauthors\u002Fhoreb-parraud.md",{"type":7,"value":1828,"toc":1829},[],{"title":10,"searchDepth":11,"depth":11,"links":1830},[],{},"Horeb Parraud","\u002Fauthors\u002Fhoreb-parraud",{"title":5,"description":10},"authors\u002Fhoreb-parraud","ajjsnUX4ohZI-ghMdbb92q_taWDkKXVZSLZXoAeLQtg",{"id":1838,"title":1738,"body":1839,"description":10,"extension":13,"meta":1843,"name":1844,"navigation":16,"path":1845,"readingTime":18,"seo":1846,"stem":1847,"__hash__":1848},"authors\u002Fauthors\u002Fhugo-contreras.md",{"type":7,"value":1840,"toc":1841},[],{"title":10,"searchDepth":11,"depth":11,"links":1842},[],{},"Hugo Contreras","\u002Fauthors\u002Fhugo-contreras",{"title":1738,"description":10},"authors\u002Fhugo-contreras","2nc3VMu9ASq9Z6Pwx2-7-Ye991Pww4p-UEDBQFfjF-Q",{"id":1850,"title":1851,"body":1852,"description":10,"extension":13,"meta":1856,"name":1857,"navigation":16,"path":1858,"readingTime":18,"seo":1859,"stem":1860,"__hash__":1861},"authors\u002Fauthors\u002Fjulien-tassin.md","Head of Engineering",{"type":7,"value":1853,"toc":1854},[],{"title":10,"searchDepth":11,"depth":11,"links":1855},[],{},"Julien Tassin","\u002Fauthors\u002Fjulien-tassin",{"title":1851,"description":10},"authors\u002Fjulien-tassin","iUIHI7SITje38Jh9X9uvYs4-VsHx4eCdt6hAlyLFG_o",{"id":1863,"title":5,"body":1864,"description":10,"extension":13,"meta":1868,"name":1869,"navigation":16,"path":1870,"readingTime":18,"seo":1871,"stem":1872,"__hash__":1873},"authors\u002Fauthors\u002Flaurent-renard.md",{"type":7,"value":1865,"toc":1866},[],{"title":10,"searchDepth":11,"depth":11,"links":1867},[],{},"Laurent Renard","\u002Fauthors\u002Flaurent-renard",{"title":5,"description":10},"authors\u002Flaurent-renard","5BP7Ed-pt1SQHjh0UJ1XUrlLTcdlFaDoKBCP4deHq8A",{"id":4,"title":5,"body":1875,"description":10,"extension":13,"meta":1879,"name":15,"navigation":16,"path":17,"readingTime":18,"seo":1880,"stem":20,"__hash__":21},{"type":7,"value":1876,"toc":1877},[],{"title":10,"searchDepth":11,"depth":11,"links":1878},[],{},{"title":5,"description":10},{"id":1882,"title":5,"body":1883,"description":10,"extension":13,"meta":1887,"name":1888,"navigation":16,"path":1889,"readingTime":18,"seo":1890,"stem":1891,"__hash__":1892},"authors\u002Fauthors\u002Floic-bousquet.md",{"type":7,"value":1884,"toc":1885},[],{"title":10,"searchDepth":11,"depth":11,"links":1886},[],{},"Loïc Bousquet","\u002Fauthors\u002Floic-bousquet",{"title":5,"description":10},"authors\u002Floic-bousquet","ko12qZwiGL8XNjAoy9oWypPkIjr29Pbq7vhdtgldqeQ",{"id":1894,"title":5,"body":1895,"description":10,"extension":13,"meta":1899,"name":1900,"navigation":16,"path":1901,"readingTime":18,"seo":1902,"stem":1903,"__hash__":1904},"authors\u002Fauthors\u002Floic-poullain.md",{"type":7,"value":1896,"toc":1897},[],{"title":10,"searchDepth":11,"depth":11,"links":1898},[],{},"Loïc Poullain","\u002Fauthors\u002Floic-poullain",{"title":5,"description":10},"authors\u002Floic-poullain","oRIyJhFRTqxy5dLCYQ2OnYZ1DB-gLDUM-85vTSYuTF0",{"id":1906,"title":1801,"body":1907,"description":10,"extension":13,"meta":1911,"name":1912,"navigation":16,"path":1913,"readingTime":18,"seo":1914,"stem":1915,"__hash__":1916},"authors\u002Fauthors\u002Fmaud-lelu.md",{"type":7,"value":1908,"toc":1909},[],{"title":10,"searchDepth":11,"depth":11,"links":1910},[],{},"Maud Lélu","\u002Fauthors\u002Fmaud-lelu",{"title":1801,"description":10},"authors\u002Fmaud-lelu","MMbsCKuE41OMHusrl12FIEsI-Trx7l8Nn_ANhvj2_y4",{"id":1918,"title":1738,"body":1919,"description":10,"extension":13,"meta":1923,"name":1924,"navigation":16,"path":1925,"readingTime":18,"seo":1926,"stem":1927,"__hash__":1928},"authors\u002Fauthors\u002Fnicolas-poirier.md",{"type":7,"value":1920,"toc":1921},[],{"title":10,"searchDepth":11,"depth":11,"links":1922},[],{},"Nicolas Poirier","\u002Fauthors\u002Fnicolas-poirier",{"title":1738,"description":10},"authors\u002Fnicolas-poirier","dXrJkYo8az4SN_D23aYc3fQ7z8s1dR2a0lt1ogjAjJs",{"id":1930,"title":1738,"body":1931,"description":10,"extension":13,"meta":1935,"name":1936,"navigation":16,"path":1937,"readingTime":18,"seo":1938,"stem":1939,"__hash__":1940},"authors\u002Fauthors\u002Fraphael-sauget.md",{"type":7,"value":1932,"toc":1933},[],{"title":10,"searchDepth":11,"depth":11,"links":1934},[],{},"Raphaël Sauget","\u002Fauthors\u002Fraphael-sauget",{"title":1738,"description":10},"authors\u002Fraphael-sauget","Uri9bcq0QDuxRA0PbBoNtu7p_5L3dALu4kzcXVW0xyM",{"id":1942,"title":1943,"body":1944,"description":10,"extension":13,"meta":1948,"name":1949,"navigation":16,"path":1950,"readingTime":18,"seo":1951,"stem":1952,"__hash__":1953},"authors\u002Fauthors\u002Fromain-koenig.md","Co-funder & Head of innovation",{"type":7,"value":1945,"toc":1946},[],{"title":10,"searchDepth":11,"depth":11,"links":1947},[],{},"Romain Koenig","\u002Fauthors\u002Fromain-koenig",{"title":1943,"description":10},"authors\u002Fromain-koenig","uyS8--eG2_ezyqRABcJnMJmQKKuSArhPWd14aUvFeEw",{"id":1955,"title":1738,"body":1956,"description":10,"extension":13,"meta":1960,"name":1961,"navigation":16,"path":1962,"readingTime":18,"seo":1963,"stem":1964,"__hash__":1965},"authors\u002Fauthors\u002Fromaric-juniet.md",{"type":7,"value":1957,"toc":1958},[],{"title":10,"searchDepth":11,"depth":11,"links":1959},[],{},"Romaric Juniet","\u002Fauthors\u002Fromaric-juniet",{"title":1738,"description":10},"authors\u002Fromaric-juniet","4Zb2artgT-eo-PHLXi3xi4d5t7s6PfhUxeSfXIikSUY",{"id":1967,"title":5,"body":1968,"description":10,"extension":13,"meta":1972,"name":1973,"navigation":16,"path":1974,"readingTime":18,"seo":1975,"stem":1976,"__hash__":1977},"authors\u002Fauthors\u002Fstanyslas-bres.md",{"type":7,"value":1969,"toc":1970},[],{"title":10,"searchDepth":11,"depth":11,"links":1971},[],{},"Stanyslas Bres","\u002Fauthors\u002Fstanyslas-bres",{"title":5,"description":10},"authors\u002Fstanyslas-bres","Xa0SahETuiN4q1jrmR2ych3moAqcZ2LbU7vSfEt2RuU",{"id":1979,"title":1980,"body":1981,"description":10,"extension":13,"meta":1985,"name":1986,"navigation":16,"path":1987,"readingTime":18,"seo":1988,"stem":1989,"__hash__":1990},"authors\u002Fauthors\u002Ftalent-acquisition.md","Talent Acquisition",{"type":7,"value":1982,"toc":1983},[],{"title":10,"searchDepth":11,"depth":11,"links":1984},[],{},"Équipe Talent Acquisition","\u002Fauthors\u002Ftalent-acquisition",{"description":10},"authors\u002Ftalent-acquisition","doDfE76txftQ4wIiKjJoDmSpyzSKk0tzlgVAp6-opAY",{"id":1992,"title":5,"body":1993,"description":10,"extension":13,"meta":1997,"name":1998,"navigation":16,"path":1999,"readingTime":18,"seo":2000,"stem":2001,"__hash__":2002},"authors\u002Fauthors\u002Fvictor-borg.md",{"type":7,"value":1994,"toc":1995},[],{"title":10,"searchDepth":11,"depth":11,"links":1996},[],{},"Victor Borg","\u002Fauthors\u002Fvictor-borg",{"title":5,"description":10},"authors\u002Fvictor-borg","-Za-JweoiP6hyclue_WkxMXdRUDTczPGlJf6AZckjUc",{"id":2004,"title":5,"body":2005,"description":10,"extension":13,"meta":2009,"name":2010,"navigation":16,"path":2011,"readingTime":18,"seo":2012,"stem":2013,"__hash__":2014},"authors\u002Fauthors\u002Fvirgil-roger.md",{"type":7,"value":2006,"toc":2007},[],{"title":10,"searchDepth":11,"depth":11,"links":2008},[],{},"Virgil Roger","\u002Fauthors\u002Fvirgil-roger",{"title":5,"description":10},"authors\u002Fvirgil-roger","DfVFe5j0bCgXeEr381ZYOM5DP4m-pWb93J9-m_muKJ0",{"id":2016,"title":5,"body":2017,"description":10,"extension":13,"meta":2021,"name":2022,"navigation":16,"path":2023,"readingTime":18,"seo":2024,"stem":2025,"__hash__":2026},"authors\u002Fauthors\u002Fyukan-zhao.md",{"type":7,"value":2018,"toc":2019},[],{"title":10,"searchDepth":11,"depth":11,"links":2020},[],{},"Yukan Zhao","\u002Fauthors\u002Fyukan-zhao",{"title":5,"description":10},"authors\u002Fyukan-zhao","LRPHugtAJnWHsmHxy9_SR5Zas_C5p-GR_uHEs1Fhk_E",1778159244452]