--- tags: ironhack, lecture, --- <style> .markdown-body img[src$=".png"] {background-color:transparent;} .alert-info.lecture, .alert-success.lecture, .alert-warning.lecture, .alert-danger.lecture { box-shadow:0 0 0 .5em rgba(64, 96, 85, 0.4); margin-top:20px;margin-bottom:20px; position:relative; ddisplay:none; } .alert-info.lecture:before, .alert-success.lecture:before, .alert-warning.lecture:before, .alert-danger.lecture:before { content:"đŸ‘šâ€đŸ«\A"; white-space:pre-line; display:block;margin-bottom:.5em; /*position:absolute; right:0; top:0; margin:3px;margin-right:7px;*/ } b { --color:yellow; font-weight:500; background:var(--color); box-shadow:0 0 0 .35em var(--color),0 0 0 .35em; } .skip { opacity:.4; } </style> ![Ironhack logo](https://i.imgur.com/1QgrNNw.png) # Mongoose&Express | Create - Update Documents ## Learning Goals After this lesson, you will be able to: - Create new documents on the database with data from a form filled by a user - Update documents in the database - Print prefilled form with info retrieved from the database - Get the updated document when doing an `update` method using Mongoose. ## Introduction :::info lecture Dans nos 2 cours prĂ©cĂ©dents, nous avons jusque maintenant listĂ© (`/books`) et affichĂ© (`/books/:id`) des documents de notre DB : C**R**UD. --- Nous allons maintenant voir comment en crĂ©er et mettre Ă  jour... ::: Continuing our `library-project` example, integrating and mixing the concepts we learned during the second module, in the following learning unit we will see how we can create and update documents using Mongoose, MongoDB, and Express. Create and update documents is an essential part of any web application, and for sure will be part of our second project. For more straightforward setting up, we will use the same project we had build till now, so go ahead an open the folder! :wink: ## Setting up everything :::info lecture Ajoutons un livre... ::: In our example, we will create new books and store them in our database. :::info **Discussion** - Which routes you need to create to display a form where a user can fill the info, and then get the info and create a new book in the database? - Which method will you use on each of those routes? ::: Yes, we will need two routes, the first one for rendering the form where the user can fill all the info about a new book, and another one for getting the data about the book and add it to the database. So let's do it! First, create a new route `books/add` on our `routes/index.js` file that should render a `book-add.hbs`. **Which method will we use?** :::info lecture Afin que l'utilisateur puisse ajouter lui-mĂȘme un livre, nous avons besoin de lui mettre Ă  disposition un formulaire : crĂ©er donc la **route d'affichage du formulaire** : ::: ```javascript // routes/index.js router.get('/books/add', (req, res, next) => { res.render("book-add"); }); ``` :::warning lecture ⚠ de dĂ©finir les routes `/books/add` AVANT `/books/:bookid` ! ::: Now we need to add a form on the `book-add.hbs` file, with all the fields we need to create a new book. :::info lecture Et notre **template du formulaire** : ::: ```htmlmixed {{! book-add.hbs }} <form action="/books/add" method="POST"> <label for="">Title:</label> <input type="text" name="title"> <label for="">Author:</label> <input type="text" name="author"> <label for="">Description:</label> <input type="text" name="description"> <label for="">Rate:</label> <input type="number" name="rating"> <button>ADD</button> </form> ``` :::info lecture Enfin, la **route de traitement du formulaire** : ::: And the second one? We need another to get the data and add to our Mongo database. We can use the same route but changing the method. ```javascript // routes/index.js router.post('/books/add', (req, res, next) => { // TODO }); ``` ## Create Documents All setup! We are ready for creating new documents and store them in our database. :::info lecture Notre formulaire en place ainsi que notre route de traitement, voyons comment enregistrer cela en base... ::: ### Retrieve the data from a POST Request & Creating the Document How can we retrieve the data in the `/books/add` route? All the data will be included on the `body` property of the `request` object, so we can handle it to create a new book in the database, but let's do it step by step: :::info You need to include all the following inside our `books/add` route with the `POST` method! ::: 1. First, we need to retrieve the data and store in new variables. ES6 destructuring will help with that: :::info lecture RĂ©cupĂ©rons les valeurs saisies grĂące Ă  `req.body` : ::: ```javascript const { title, author, description, rating } = req.body; ``` Remember this is possible only if the variables have the same name on the `req.body` that means they need to have the same name on the `name` attribute of each `input`. 2. We can create a new `Book` using the model we import. It's important to notice we are using some ES6 features that make our code looks much cleaner! :::info lecture Avec ces valeurs, crĂ©ons une **nouvelle instance** de livre : ::: ```javascript const newBook = new Book({ title, author, description, rating}); ``` 3. We can store it in the database, using the `save()` method. Since this is an asynchronous process, we need to process it as a `Promise`. If everything goes ok, we will receive the `book` we just save. Otherwise, we will have an error. We need to control both options! :::info lecture **Persistons** cette instance en base : ::: ```javascript newBook.save() .then((book) => { }) .catch((error) => { }) ; ``` 4. Finally, we can redirect our user to the `/books` view, where we list all the book we have in the database. The new book should be already there. We should have the following code on our route: :::info lecture Enfin : - redirigeons si tout c'est bien passĂ© - ou affichons l'erreur Ă  l'utilisateur grĂące Ă  `next` ::: ```javascript // routes/index.js router.post('/books/add', (req, res, next) => { const { title, author, description, rating } = req.body; const newBook = new Book({ title, author, description, rating}) newBook.save() .then((book) => { res.redirect('/books'); // redirect to listing }) .catch((err) => { next(err); // display error }) ; }); ``` Awesome! Now we should see the new book we just created! Creating documents is a super standard action on web applications, and you need to be sure you understand step by step all the process you will need to do it, so if anything is not clear enough, now it is the perfect time to ask! :wink: :::info lecture Testons tout cela et ajoutons livre ! ::: ## Edit Documents Any user can add books to our `library-project`, but what about modifying one? Let's add this feature to our project! ### Edit Form :::info lecture Afin que l'utilisateur puisse editer lui-mĂȘme un livre, nous devons lĂ  encore lui mettre Ă  disposition un formulaire d'Ă©dition : `views/book-edit.hbs` ::: First, we need an edit form, where the user will be able to modify the info of each book. Let's create a `book-edit.hbs` view and add the following code: ```htmlmixed {{! views/book-edit.hbs }} <form action="/books/edit" method="post"> <label for="">Title:</label> <input type="text" name="title"> <label for="">Author:</label> <input type="text" name="author"> <label for="">Description:</label> <input type="text" name="description"> <label for="">Rate:</label> <input type="number" name="rating"> <button>EDIT</button> </form> ``` :::warning lecture Ici, tous **les champs seront vides** ! Nous voudrions au contraire qu'il soient **prĂ©-remplis** avec leur valeur courante... ::: **Is this enough to edit a book?** It is, but with the awfull user experience. Imagine you as a user clicking on an edit button and all the info about the element you are trying to edit is not there, so you have to fill all the fields again? That sucks right? So we need to set all the inputs value with prefilled values from the existing book. #### How can we do that? We need to create a route where we will render this view, but before we should pass the info about the book the user is trying to edit. First, let's add the edit link to each of our books on the `/books` route. **How can we pass the info about the book we are trying to edit?** - *Route Params*. We can set a route like the following: `book/edit/:id` where we will receive the `id` as a `req.params`. - *Query String*. Another option is to set the route: `book/edit` and pass the data as a query string using the `?`. We choose the second option, but any of them is valid! :wink: Add the following code to our `books.hbs` file: :::info lecture Ajoutons tout d'abord le lien vers la page d'Ă©dition dans le listing des livres : L7 ::: ```htmlmixed= {{! views/books.hbs }} <h1>BOOKS</h1> {{#each books}} <p> <a href="book/{{this._id}}">{{this.title}}</a> <a href="/books/edit?book_id={{this._id}}" class="edit-button">EDIT</a> </p> {{/each}} ``` :::warning lecture Ici l'URL sera : `/books/edit?book_id=` (query string) mais nous aurions tout aussi bien pu dĂ©cider d'utiliser les route-params ::: :::info Notice how we set the `href` attribute for editing the documents, this way the `book_id` property will be dynamic. You can also add the following **css** to the `style.css` file, so we differentiate the **edit** button. ```css .edit-button { margin-left:20px; color: #fff; text-decoration: none; padding: 2px 4px; background-color: grey; border-radius: 6px; } ``` ::: ### Get the data We know where the user will go when clicking on the **Edit** button. We need to create the route to get that user `request` and render the view. :::info lecture Notre lien vers la page d'Ă©dition en place, occupons-nous de sa route : ::: ```javascript router.get('/books/edit', (req, res, next) => { res.render("book-edit"); }); ``` But before rendering the **edit form**, we should retrieve the data of the book from our database and pass that data to our view. How can we get the data of the book we are clicking? Remember we are passing the `id` through the **query string**. :::info lecture Cependant, **afin de pouvoir prĂ©-remplir les champs** du formulaire par les valeurs actuelles du document en question, nous devons passer ses info Ă  la vue, et par consĂ©quent au prĂ©alable les retrouver depuis la base : ::: ```javascript router.get('/books/edit', (req, res, next) => { // // 👇 Retrieve book datas before rendering the edit form // Book.findOne({_id: req.query.book_id}) // 👈 we consume the query-string ?book_id= .then((book) => { res.render("book-edit", { book: book // 👈 once our book retrieved, we pass it to the view }); }) .catch((error) => { next(err); }) ; }); ``` :::info lecture Nous utilisons ici `req.query.book_id` du lien afin d'identifier quel livre fetcher depuis la base. ::: We need the `req.query` object to get the `id` of the book and then query the database asking for all the info about it. :::info Notice we are using the `findOne` method, this way the database returns an object with the book we are looking for. If we use the `find` method, Mongoose retrieves an array of objects. ::: #### Prefilled fields We are now rendering our edit form, but they are empty. We need to fill those `input` with the info we retrieve from the database. Add the `value` attribute, with the corresponding info about each field. :::info lecture Les infos du livre en question ayant maintenant Ă©tĂ© passĂ©es Ă  la vue, **nous pouvons prĂ©-remplir chacun des champs** avec la valeur actuelle : grĂące Ă  l'attribut `value=""` ::: ```htmlmixed {{! views/book-edit.hbs }} <form action="/books/edit?book_id={{book._id}}" method="post"> <label for="">Title:</label> <input type="text" name="title" value="{{book.title}}"> <label for="">Author:</label> <input type="text" name="author" value="{{book.author}}"> <label for="">Description:</label> <input type="text" name="description" value="{{book.description}}"> <label for="">Rate:</label> <input type="number" name="rating" value="{{book.rating}}"> <button type="submit">EDIT</button> </form> ``` Perfect! Now every input is prefilled with the info of the book we are trying to edit. We also modify the **action** attribute of the `form`. When the user clicks on the **EDIT** button, the web will make a `POST` request to that **URL**. Let's move forward! :::info lecture Testons l'affichage du formulaire d'Ă©dition... ::: ### Update the Document :::info lecture Ne nous **manque plus que la route de traitement** du formulaire, ie : la route appelĂ©e quand le formulaire sera soumis... ::: Create the route with a `POST` method so we can get the info of the book. Inside the route, we should get all the info from the `req.body`, and the book `id` from the `req.query` and then use the `update` method to edit the book on our database. :::info Remember the syntax for the `update` method. The first parameter is the query to find the element we want to edit. On the second parameter, we specify the fields we want to update. Since we are getting all the fields from the `req.body` you can set all of them. ```javascript Model.update({ query }, { $set : { key: value, key: value }}) .then() .catch() ``` ::: On the `POST` method of the route you should have the following: :::info lecture CrĂ©ons cette nouvelle route, correspondant Ă  l'`action` du formulaire : ::: ```javascript= // routes/index.js router.post('/books/edit', (req, res, next) => { const { title, author, description, rating } = req.body; Book.update({_id: req.query.book_id}, { $set: {title, author, description, rating }}) .then((book) => { res.redirect('/books'); }) .catch((error) => { next(error); }) ; }); ``` :::info lecture - L4 : rĂ©cupĂ©ration des valeurs saisies dans le form - L6 : appel de `Book.update` avec les valeurs rĂ©cupĂ©rĂ©es ::: #### Get the updated Document After updating the document, **Mongoose** returns us the old document from the database. And sometimes this can confuse us a bit because in some cases we want to pass to the view the updated document to print it. Fortunately, we can add a *third parameter* to the `update` method so Mongoose will return us the updated document. The third parameter should be an object we specify we want the *new* document: `{ new: true }`. The full syntax, looks like this: :::warning lecture le `book` retournĂ© dans le `then` de l'update, sera l'ancienne version. ::: ```javascript Model.update({ query }, { $set : { key: value, key: value }}, { new: true }) .then() .catch() ``` ## Summary We just learned how we could create and edit documents on our website using Express & Mongoose. This two actions will be essential when doing our second project. Every step we saw in this learning, we already learned before, but now we are mixing everything. You must be sure you understand every step, this way when you face by yourself this challenge you are ready to do it and customize to your web requirements. ## Extra Resources - [Mongoose Create and Update](http://mongoosejs.com/docs/documents.html) - Official Docs