<style> @import url('https://fonts.googleapis.com/css?family=Roboto+Mono&display=swap'); .reveal { font-family: "Roboto Mono", monospace; font-size: 40px; } </style> # MongoDB 🍃 ### Open Source @ UCSD ###### tags: `MERN` ###### Install mongoose in your **backend** directory for this workshop: ```npm install mongoose``` --- ## What is MongoDB? * **MongoDB** is a **document-based** open source database program * How MongoDB and other document-based databases store data * Uses a JSON-like format! (key:value pairs) * **Collections**: holds documents that represent the same type of data * **Documents**: represents specific data points with attributes ---- ## Example * You want to store multiple users in your database, and each user will have a name, age, and gender. * To store this, you will have a collection called 'users' and each document inside the collection will represent a single user. * In each document, you will store the single user's name, age, and gender. ---- ## Users Example ![](https://i.imgur.com/NoMExYV.png =700x) --- ![](https://media-cdn.tripadvisor.com/media/photo-s/02/2c/2e/e0/filename-ant1100-jpg.jpg) --- ## What is Mongoose? * In a technical sense, **Mongoose** is a simple, schema-based solution to model application data * In an understandable sense, Mongoose is essentially a tool that makes using MongoDB in Node.js easier. * Allows you to make "schemas" for your data --- ## What Data are we Storing? Before starting anything, we need to know what data we are actually storing in this project. What data we are storing will depend on what our project is doing. ---- ## Users Collection * Let's think about what data each user should have. Appications commonly store the user's name and email. Something that Twitter also has is a user bio. ---- ## Tweets Collection * The main functionality of our application will be posting tweets, so we'll need to store a **tweets collection**. * What attributes will each tweet have? Probably the name of the user who posted the tweet and the text that makes up the tweet. * We can also store the date and time that the tweet was posted. --- Now let's get started with setting everything up! We will be creating a MongoDB database to store the data we need for our Twitter clone! --- ## Setting up MongoDB Atlas MongoDB Atlas is a **free** tool we'll use to manage our database (super useful for checking what data is being stored!) ---- 1. Head over to https://www.mongodb.com/cloud/atlas 2. In the top right corner, click 'Try Free' 3. Enter all required information (you can use either your personal email or UCSD email) 4. Choose 'Starter Clusters' (it's free!) ---- 5. Choose Google Cloud Platform as your cloud provider and Iowa as our region ![](https://i.imgur.com/gsOeXX1.png) ---- 6. Click 'Create Cluster' and take a break, because it'll take a couple minutes to create your cluster! 👏 --- Now we'll be moving onto making the actual connection to MongoDB in our application. Be sure to have your project open in your editor! --- ## Setting up a MongoDB Connection in Node.js 1. Install mongoose in your terminal in your backend folder ``` npm install mongoose ``` 2. Require mongoose in your backend/index.js file ```javascript // inside of backend/index.js const mongoose = require('mongoose'); ``` ---- 3. Inside of your backend folder, create a config.js file. This file will hold all of the data we need to configurate our database. 4. Go to your cluster on MongoDB Atlas (in your browser) and click 'connect' ---- 6. Click 'Add Your Current IP Address' and your IP address will already be filled out for you 7. Create a new MongoDB user. Remember your password! 8. Click 'Choose a Connection Method' 9. Choose 'Connect Your Application' ---- 8. Now, copy the connection string. We'll be pasting this into our config file. ![](https://i.imgur.com/tpYMG1N.png =700x) ---- 9. Go into your config.js file and paste the connection string as a variable, ATLAS_URI ```javascript= module.exports = { ATLAS_URI: // your connection string here } ``` We use module.exports so that we can access the variable ATLAS_URI in files outside of config.js Note: replace `<password>` in the connection string with the password you gave the user. ---- 10. Make the connection in backend/index.js! ```javascript= // get the uri from the config file const uri = require('./config.js).ATLAS_URI; // start the connection mongoose.connect(uri, {useNewUrlParser: true, useCreateIndex: true}); const connection = mongoose.connection; connection.once('open', () => { console.log('MongoDB database connected!'); }); ``` 🏁 Checkpoint 1 🏁: Run `node index.js`. You should see "MongoDB database connected!" on your command line! ---- Wowow, we're all done setting up! Now let's talk about **Mongoose schemas** and how to structure our database. --- # Mongoose Schemas ---- ## What are Mongoose Schemas? A schema pretty much defines what each document in a collection will look like. You will be defining the name of each key and the type of the value for that key. ---- In general, we want a schema for every collection. In our project, we will have a **users** collection and a **tweets** collection, so we will need to set up the schema for both users and tweets. ---- Let's jump right into this by setting up our users schema! --- ## Creating Schemas 1. In your backend folder, create a models folder (we'll talk about why it's called models later) 2. Create the file for your users schema called user.model.js All schema files will share the same name structure: [name of collection].model.js ---- Your backend directory should have this structure: ``` backend |_ index.js |_ index.html |_ routes |_ users.js |_ tweets.js |_ models |_ user.model.js ``` ---- Inside of user.model.js, add the following: ```javascript= var mongoose = require('mongoose'); var Schema = mongoose.Schema; // the schema for our users collection var userSchema = new Schema({ name: String, email: String, bio: String, }); ``` ---- 3. We will be converting this schema into a Model. Each instance of a users model represents a single user (a.k.a. a document). Add this code below your userSchema: ```javascript= var User = mongoose.model('User', userSchema); module.exports = User; ``` ---- Since each instance of a user model represents a single user, when we create or read a model instance, we are creating or reading a user. We export the model so that we can do these actions with our database outside of this file. --- ## More About Schemas There are multiple data types that Schemas allow. Here are some of the most common: 1. String 2. Number 3. Date (a Javascript Date object) 4. Boolean 5. Array 6. ObjectId (the ID of another document in your database) 7. Map ---- You can also add **validations** to each of your Schema fields. Here's an example: ```javascript name: { type: String, required: true, unique: false, trim: true, minlength: 5, } ``` ---- * The first validation is **type**, which defines the data type of the field (name is a String, here) * Next is **required**, which means that we will receive an error if we try to create a user document without a name. ---- * The next validation is **unique**. We set it to false here, meaning we can have multiple documents with the same name value. If we set unique to true, then we will receive an error if we try to create a document with a duplicate name value. * The **trim** validation removes whitespace before and after the given String if trim is set to true. ---- * Finally, we have **minlength**, which determines the minimum number of characters the name can be. We will receive an error if the name length is less than that minimum. --- ## Embedded Data vs Referenced Data This is one of the big questions when determine how you want your database to look. What's the difference?! 🙀 ---- * **Embedded data** means putting the data straight into the document. * **Referenced data** means creating a separate collection/document, which will have it's own ObjectId, and referencing the ObjectId in the original document. ---- **Note:** ObjectId's are id's generated by MongoDB when you create a new document. The type of the id is ObjectId If you wanted to make a comparison between an ObjectId and a String, you would need to convert one type to the other. ```javascript ObjectId('123njddkjwnks8').toString(); //returns string ``` ---- Here's an example of embedded and referenced data using an example of a tweet object that stores the user that posted the tweet, the tweet text, and the date posted. ---- ```javascript= // embedding data straight into the document tweet1 = { user: 'Chau Vu', text: 'I\'m a short, fun-sized guy', createdAt: 27 July 2016 13:30:00 GMT+05:45 } tweet2 = { user: 'Chau Vu', text: 'It\'s every day bro, do you give up at night?', createdAt: 29 July 2016 13:30:00 GMT+05:45 } ``` ---- ```javascript= /* Referencing data from other collection We have a users collection, where Chau is a document in it with an ObjectId of 420 */ tweet1 = { user: ObjectId(420), text: 'I\'m a fun-sized, super nice guy', createdAt: 27 July 2016 13:30:00 GMT+05:45 } tweet2 = { user: ObjectId(420), text: 'It\'s every day bro, do you give up at night?', createdAt: 29 July 2016 13:30:00 GMT+05:45 } ``` ---- ## Which one to use? * The most popular reason for using data references rather than embedding data is due to **data repeats**. * For example, both of our tweet objects have the same user who posted them, so we have repeat values for our user fields. ---- * If Chau were to post thousands of tweets, our database may become unnecessarily slow and bulky from the repeated information * It'd be difficult to make changes to the user, as we'd have to change every single tweet document made by Chau. ---- * To avoid this, we can just have one instance of Chau as a separate user document and simply reference that document in all of Chau's tweets. ---- * Besides this, embedding data is usually preferred over reference because MongoDB is efficient at querying data within a document's structure. --- ## Your Turn! **Your task:** We created the user schema for our project, but we still need to create a tweets schema! Think about what attributes each tweet will have (we discussed this earlier) and do the following: * Create your schema in tweet.model.js 🐣 **Next:** Actually making CRUD operations on our database! --- # CRUD Operations ---- ## What are CRUD Operations? **CRUD** stands for create, read, update, and delete. These are the basic operations for manipulating data in our database. We will be doing these CRUD operations using API routers (created with Express) to endpoints that we will create. ---- ## Quick Note About Request Types The two most common request types are **GET** and **POST** requests ---- * When we make a GET request, we are simply retrieving information from our database * When we make a POST request, we are making changes to our database, such as adding to the database, updating the database, etc. --- # Let's review API routes and endpoints! ---- ## What are API Routes and Endpoints? * Endpoints are functions available through our API * Inside of these functions, we perform our CRUD operations, such as adding a user, creating a tweet, deleting a tweet, etc. * You will have an endpoint for each specific operation ---- * Routes are names you use to access the endpoint, which are usually appended to your URL. ---- * **Example:** We have an endpoint that adds a user. The route to the endpoint is '/user/add' and our base URL is 'http://localhost:5000'. * If we want to call our endpoint to add a user, we will make an HTTP request to the URL 'http://localhost:5000/user/add'. --- ## Review & Adding Database Queries 1. Create a routes folder inside of our backend folder. 2. Inside of the routes folder, create users.js This will be the file that holds all of our endpoints that deal with the users collection (adding users, deleting users, editing users, etc.) ---- 3. We will first be creating our endpoint to get all users in the database. ```javascript= const router = require('express').Router(); const User = require('../models/user.model.js'); /* @route GET /users/ @desc Gets all users @access Public */ router.route('/').get((req, res) => { User.find() .then(users => res.json(users)) .catch(err => res.status(400).json(err)) }); module.exports = router; ``` ---- Let's break this code down. ---- * The route here will be /users/, but how come we are setting the route as '/'? We'll get back to this soon, but the fact that this endpoint is in users.js is important. ---- * The req variable represents the request data. This is where you'll get any data that you send to the endpoint (like if you want to find a user by an ID, then you would send the ID to the endpoint). ---- * The res variable represents the response data. This is where you'll send data back to the client-side. In this endpoint, you're sending the array of users back by using res.json(users). If an error occurred, then we will be sending back a 400 error with the error message. ---- * We will need to require the user model. Using the user model, we can make changes to it and "save" it to make changes on our actual database. ---- * We call the find() method on our User model, which returns all users in the users collection. There are many find methods that you can use, including findById, findOne, etc. * You can even add an optional parameter into find() to find users with a specific field value. * Here's a list of find methods: https://mongoosejs.com/docs/queries.html ---- * We are exporting the router, which allows our endpoints to be accessed through the given routes. --- Now, let's create an endpoint to add a user to our database above the export. ```javascript= /* @route /users/add @desc Add a given user @access Public */ router.route('/add').post((req, res) => { // get the data sent by the user const name = req.body.name; const email = req.body.email; const bio = req.body.bio; // to do this in one line: const {name, email, password} = req.body; // create a new user model instance const newUser = new User({ name, email, bio, }); // 'save' the new user to the database (creates a new document) newUser.save() .then(() => res.json('User added')) .catch(err => err.status(400).json(err)); }) ``` ---- Let's break this code down. ---- Since we're creating a new user, we need to client to send the new user's information: their name, email, and password. We use this information to create a new object. Note that the keys in this object match the keys specified in the User schema. This is important! ---- We then call ```save()``` on the object we just created. This creates a new user document in the users collection. We return the text "User added" once the user was successfully added to the database. ---- If you want to, you can redirect the user to a tweets page with the route /tweets ```javascript= // 'save' the new user to the database (creates a new document) newUser.save() .then(() => res.redirect('/tweets')) .catch(err => err.status(400).json(err)); ``` ---- On your own time, set up a tweets.html page and in index.js, figure out how to connect this HTML page to the route /tweets Hint: it's similar to how we set up our form --- Finally, let's create an endpoint to get a user with a specific ID. ```javascript= /* @route /users/:id @desc Get a specified user @access Public */ router.route('/:id').get((req, res) => { // get the id in the actual route const userID = req.params.id; User.findById(userID) // send the user back to client-side .then(user => res.json(user)) .catch(err => res.status(400).json(err)); }) ``` ---- Note that instead of using ```req.body``` we are using ```req.params```. This is because we specified the id of the user in the route (we used :id, meaning that the route will be /{WHATEVER THE USER'S ID IS} rather than /id). When we pass in data to the route itself, the data can be found in ```req.params```. --- ## Making our Routes Visible In order to use our endpoints, we need to add some code to our index.js file. ```javascript= const usersRouter = require('./routes/users'); app.use('/users', usersRouter); ``` This is where the '/users' part of the route comes from. Any endpoint in our user usersRouter will have the prefix /users. ---- This is why when you have the route '/' in users.js we can access that endpoint with the route '/users/'. Similarly, having the '/add' route in users.js means we can access that endpoint with the route '/users/add'. --- # Make Changes to Our Frontend ---- We now know that we will be accepting a name, email, and bio for our users to login. Let's reflect those changes in our index.html file: ---- ```htmlmixed= <!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <title> Index.html </title> </head> <body> <form method="POST" action="/users/add"> <label> Name: </label> <input type="name" name="name"></input> <br> <label> Email: </label> <input type="email" name="email"></input> <br> <label> Bio: </label> <input type="text" name="bio"></input> <br> <button type="submit"> Submit </button> </form> </body> </html> ``` --- Now you should get the data you entered in the /users/add URL ![](https://i.imgur.com/qTjKJSJ.png) 🏁 Checkpoint 2 🏁: You should see "User Added" on your screen! Also check to see that the user you've just created shows up in the MongoDB Atlas! ---- If you look in your Collections on Atlas, you'll see the newly created object in the users collection! Yippee! ![](https://i.imgur.com/6oNir87.png) --- ## Your Turn! **Your task:** We set up the routes we need for users. Now, it's your turn to set up our tweet API! Implement the endpoints for the following routes: 1. /tweets/ (Getting all tweets) 2. /tweets/add (Adding a tweet) 3. /tweets/:id (Get a specific tweet) --- ## Testing our API with Postwoman! Postwoman is an online tool, similar to the desktop tool Postman, that helps us test our API endpoints. We can check if our endpoints are actually doing what they're supposed to be doing! First, go to https://postwoman.io/ and look around. ---- What Postwoman will look like when you first get there ![](https://i.imgur.com/FdBbDK3.png =700x) ---- What it'll look like when you test /users/add ![](https://i.imgur.com/E3ECmGg.jpg =700x) ---- Let's go through the parts of Postwoman * The Request Section * We can choose our **request type** in 'Method' (GET, POST, etc.). * We can specify our **base URL** (in our case, will be along the lines of 'http://localhost:5000'). * In path, we specify our **route**, such as /users/add ---- * The Parameters Section * We specify all of the parameters we need to make the request. In the case of /users/add, we're creating a new user, so we need to send a name, email, and password to create that user in teh database. ---- * The Response Section * The Status will tell you if your request was successful (your endpoint did throw an error) or what type of error was thrown (400, 404, etc.). * Here's a list of request errors and their meanings: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status ---- If you return any data, such as during our GET requests, then that data will be displayed in the Response section. If you get a 200 successful code, that **DOES NOT** mean that your endpoint is working! Be sure to check the data in the Response section to make sure it's correct! --- ## Your Turn! **Your task:** We showed you how to test the add user endpoint on Postwoman. Now test each of your tweet endpoints and make sure they're working properly! ---- Here's a reminder of the routes you will need to test: 1. /tweets/ (Getting all tweets) 2. /tweets/add (Adding a tweet) 3. /tweets/:id (Get a specific tweet) --- ## You have successfully created your MongoDB database!