Some notes on CRUD application – Node.js, Express.js & MongoDB

CRUD - READ

In Express, we handle a GET request with the get method:

app.get(endpoint, callback)

endpoint is the requested endpoint. It’s the value that comes after your domain name. Here are some examples:

  • When you visit localhost:3000, you’re actually visiting localhost:3000/. In this case, browsers requested for /.
  • You’re reading this article on https://zellwk.com/blog/crud-express-mongodb/. The domain name is zellwk.com. The requested endpoint is anything that comes after zellwk.com (which is /blog/crud-express-mongodb).

callback tells the server what to do when the requested endpoint matches the endpoint stated. It takes two arguments: A request object and a response object.

Set up HTML site

To do this, we use the sendFile method that’s provided by the res object

app.get('/', (req, res) => {
  res.sendFile(__dirname + '/index.html')
  // Note: __dirname is directory current directory you're in. Try logging it and see what you get!
})

Nodemon

Nodemon restarts the server automatically when you save a file that’s used by the server.js. We can install Nodemon with the following command:

$ npm install nodemon --save-dev

Nodemod behaves like Node. So you can run nodemon server.js and you’d expect to see the same thing. Unfortunately, this only works if you’ve installed nodemon globally with the -g flag (and we didn’t do this).
We have other ways to run Nodemon. For example, you can execute Nodemon directly from the node_modules folder. This is super unweildy, but it works:

./node_modules/.bin/nodemon server.js

We can make things simpler by adding script key in the package.json file. This lets us run nodemon server.js without the ./node_modules... preamble.

{
  // ...
  "scripts": {
    "dev": "nodemon server.js"
  }
  // ...
}

Now, you can run npm run dev to trigger nodemon server.js.

CRUD - CREATE

To send a POST request through a

, you need to add the

element to your index.html file.

<form action="/quotes" method="POST">
  <input type="text" placeholder="name" name="name">
  <input type="text" placeholder="quote" name="quote">
  <button type="submit">Submit</button>
</form>

The method tells browsers what kind of request to send. In this case, we use POST because we’re sending a POST request.
The action attribute tells the browser where to send the POST request. In this case, we’re send the POST request to /quotes.
We can handle this POST request with a post method in server.js. The path path should be the value you placed in the action attribute.

app.post('/quotes', (req, res) => {
  console.log('Hellooooooooooooooooo!')
})

Get the input values with Express

Turns out, Express doesn’t handle reading data from the

element on it’s own. We have to add another package called body-parser to gain this functionality.

npm install body-parser --save

Body-parser is a middleware. They help to tidy up the request object before we use them. Express lets us use middleware with the use method.

const express = require('express')
const bodyParser= require('body-parser')
const app = express()

// Make sure you place body-parser before your CRUD handlers!
app.use(bodyParser.urlencoded({ extended: true }))

// All your handlers here...
app.get('/', (req, res) => {/*...*/})
app.post('/quotes', (req, res) => {/*...*/})

The urlencoded method within body-parser tells body-parser to extract data from the < form > element and add them to the body property in the request object.

You should be able to see values from the

element inside req.body now. Try doing a console.log and see what it is!

app.post('/quotes', (req, res) => {
  console.log(req.body)
})

Set up MongoDB

MongoDB is a database. We can store information into this database. Then, we can retrieve this information and display to people who view our app.

First, we need to install MongoDB via npm.

npm install mongodb --save

Once installed, we can connect to MongoDB through the MongoClient's connect method as shown in the code below:

const MongoClient = require('mongodb').MongoClient
MongoClient.connect('mongodb-connection-string', (err, client) => {
  // ... do something here
})

The next part is to get the correct link to our database. Most people store their databases on cloud services like MongoDB Atlas. We’re going to do same as well. (It’s free).
The link for MongoDB Atlas.
How to set up a local MongoDB.
Create an account on MongoDB Atlas.

You need to create a Database in MongoDB Atlas. There are several steps to do this.
First, you need to create a new Project. You can do this by going under “Context” in the top left hand menu. Click the Dropdown. Then, select New Project.
Then, you will need to add members. Again, you’re already added so go ahead and click “Create Project” to move on.
Just pick the free building options for this project, and building your first cluster.

Connecting to MongoDB Atlas

Click on the Connect button.

You need to whitelist your IP address before you can connect to your cluster. This is a security feature built into MongoDB Atlas. Go ahead and click “Add your Current IP Address”.

Next, you need to create a MongoDB user. This username and password is different from the one you used to login to MongoDB Atlas. This username and password is used ONLY for the database.

Next, click on choose your connection method. Select “Connect to your application” and copy the connection string.

Put this connection string inside the MongoClient.connect.

MongoClient.connect(connectionString, (err, client) => {
  // ... do something here
}))

We know we’ve connected to the database if there are no errors. Let’s create console.log statement that says “Connected to database”. This will help us know we’ve connected to the database when we restart the server.

MongoClient.connect(connectionString, (err, client) => {
  if (err) return console.error(err)
  console.log('Connected to Database')
})

You can remove the deprecation warning by adding the option into MongoClient.connect

MongoClient.connect(connectionString, {
  useUnifiedTopology: true
}, (err, client) => {
  if (err) return console.error(err)
  console.log('Connected to Database')
})

MongoDB supports promises. If you want to use promises instead of callbacks, you can write MongoClient.connect like this. It behaves exactly like the code above.

MongoClient.connect(connectionString, { useUnifiedTopology: true })
  .then(client => {
    console.log('Connected to Database')
  })
  .catch(error => console.error(error))
  ```

 #### Changing the Database
 We need to change the database from test to something else. You can name it anything you want. 
 ```
 MongoClient.connect(connectionString, { useUnifiedTopology: true })
  .then(client => {
    console.log('Connected to Database')
    const db = client.db('star-wars-quotes')
  })
  ```
  #### MongoDB and Server
  We need the db variable from the connection to to access MongoDB. This means we need to put our express request handlers into the MongoClient’s then call.
 ```
 MongoClient.connect(/* ... */)
  .then(client => {
    // ...
    const db = client.db('star-wars-quotes')
    app.use(/* ... */)
    app.get(/* ... */)
    app.post(/* ... */)
    app.listen(/* ... */)
  })
  .catch(console.error)
  ```

#### CRUD - CREATE (continued)
We need to create a collection before we can store items into a database. 
We use db.collection to specify the collection.

MongoClient.connect(/ ... /)
.then(client => {
// ...
const db = client.db('star-wars-quotes')
const quotesCollection = db.collection('quotes')

// ...

})

We can use the insertOne method to add items into a MongoDB collection.

app.post('/quotes', (req, res) => {
quotesCollection.insertOne(req.body)
.then(result => {
console.log(result)
})
.catch(error => console.error(error))
})


If you go back to the Browser, you’ll see it’s still trying to load something.
This happens because the browser expects something back from the server.
In this case, we don’t need to send the browser information. Let’s ask the browser to redirect back to / instead. We do this with res.redirect.

app.post('/quotes', (req, res) => {
quotesCollection.insertOne(req.body)
.then(result => {
res.redirect('/')
})
.catch(error => console.error(error))
})


#### Showing quotes to users (READ operation)
We need to do two things to show quotes from MongoDB Atlas to our users.

1. Get quotes from MongoDB Atlas.
2. Rendering the quotes in HTML with a template engine

Let’s go one step at a time.

#### Getting quotes from MongoDB
We can get quotes we stored in MongoDB with the find method. This method from mLab by using the find method that’s available in the collection method.

app.get('/', (req, res) => {
const cursor = db.collection('quotes').find()
console.log(cursor)
// ...
})

The find method returns a cursor which won’t make sense if you tried logging it.

But this cursor object contains all quotes from our database! It has a bunch of method that lets us get our data. For example, we can use toArray to convert the data into an array.

app.get('/', (req, res) => {
db.collection('quotes').find().toArray()
.then(results => {
console.log(results)
})
.catch(error => console.error(error))
// ...
})

#### Rendering the HTML

We cannot serve up the index.html file and expect quotes to magically appear because there’s no way to add dynamic content to a HTML file.

What we can do, instead, is to use a template engine to generate the HTML. Popular template engines include Pug, Embedded JavaScript, and Nunjucks.

For this tutorial, we will use [Embedded JavaScript (EJS)](http://www.embeddedjs.com/) as our template engine because it’s the easiest to start with. You’ll find it familiar from the get-go since you’ll be writing HTML and JavaScript.

#### Using EJS
First, we need to install EJS.

npm install ejs --save

Next, we need to set view engine to ejs. This tells Express we’re using EJS as the template engine. You can need to place it before any app.use, app.get or app.post methods.

app.set('view engine', 'ejs')

// Middlewares and other routes here...

We can now generate HTML that contains the quotes. This process is called rendering the HTML.
We will use the render method built into Express’s response. It needs to follow the following syntax:

res.render(view, locals)

* view is the name of the file we’re rendering. This file must be placed inside a views folder.
* localsis the data passed into the file.

Let’s create a view folder. We’ll make an index.ejs file inside the views folder.

We’ll copy/paste everything from index.html into index.ejs.

<!DOCTYPE html>




Star Wars Quote App

May Node and Express be with you.





Next, we’ll use res.render to render this index.ejs file.

app.get('/', (req, res) => {
db.collection('quotes').find().toArray()
.then(/ ... /)
.catch(/ ... /)
res.render('index.ejs', {})
})

Let’s put the quotes into index.ejs. To do this, we need to pass the quotes into the render method.

app.get('/', (req, res) => {
db.collection('quotes').find().toArray()
.then(results => {
res.render('index.ejs', { quotes: results })
})
.catch(/ ... /)
})

In index.ejs, we can use place variables between <%= and %> tags. Let’s try putting quotes into the HTML:


...

...

<%= quotes %>

We see lots of [object Object] because each quote inside results is a JavaScript object. ejs cannot convert that object into HTML automatically.

We need to loop through the quotes. We can do this with a for loop. In EJS, we write a for loop like how we write a JavaScript for loop. The only difference is we need to put the for loop statements between <% and %>.

Quotes


    <% for(var i = 0; i < quotes.length; i++) {%>


  • <%= quotes[i].name %>:

    <%= quotes[i].quote %>
  • <% } %>


#### CRUD - UPDATE
We use the UPDATE operation when we want to change something. It can be triggered with a PUT request. Like POST, PUT can be triggered either through JavaScript or through a < form > element.

we need to add a button into the index.ejs file:

Darth Vadar invades!

Replace first Yoda's quote with a quote written by Darth Vadar

We will also create an external JavaScript file to execute a PUT request. According to Express conventions, this JavaScript is kept in a folder called public

$ mkdir public
$ touch public/main.js


Then, we have to tell Express to make this public folder accessible to the public by using a built-in middleware called express.static

app.use(express.static('public'))


We now can add the main.js file to the index.ejs file:




We will send a PUT request when the button gets clicked. This means we need to listen to a click event.

Next, we’re going to send the PUT request when the button is clicked:

// main.js
const update = document.querySelector('#update-button')

update.addEventListener('click', _ => {
// Send PUT Request here
})


#### Sending a PUT Request
The easiest way to trigger a PUT request in modern browsers is to use the Fetch API.
Fetch has the following syntax:

fetch(endpoint, options)

In this case, let’s say we want to send the request to /quotes. We’ll set endpoint to /quotes.

update.addEventListener('click', _ => {
fetch('/quotes', {/ ... /})
})


We need to send a PUT request this time. We can do this by setting Fetch’s method to put.

update.addEventListener('click', _ => {
fetch('/quotes', {
method: 'put'
})
})


We need to tell the server we’re sending JSON data by setting the Content-Type headers to application/json.

update.addEventListener('click', _ => {
fetch('/quotes', {
method: 'put',
headers: { 'Content-Type': 'application/json' },
})
})

Next, we need to convert the data we send into JSON. We can do this with JSON.stringify. This data is passed via the body property.

update.addEventListener('click', _ => {
fetch('/quotes', {
method: 'put',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Darth Vadar',
quote: 'I find your lack of faith disturbing.'
})
})
})


#### Accepting the PUT request
Our server doesn’t accept JSON data yet. We can teach it to read JSON by adding the body-parser's json middleware.

app.use(bodyParser.json())

Next, we can handle the PUT request with a put method. You should be able to see the values we send from the fetch request.

app.put('/quotes', (req, res) => {
console.log(req.body)
})


#### Changing Yoda’s quote
MongoDB Collections come with a method called findOneAndUpdate. This method lets us find and change one item in the database. It has the following syntax:

quotesCollection.findOneAndUpdate(
query,
update,
options
)
.then(result => {/ ... /})
.catch(error => console.error(error))


query lets us filter the collection with key-value pairs. If we want to filter quotes to those written by Yoda, we can set { name: 'Yoda' } as the query.

quotesCollection.findOneAndUpdate(
{ name: 'Yoda' },
update,
options
)
.then(result => {/ ... /})
.catch(error => console.error(error))

 update, tells MongoDB what to change. It uses MongoDB’s update operators like $set, $inc and $push.

We will use the $set operator since we’re changing Yoda’s quotes into Darth Vadar’s quotes:

quotesCollection.findOneAndUpdate(
{ name: 'Yoda' },
{
$set: {
name: req.body.name,
quote: req.body.quote
}
},
options
)
.then(result => {/ ... /})
.catch(error => console.error(error))
```
options tells MongoDB to define additional options for this update request.

