By the end of this lesson, you should be able to connect to a non-relational database (MongoDB) and perform basic CRUD operations.
- Interact with a document database
- Connect to a MongoDB database with Mongoose
- Perform basic CRUD operations on the database
- Build and run specific queries
- Validate schemas
- Model complex application ideas using embedded data models
-
Fork & clone
-
cp nodemon.sample.json nodemon.json -
Create a new Cluster on MongoDB Atlas titled something like
general-purposeor reuse one you already have. -
Update the
MONGO_DB_CONNECTIONin yournodemon.jsonfile with the full connection string. Make sure you include the password you set up for the database and change the name of the database fromtestto something lketelevision_characters_dev. -
npm install -
npm run dev
Once installation is working, try creating and requesting resources. Note that there is currently no validation set up.
- Take a moment to look through the code that already exists in this repository. Run the code and test out each route, ensuring it does what you expect.
- We will be using both mongoose and mongodb to build this project. Open up each project's documentation.
-
Question: What is the difference between mongoose and mongodb?
-
Your Answer: mongodb is a document-based NoSQL database. Mongoose is used to easily persist javascript objects in mongodb.
- MongoDB uses a number of terms that may be new to you such as database, collection, and document.
-
Question: What is the difference between the above terms? This page may help.
-
Your Answer: A database holds collections of documents. A document is a record made up of key/value pairs. A collection is a set of documents.
- Create a new file with the path of
api/models/series.js. In that file, define the schema for a television series. Include the fields "title", "start_year", and "season_count".
-
Question: Where does
StringandNumbercome from? -
Your Answer: They are javascript global constructors.
- Merge the following into your schema:
{ _id: Schema.Types.ObjectId }
-
Question: Describe what the above code is adding to your schema.
-
Your Answer: It is defining a primary key for the documents.
- Remove the line we just added from your schema.
-
Question: If we create a new document, will there be an ID? Why or why not?
-
Your Answer: Yes. It will be auto-generated.
- Add the following object as the second argument to the
new Schema()statement.{ timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }
-
Question: Describe what the above code is adding to your schema.
-
Your Answer: re-mapping the createdAt and updatedAt fields to snake-case.
- Import the model into your
routes/series.jsfile. Use either basic promises orasync/awaitto update theGET /method to retrieve from the database instead of the given array. This page or this page may be useful.
-
Question: What method do you use to return all documents from a collection?
-
Your Answer:
Model.find()
- Update the API so that you can create documents in the database. You may either use the
<document>.save()method or theModel.create()method.
-
Question: What happens if you do not include all the fields as specified in the schema?
-
Your Answer: It creates the document without the missing fields.
- Take a moment to view your new document in the MongoDB Atlas console.
- Update the API so that you retrieve a document by ID.
-
Question: There are a couple different ways to accomplish this goal. Which method did you choose?
-
Your Answer:
await Series.findById(req.params.id)
- Update the API so that you are able to update a document. Use the Model.updateOne() method.
-
Question: What are the arguments for
Model.updateOne()? -
Your Answer: a filter object for selecting the document to update and another object with the fields that you wish to update.
-
Question: The response you receive is not the document you updated. What information is being represented here? Try replacing
Model.updateOne()with the Model.findOneAndUpdate() and see the difference in the result. -
Your Answer: You get back information about the operation like how many documents were modified and how long it took. With
findOneAndUpdate()you get back the document that was found. -
Question: This new method will return the old document instead of the new one. What option can you add to your statement to return the new one?
-
Your Answer:
{ new: true } -
Question: Another field was updated when you ran this command. Which one was it?
-
Your Answer:
updated_at -
Question: Take a look at the terminal window running your server. You are likely getting the following deprecation warning:
DeprecationWarning: Mongoose: `findOneAndUpdate()` and `findOneAndDelete()` without the `useFindAndModify` option set to false are deprecated. See: https://mongoosejs.com/docs/deprecations.html#-findandmodify-Take a look at this page to see how to fix it. Describe the changes that took place.
-
Your Answer: added
useFindAndModify: falseto themongoose.connect()in app.js
- Update the API so that you can successfully delete a record. Use the Model.findOneAndDelete() method.
- A lot of information is being returned on each request. Some of this information we may want to keep private. Using the .select() method, return only the "_id", "title", "start_year", and "season_count" on all the above requests.
-
Question: At least one of these will throw an error. Which one? How can you get around this issue?
-
Your Answer: Create() throws an error because it does not return a Query. You can just create your own response object populated with the desired fields.
- Modify your
GET /api/seriesroute so that you can search through the content based off of query parameters. For example, if your request wasGET /api/series?start_year=1997, it would return all series that start in 1997.
- At the moment, there is no validation set on creating any series. Add validation so that each of the three fields are required in order to post to series. Handle any errors that arise with a status code 400 and a brief, standardized message.
-
Question: You may notice that you can add custom error messages to individual fields failing. Try adding these and take a look at the error message received. How can you make use of those specific messages for each field?
-
Your Answer: A ValidationError is thrown. You can get the specific errors inside this object.
- With Mongo, it is simple to create complex data structures. Add a
charactersfield to your Series model that is an array of objects. Each object should have anameandimage_url. Only thenamefield on the character should be required. Note: Don't forget to change your select statements to include thecharactersfield!
-
Question: Take a look at the response from making a new series with a character. What field was created that you did not have to define?
-
Your Answer: The
_idfor characters. -
Question: With the current routes that we have, how would you upate the name of a character in a series?
-
Your Answer: You can use the
PUTroute to update the series. Unfortunately, you have to set the entire character list again to change a single character's name.
- While we can now update subdocuments, it is difficult to make changes that only relate to a single subdocument. To do so, we should make a new set of routes that relates to characters. Start by creating a
GET ALLroute for characters. The route will look something like the following and will return only the list of characters:GET /api/series/:seriesID/characters
-
Question: Where did you decide to put this route and why?
-
Your Answer: I put it in
routes/series.jssince the characters are a subdocument of Series, but I guess it could be put in a new file.
Spend the rest of class building out the other routes for characters. A few notes to help:
- It's worth reading the subdocument documentation in full
- Take note of the
.id()method for finding subdocuments by id - Note that in order to save subdocuments, you will need to save the parent record; in our case, you should be calling
series.save(), notcharacter.save()
If you're interested in going further, I would recommend looking into references.