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.
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:
- Using the databases' native query language (e.g. SQL)
- 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.
- 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.
-
Create an npm project directory with
npm init
-
Install dependencies:
- express
- mongoose
- nodemon
Add start script tag in package.json file.
-
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.
-
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}`) })
-
-
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") })
-
-
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)
-
Require the model in your server file:
const Pokemon = require('./models/pokemon')
-
Create endpoints:
app.get('/pokemon', (req, res) => { Pokemon.find({}) .then( allPokemon => { console.log(allPokemon) return res.json(allPokemon) }) .catch( err => res.json(err)) })
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)) })
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)) })
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)) }) })
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)) })
- 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)
- 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.
- 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
}
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"
}
]
- Implement basic CRUD operations for your pokemon.
- Implement a get endpoint to find a pokemon by name.
- Implment an endpoint that finds a pokemon and adds more moves to it
- Refactor the put endpoint with findOneAndUpdate (https://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate)
B. To do challenge
- make a todo app in express using mongoose by implementing CRUD operations