In this case, it’s possible that no Yoda quotes exist in the database. We can force MongoDB to create a new Darth Vadar quote if no Yoda quotes exist. We do this by setting upsert to true. upsert means: Insert a document if no documents can be updated.

quotesCollection.findOneAndUpdate(
  { name: 'Yoda' },
  {
    $set: {
      name: req.body.name,
      quote: req.body.quote
    }
  },
  {
    upsert: true
  }
)
  .then(result => {/* ... */})
  .catch(error => console.error(error))

Finally, let’s log the result into the command line.

  app.put('/quotes', (req, res) => {
  quotesCollection.findOneAndUpdate(/* ... */)
    .then(result => {
      console.log(result)
     })
    .catch(error => console.error(error))
}

Finally, we need to respond to the JavaScript that sent the PUT request. In this case, we’ll simply send the success message.

app.put('/quotes', (req, res) => {
  quotesCollection.findOneAndUpdate(/* ... */)
    .then(result => {
       res.json('Success')
     })
    .catch(error => console.error(error))
}

Next, we can handle the response from the server via a then object. (We do this because fetch returns a promise). However, Fetch is slightly different from most promises. You need to use another then object to get the response from the server.

fetch({ /* request */ })
  .then(res => {
    if (res.ok) return res.json()
  })
  .then(response => {
    console.log(response)
  })

CRUD - DELETE

The DELETE operation can be triggered through a DELETE request. It’s similar to the UPDATE request so this should be simple if you understand what we’ve done above.
First, we need to add a delete button to index.ejs.

<div>
  <h2>Remove Darth Vadar!</h2>
  <p>
    Delete one Darth Vadar's quote. Does nothing if there are no more Darth
    Vadar's quote
  </p>
  <button id="delete-button">Delete Darth Vadar's quote</button>
</div>

Then, we’ll trigger a DELETE request through Fetch when a user clicks the delete button.

const deleteButton = document.querySelector('#delete-button')

deleteButton.addEventListener('click', _ => {
  fetch('/quotes', {
    method: 'delete',
  })
})
deleteButton.addEventListener('click', _ => {
  fetch(/* ... */, {
    method: 'delete',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      name: 'Darth Vadar'
    })
  })
    .then(res => {
      if (res.ok) return res.json()
    })
    .then(data => {
      window.location.reload()
    })
})

We can then handle the event on our server side with the delete method:

app.delete('/quotes', (req, res) => {
  // Handle delete event here
})

Deleting a document from MongoDB

MongoDB Collections has a method called deleteOne. It lets us remove a document from the database. It takes in two parameters: query and options.

quotesCollection.remove(
  query,
  options
)
  .then(result => {/* ... */})
  .catch(error => console.error(error))
  ```

app.delete('/quotes', (req, res) => {
quotesCollection.deleteOne(
{ name: req.body.name }
)
.then(result => {
res.json(Deleted Darth Vadar's quote)
})
.catch(error => console.error(error))
})

#### What if there are no more Darth Vadar quotes?
If there are no more Darth Vadar quotes, result.deletedCount will be 0. We can send a message that says tells the browser that there are no more Darth Vadar quotes to delete.

app.delete('/quotes', (req, res) => {
quotesCollection.deleteOne(/ ... /)
.then(result => {
if (result.deletedCount === 0) {
return res.json('No quote to delete')
}
res.json(Deleted Darth Vadar's quote)
})
.catch(error => console.error(error))
})



[ORIGINAL ARTICLE FROM ZELL'S BLOG, A BIG THANKS FOR HELP!] (https://zellwk.com/blog/crud-express-mongodb/)

Leave a Reply