323 lines
25 KiB
JSON
323 lines
25 KiB
JSON
{
|
||
"name": "MongoDB and Mongoose",
|
||
"order": 3,
|
||
"time": "5 hours",
|
||
"helpRoom": "Help",
|
||
"challenges": [
|
||
{
|
||
"id": "587d7fb5367417b2b2512c05",
|
||
"title": "Introduction to the MongoDB and Mongoose Challenges",
|
||
"description": [
|
||
[
|
||
"",
|
||
"",
|
||
"MongoDB is a database that stores data records (documents) for use by an application. Mongo is a non-relational, \"NoSQL\" database. This means Mongo stores all data associated within one record, instead of storing it across many preset tables as in a SQL database. Some benefits of this storage model are:<br><br><ul><li>Scalability: by default, non-relational databases are split (or \"sharded\") across many systems instead of only one. This makes it easier to improve performance at a lower cost.</li><li>Flexibility: new datasets and properties can be added to a document without the need to make a new table for that data.</li><li>Replication: copies of the database run in parallel so if one goes down, one of the copies becomes the new primary data source.</li></ul><br>While there are many non-relational databases, Mongo's use of JSON as its document storage structure makes it a logical choice when learning backend JavaScript. Accessing documents and their properties is like accessing objects in JavaScript.<br><br>Mongoose.js is an npm module for Node.js that allows you to write objects for Mongo as you would in JavaScript. This can make is easier to construct documents for storage in Mongo.",
|
||
""
|
||
],
|
||
[
|
||
"",
|
||
"",
|
||
"Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.<br>Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-mongomongoose/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-mongomongoose/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!",
|
||
""
|
||
]
|
||
],
|
||
"releasedOn": "Feb 17, 2017",
|
||
"challengeSeed": [],
|
||
"tests": [],
|
||
"type": "waypoint",
|
||
"challengeType": 7,
|
||
"isRequired": false,
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb5367437b2b2512c05",
|
||
"title": "Create an online database using mLab",
|
||
"description": [
|
||
[
|
||
"//i.imgur.com/Ux1CD2f.png",
|
||
"mLab logo.",
|
||
"For the following challenges, we are going to start using MongoDB to store our data. To simplify the configuration, we are going to use mLab.<br><br>mLab is a MongoDB Database-as-a-Service platform, which basically means that they configure and host the database for you, making it so the only responsibility you have is to populate your database with what matters: data!<br>In the following steps, we are going to show you how to: <ul><li>Create an mLab account.</li><li>Create a free online database.</li><li>Create a new admin user on the database, so you can access it.</li><li>Get the mLab URI, which you will use in your application to connect to your database.</li></ul>Let's start by going to mLab by clicking the button below.",
|
||
"https://mlab.com/"
|
||
],
|
||
[
|
||
"",
|
||
"",
|
||
"Once you open the mLab page, you should sign up for a new account.<ul><li>Click the <a href='https://mlab.com/signup/'>Sign Up</a> button in the top right corner to open the registration page.</li><li>Fill the registration form with your information and send it.</li><li>You should be logged into your new, unverified account.</li><li>In the top of the screen, a message should appear asking to send you an e-mail for account verification. Send and confirm it.</li><li>After you confirm your account, click <bold>Create new</bold> in the <i>MongoDB Deployments</i> section.</li></ul>"
|
||
],
|
||
[
|
||
"",
|
||
"",
|
||
"Now we are going to create the actual database you are going to be using.<ul><li>Choose a <i>Cloud Provider</i> from the available list.</li><li>Select the <i>Sandbox</i> plan type, which is the only one with no cost, and press <bold>Continue</bold>.</li><li>Select a region for your Sandbox, from the available list, and press <bold>Continue</bold>.</li><li>Input a name for your database. This name will be used in the URI for your database. After that, press <bold>Continue</bold>.</li></ul>A summary of all your choices should appear, allowing you to change any information provided in the previous steps. Press <bold>Submit Order</bold> to confirm the information."
|
||
],
|
||
[
|
||
"",
|
||
"",
|
||
"After you confirmed your configuration, a new sandbox should have been created in the <i>MongoDB Deployments</i> section. We are now going to create an administrator, so you can use the database in your application.<ul><li>Click the newly-created database.</li><li>Click the <i>Users</i> section.</li><li>Click the <bold>Add database user</bold> button.</li><li>Input an username and password for your administrator. Do not mark it as read-only or you will not be able to add any information to the database with this user.</li></ul>"
|
||
],
|
||
[
|
||
"",
|
||
"",
|
||
"Almost done! We have created our new database and created an user to access it, so we just need to find a way to use it in our applications.<ul><li>In your database page, you should see some instructions about connecting using the standard MongoDB URI.</li><li>The line should look like this <code>mongodb://dbuser:dbpassword@ds0$PORT$.mlab.com:$PORT$/$DATABASE-NAME$</code>.</li><li>Copy this URI and substitute dbuser and dbpassword with the information for the user you previously created in the database.</li><li>That's it! This is the URI you will add to your application to connect to your database. Keep this URI safe somewhere, so you can use it later!</li>Feel free to create separate databases for different applications, if they don't have an use for the same data. You just need to create the sandbox, user and obtain the new URI.</ul>"
|
||
]
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [],
|
||
"type": "waypoint",
|
||
"challengeType": 7,
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb6367417b2b2512c06",
|
||
"title": "Install and Set Up Mongoose",
|
||
"description": [
|
||
"Add mongodb and mongoose to the project’s package.json. Then require mongoose. Store your mLab database URI in the private .env file as MONGO_URI. Connect to the database using mongoose.connect(<Your URI>)"
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "\"mongodb\" dependency should be in package.json",
|
||
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/file/package.json').then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'mongodb'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
},
|
||
{
|
||
"text": "\"mongoose\" dependency should be in package.json",
|
||
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/file/package.json').then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'mongooose'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb6367417b2b2512c07",
|
||
"title": "Create a Model",
|
||
"description": [
|
||
"First of all we need a Schema. Each schema maps to a MongoDB collection. It defines the shape of the documents within that collection.",
|
||
"Schemas are building block for Models. They can be nested to create complex models, but in this case we’ll keep things simple.",
|
||
"A model allows you to create instances of your objects, called documents.",
|
||
"Create a person having this prototype :",
|
||
"<code>- Person Prototype -</code>",
|
||
"<code>--------------------</code>",
|
||
"<code>name : string [required]</code>",
|
||
"<code>age : number</code>",
|
||
"<code>favoriteFoods : array of strings (*) </code>",
|
||
"Use the mongoose basic schema types. If you want you can also add",
|
||
"more fields, use simple validators like required or unique,",
|
||
"and set default values. See the <a href='http://mongoosejs.com/docs/guide.html'>mongoose docs</a>.",
|
||
"[C]RUD Part I - CREATE",
|
||
"Note: Glitch is a real server, and in real servers the interactions with the db happen in handler functions. These function are executed when some event happens (e.g. someone hits an endpoint on your API). We’ll follow the same approach in these exercises. The done() function is a callback that tells us that we can proceed after completing an asynchronous operation such as inserting, searching, updating or deleting. It’s following the Node convention and should be called as done(null, data) on success, or done(err) on error.",
|
||
"Warning - When interacting with remote services, errors may occur !",
|
||
"<code>/* Example */</code>",
|
||
"<code>var someFunc = function(done) {</code>",
|
||
"<code> //... do something (risky) ...</code>",
|
||
"<code> if(error) return done(error);</code>",
|
||
"<code> done(null, result);</code>",
|
||
"<code>};</code>"
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Creating an instance from a mongoose schema should succeed",
|
||
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/mongoose-model', {name: 'Mike', age: 28, favoriteFoods: ['pizza', 'cheese']}).then(data => { assert.equal(data.name, 'Mike', '\"model.name\" is not what expected'); assert.equal(data.age, '28', '\"model.age\" is not what expected'); assert.isArray(data.favoriteFoods, '\"model.favoriteFoods\" is not an Array'); assert.include(data.favoriteFoods, 'pizza', '\"model.favoriteFoods\" does not include the expected items'); assert.include(data.favoriteFoods, 'cheese', '\"model.favoriteFoods\" does not include the expected items'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb6367417b2b2512c09",
|
||
"title": "Create and Save a Record of a Model",
|
||
"description": [
|
||
"Create a document instance using the Person constructor you build before. Pass to the constructor an object having the fields name, age, and favoriteFoods. Their types must be conformant to the ones in the Person Schema. Then call the method document.save() on the returned document instance. Pass to it a callback using the Node convention. This is a common pattern, all the following CRUD methods take a callback function like this as the last argument.",
|
||
"<code>/* Example */</code>",
|
||
"<code>// ...</code>",
|
||
"<code>person.save(function(err, data) {</code>",
|
||
"<code>// ...do your stuff here...</code>",
|
||
"<code>});</code>"
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Creating and saving a db item should succeed",
|
||
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/create-and-save-person').then(data => { assert.isString(data.name, '\"item.name\" should be a String'); assert.isNumber(data.age, '28', '\"item.age\" should be a Number'); assert.isArray(data.favoriteFoods, '\"item.favoriteFoods\" should be an Array'); assert.equal(data.__v, 0, 'The db item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb7367417b2b2512c0a",
|
||
"title": "Create Many Records with model.create()",
|
||
"description": [
|
||
"Sometimes you need to create many instances of your models, e.g. when seeding a database with initial data. Model.create() takes an array of objects like [{name: 'John', ...}, {...}, ...] as the first argument, and saves them all in the db. Create many people with Model.create(), using the function argument arrayOfPeople."
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Creating many db items at once should succeed",
|
||
"testString": "getUserInput => $.ajax({url: getUserInput('url') + '/_api/create-many-people', type: 'POST', contentType:'application/json', data: JSON.stringify([{name: 'John', age: 24, favoriteFoods: ['pizza', 'salad']}, {name: 'Mary', age: 21, favoriteFoods: ['onions', 'chicken']}])}).then(data => { assert.isArray(data, 'the response should be an array'); assert.equal(data.length, 2, 'the response does not contain the expected number of items'); assert.equal(data[0].name, 'John', 'The first item is not correct'); assert.equal(data[0].__v, 0, 'The first item should be not previously edited'); assert.equal(data[1].name, 'Mary', 'The second item is not correct'); assert.equal(data[1].__v, 0, 'The second item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb7367417b2b2512c0b",
|
||
"title": "Use model.find() to Search Your Database",
|
||
"description": [
|
||
"Find all the people having a given name, using Model.find() -> [Person]",
|
||
"In its simplest usage, Model.find() accepts a query document (a JSON object ) as the first argument, then a callback. It returns an array of matches. It supports an extremely wide range of search options. Check it in the docs. Use the function argument personName as search key."
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Find all items corresponding to a criteria should succeed",
|
||
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/find-all-by-name', {name: 'r@nd0mN4m3', age: 24, favoriteFoods: ['pizza']}).then(data => { assert.isArray(data, 'the response should be an Array'); assert.equal(data[0].name, 'r@nd0mN4m3', 'item.name is not what expected'); assert.equal(data[0].__v, 0, 'The item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb7367417b2b2512c0c",
|
||
"title": "Use model.findOne() to Return a Single Matching Document from Your Database",
|
||
"description": [
|
||
"Model.findOne() behaves like .find(), but it returns only one document (not an array), even if there are items. It is especially useful when searching by properties that you have declared as unique. Find just one person which has a certain food in her favorites, using Model.findOne() -> Person. Use the function argument food as search key."
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Find one item should succeed",
|
||
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/find-one-by-food', {name: 'Gary', age: 46, favoriteFoods: ['chicken salad']}).then(data => { assert.equal(data.name, 'Gary', 'item.name is not what expected'); assert.deepEqual(data.favoriteFoods, ['chicken salad'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 0, 'The item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb7367417b2b2512c0d",
|
||
"title": "Use model.findById() to Search Your Database By _id",
|
||
"description": [
|
||
"When saving a document, mongodb automatically adds the field _id, and set it to a unique alphanumeric key. Searching by _id is an extremely frequent operation, so moongose provides a dedicated method for it. Find the (only!!) person having a given _id, using Model.findById() -> Person. Use the function argument personId as search key."
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Find an item by Id should succeed",
|
||
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/find-by-id').then(data => { assert.equal(data.name, 'test', 'item.name is not what expected'); assert.equal(data.age, 0, 'item.age is not what expected'); assert.deepEqual(data.favoriteFoods, ['none'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 0, 'The item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb8367417b2b2512c0e",
|
||
"title": "Perform Classic Updates by Running Find, Edit, then Save",
|
||
"description": [
|
||
"In the good old days this was what you needed to do if you wanted to edit a document and be able to use it somehow e.g. sending it back in a server response. Mongoose has a dedicated updating method : Model.update(). It is binded to the low-level mongo driver. It can bulk edit many documents matching certain criteria, but it doesn’t send back the updated document, only a ‘status’ message. Furthermore it makes model validations difficult, because it just directly calls the mongo driver.",
|
||
"Find a person by _id ( use any of the above methods ) with the parameter personId as search key. Add “hamburger” to the list of her favoriteFoods (you can use Array.push()). Then - inside the find callback - save() the updated Person.",
|
||
"[*] Hint: This may be tricky if in your Schema you declared favoriteFoods as an Array, without specifying the type (i.e. [String]). In that casefavoriteFoods defaults to Mixed type, and you have to manually mark it as edited using document.markModified('edited-field'). (http://mongoosejs.com/docs/schematypes.html - #Mixed )"
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Find-edit-update an item should succeed",
|
||
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/find-edit-save', {name:'Poldo', age: 40, favoriteFoods:['spaghetti']}).then(data => { assert.equal(data.name, 'Poldo', 'item.name is not what expected'); assert.equal(data.age, 40, 'item.age is not what expected'); assert.deepEqual(data.favoriteFoods, ['spaghetti', 'hamburger'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 1, 'The item should be previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb8367417b2b2512c0f",
|
||
"title": "Perform New Updates on a Document Using model.findOneAndUpdate()",
|
||
"description": [
|
||
"Recent versions of mongoose have methods to simplify documents updating. Some more advanced features (i.e. pre/post hooks, validation) behave differently with this approach, so the Classic method is still useful in many situations. findByIdAndUpdate() can be used when searching by Id.",
|
||
"Find a person by Name and set her age to 20. Use the function parameter personName as search key.",
|
||
"Hint: We want you to return the updated document. o do that you need to pass the options document { new: true } as the 3rd argument to findOneAndUpdate(). By default these methods return the unmodified object."
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "findOneAndUpdate an item should succeed",
|
||
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/find-one-update', {name:'Dorian Gray', age: 35, favoriteFoods:['unknown']}).then(data => { assert.equal(data.name, 'Dorian Gray', 'item.name is not what expected'); assert.equal(data.age, 20, 'item.age is not what expected'); assert.deepEqual(data.favoriteFoods, ['unknown'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 0, 'findOneAndUpdate does not increment version by design !!!'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb8367417b2b2512c10",
|
||
"title": "Delete One Document Using model.findByIdAndRemove",
|
||
"description": [
|
||
"Delete one person by her _id. You should use one of the methods findByIdAndRemove() or findOneAndRemove(). They are like the previous update methods. They pass the removed document to the cb. As usual, use the function argument personId as search key."
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Deleting an item should succeed",
|
||
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/remove-one-person', {name:'Jason Bourne', age: 36, favoriteFoods:['apples']}).then(data => { assert.equal(data.name, 'Jason Bourne', 'item.name is not what expected'); assert.equal(data.age, 36, 'item.age is not what expected'); assert.deepEqual(data.favoriteFoods, ['apples'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 0); assert.equal(data.count, 0, 'the db items count is not what expected'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb8367417b2b2512c11",
|
||
"title": "Delete Many Documents with model.remove()",
|
||
"description": [
|
||
"Model.remove() is useful to delete all the documents matching given criteria. Delete all the people whose name is “Mary”, using Model.remove(). Pass to it a query ducument with the “name” field set, and of course a callback.",
|
||
"Note: Model.remove() doesn’t return the deleted document, but a JSON object containing the outcome of the operation, and the number of items affected. Don’t forget to pass it to the done() callback, since we use it in tests."
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Deleting many items at once should succeed",
|
||
"testString": "getUserInput => $.ajax({url: getUserInput('url') + '/_api/remove-many-people', type: 'POST', contentType:'application/json', data: JSON.stringify([{name: 'Mary', age: 16, favoriteFoods: ['lollipop']}, {name: 'Mary', age: 21, favoriteFoods: ['steak']}])}).then(data => { assert.isTrue(!!data.ok, 'The mongo stats are not what expected'); assert.equal(data.n, 2, 'The number of items affected is not what expected'); assert.equal(data.count, 0, 'the db items count is not what expected'); }, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
},
|
||
{
|
||
"id": "587d7fb9367417b2b2512c12",
|
||
"title": "Chain Search Query Helpers to Narrow Search Results",
|
||
"description": [
|
||
"If you don’t pass the callback as the last argument to Model.find() (or to the other search methods), the query is not executed. You can store the query in a variable for later use. This kind of object enables you to build up a query using chaining syntax. The actual db search is executed when you finally chain the method .exec(). Pass your callback to this last method. There are many query helpers, here we’ll use the most ‘famous’ ones.",
|
||
"Find people who like \"burrito\". Sort them by name, limit the results to two documents, and hide their age. Chain .find(), .sort(), .limit(), .select(), and then .exec(). Pass the done(err, data) callback to exec()."
|
||
],
|
||
"challengeSeed": [],
|
||
"tests": [
|
||
{
|
||
"text": "Chaining query helpers should succeed",
|
||
"testString": "getUserInput => $.ajax({url: getUserInput('url') + '/_api/query-tools', type: 'POST', contentType:'application/json', data: JSON.stringify([{name: 'Pablo', age: 26, favoriteFoods: ['burrito', 'hot-dog']}, {name: 'Ashley', age: 32, favoriteFoods: ['steak', 'burrito']}, {name: 'Mario', age: 51, favoriteFoods: ['burrito', 'prosciutto']} ]) }).then(data => { assert.isArray(data, 'the response should be an Array'); assert.equal(data.length, 2, 'the data array length is not what expected'); assert.notProperty(data[0], 'age', 'The returned first item has too many properties'); assert.equal(data[0].name, 'Ashley', 'The returned first item name is not what expected'); assert.notProperty(data[1], 'age', 'The returned second item has too many properties'); assert.equal(data[1].name, 'Mario', 'The returned second item name is not what expected');}, xhr => { throw new Error(xhr.responseText); })"
|
||
}
|
||
],
|
||
"solutions": [],
|
||
"hints": [],
|
||
"type": "backend",
|
||
"translations": {}
|
||
}
|
||
]
|
||
}
|