Skip to content

Instantly share code, notes, and snippets.

@anharathoi
Last active June 18, 2019 06:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anharathoi/9c924e07d44e41df83e2e1e047c9eddc to your computer and use it in GitHub Desktop.
Save anharathoi/9c924e07d44e41df83e2e1e047c9eddc to your computer and use it in GitHub Desktop.

Mongoose

Mongoose is an ODM (Object Document Mapping) library for MongoDB and NodeJS.

Whereas ORM (Object Relational Mapping) is to map an object with a relational world, ODM is to map objects with a Document Database like MongoDB. (medium)

If we look back at our Rails projects, we used ActiveRecord (ORM) to interact with our PostgreSQL database (relational database). Similarly, Mongoose (ODM) is what we will use in our MERN stack application to interact with MongoDB (document database.

Click here for more on differences between ODM and ORM.


Why we're using mongoose?

Express apps can use any database supported by Node (Express itself doesn't define any specific additional behavior/requirements for database management). There are many popular options, including PostgreSQL, MySQL, Redis, SQLite, and MongoDB.

There are two approaches for interacting with a database:

  1. Using the databases' native query language (e.g. SQL)
  2. Using an Object Data Model ("ODM") / Object Relational Model ("ORM"). An ODM/ORM represents the website's data as JavaScript objects, which are then mapped to the underlying database.

As we're using MongoDB as our database we'll be using an ODM and in this case that is mongoose.

Now Let's look at the logical flow of control in our Web Application:

alt text

  • The browser loads up a web page.
  • The web page has a script from which we are making a fetch request to the localhost over port 3333 (Or whatever port your server is running on) asking for the /pokemon endpoint.
  • The node and express layer services that request and calls the appropriate call back.
  • Within the callback we will use mongoose which will inturn connect to the Mongo DB and get the documents from the Mongo DB.
  • The node and express layer processes this and sends it to the client.

Setup:

  1. Create an npm project directory with npm init

  2. Install dependencies:

    • express
    • mongoose
    • nodemon

    Add start script tag in package.json file.

  3. Require dependencies, define port and configure body parser

    const express = require('express')
    const app = express()
    const mongoose = require('mongoose')
    const PORT = 3000
    
    // body-parser configuration
    app.use(express.json())
    
    // This is a built-in middleware function in Express.
    // It parses incoming requests with JSON payloads and is based on body-parser.
  4. Connecting your application to a port

    • The network port identifies the application or service running on the computer.

    • The call to app.listen causes the server to start waiting for connections on port 3000

    • This is why you have to connect to localhost:3000 to speak to this server, rather than just localhost, which would use the default port 80

      // connecting to the port
      app.listen(PORT, () => {
        console.log(`Listening on port ${PORT}`)
      })
  5. Connecting to the database:

    • by default, the mongo server runs on port 27017. When you run mongo in your teminal, it'll say where the mongo server is running.

    • The URI for mongoDB server is mongodb://localhost:27017/nameOfDatabase. mongodb://localhost/nameOfDatabase works as well.

    • Let's save the URI in a variable and connect to the database

      // Define the development database
      const mongoURI = 'mongodb://localhost/pokemondb'
      
      // connecting to mongodb from your application
      mongoose.connect(mongoURI, { useNewUrlParser: true }, (err) => {
         if(err) return console.log(`${err}`)
         console.log("connected to mongodb")
       })
  6. Mongoose Schema

    • Everything in Mongoose starts with a Schema.
    • Each schema maps to a MongoDB collection and defines the shape of the documents within that collection, e.g.
    • Create a folder called models and inside create a file called Pokemon.js and copy the following bit of code in that file.
    const mongoose = require('mongoose')
    
    const pokeSchema = new mongoose.Schema({
        id: Number,
        name: String,
        height: Number,
        moves: Array,
        image: String
      },
      { collection: 'pokemon' }
    )
    // you normally don't need to pass the name of the collection;
    // the reason here is because otherwise it'll create a collection called pokemons
    // we do not have a pokemons collection. So we specify the name of our collection here.
    
    module.exports = mongoose.model('pokemon', pokeSchema)
  7. Require the model in your server file:

    const Pokemon = require('./models/pokemon')
  8. Create endpoints:

    GET all pokemon

    app.get('/pokemon', (req, res) => {
      Pokemon.find({})
      .then( allPokemon => {
        console.log(allPokemon)
        return res.json(allPokemon)
      })
      .catch( err => res.json(err))
    })

    Get one pokemon

    By ID

    app.get('/pokemon/:id', (req, res) => {
      const { id } = req.params
    
      Pokemon.findOne({id: id})
      .then( poke => {
        console.log(poke)
        return res.json(poke)
      })
      .catch( err => res.json(err))
    })

    Post one pokemon

    app.post('/pokemon', (req,res) => {
      const { id, name, height, moves, image } = req.body
    
      Pokemon.create({ id, name, height, moves, image })
      .then( newPoke => {
        res.json(newPoke)
      })
      .catch( err => res.json(err))
    })

    Update one pokemon: update name

    app.put('/pokemon/:id', (req, res) => {
      const { id } = req.params
      const { newName } = req.body
      Pokemon.findOne({id})
      .then( poke => {
        poke.name = newName
        poke.save()
        .then( doc => res.send(`${doc.name} has been updated`))
        .catch( error => console.log(error) )
      })
      .catch( err => res.json(err))
      })
    })

    Delete one pokemon

    app.delete('/pokemon/:id', (req, res) => {
      const { id } = req.params
      Pokemon.findOneAndDelete({ id })
      .then( doc => {
        if(!doc) return res.send(`No pokemon found at id ${id}`)
          res.send(`${doc.name} deleted from database`)
        })
      .catch( err => res.json(err))
    })

Mongoose schema with validation

  • the syntax for adding validation:
    const mongoose = require('mongoose')
    
    const pokeSchema = new mongoose.Schema({
      id: {
        type: Number,
        required: true
      },
      name: 
      {
        type: String,
        required: false
      },
      height: {
        type: Number,
        required: false
      },
      moves: {
        type: Array,
        required: false
      },
      image: {
        type: String,
        required: false
      },
    },
      { collection: 'pokemon' }
    )
    
    
    module.exports = mongoose.model('pokemon', pokeSchema)

Common bugs/ issues

Model:

  • In your model(schema) file, when you write the following bit of code, the 1st argument after .model is the name of the collection. Mongoose automatically converts that to be plural, i.e. pokemon will become pokemons.
    module.exports = mongoose.model('pokemon', pokeSchema)
  • If you want your collection to be something specific( not automatically pluralised), you'll need to pass the name of the collection as an option in your schema like this:
    const pokeSchema = new mongoose.Schema({
      _id: Number,
      name: String
    }, { collection: 'pokemon' })
  • A field that has not been defined in your schema, will not be saved in your database. E.g., in the above example, the only field that will be saved in the database is name and _id even if you pass moves, image and height in the body.

Manual _id

  • Mongo automatically creates id objects. The _id field is the unique naming convention that MongoDB uses across all of its content.
  • If you have manually assigned value to the _id field, and we're specifically talking about the _id field, you must specify that in your schema
  • it would not be a problem if you have another id field called id.
  • assigning IDs manually is NOT RECOMMENDED, but this is how you fix it if you do:
const pokeSchema = new mongoose.Schema({
  _id: Number,
  name: String
}

Challenges:

A. Pokemon challenge

  • Drop the pokemon collection from you mongo CLI. Redefine your schema with validation and create an endpoint that inserts all the pokemon from below at once.(hint: insertMany)
 const pokemon = [
   {
     id: 1,
     name: "bulbasaur",
     height: 7,
     moves: ["razor-wind", "swords-dance"],
     image: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png"
   },
   {
     id: 2,
     name: "ivysaur",
     height: 10,
     moves: ["cut", "bind", "swords-dance"],
     image: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/2.png"
   },
   {
     id: 3,
     name: "venusaur",
     height: 20,
     moves: ["cut", "swords-dance"],
     image: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/3.png"
   },
   {
     id: 4,
     name: "charmander",
     height: 6,
     moves: ["mega-punch", "fire-punch"],
     image: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/4.png"
   },
   {
     id: 5,
     name: "charmeleon",
     height: 11,
     moves: ["mega-punch", "fire-punch"],
     image: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/5.png"
   }
 ]

B. To do challenge

  • make a todo app in express using mongoose by implementing CRUD operations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment