CRUD, MVC, REST, INDUCES and JSX: Remember where we are going

arthur_node_jsx_diagram_photoshopped

mvc-meme

mongoose

Our Applications have 7 Restful Routes that can be described with the acronym INDUCES

  1. INDEX
  2. NEW
  3. DELETE
  4. UPDATE
  5. CREATE
  6. EDIT
  7. SHOW

    The Purpose Behind these routes is to create an organized system for Handling CRUD functionality on A Given Resource

These Are Your 7 RESTful REST standing for Representational State Transfer

i.e.

URL HTTP Verb Action Notes Mongoose Method View
/fruits/ GET index INDEX when a user types localhost:3000/fruits in browser this route shows a list or index of all fruits Fruit.find Index.jsx
/fruits/new GET new NEW when a user types localhost:3000/fruits/new in browser this route shows the user a form to create a NEW fruit none New.jsx
/fruits/:id DELETE destroy DELETE initiates a delete request through a form submission with action = http://localhost:3000/fruits/:idOfFruit and allows the application the ability to delete a fruit Fruit.findByIdAndRemove or Fruit.findByIdAndDelete none
/fruits/:id PUT update UPDATE initiates a put request through a form submission with action = http://localhost:3000/fruits/:idOfFruit and allows the application the ability to Update data about a fruit Fruit.findByIdAndUpdate or Fruit.findOneAndUpdate none
/fruits POST create CREATE initiates a post request through a form submission with action = http://localhost:3000/fruits/ and allows the application the ability to Create a fruit Fruit.create none
/fruits/:id/edit GET edit EDIT when a user types localhost:3000/fruit/:idOfFruit/edit in browser shows the user a form to edit a fruit Fruit.findOne or Fruit.findById Edit.jsx
/fruits/:id GET show SHOW when a user types localhost:3000/fruit/:idOfFruit shows the user an Individual fruit in the browser Fruit.findOne or Fruit.findById Show.jsx

What is Middleware?

  • In the Intro to Express lesson, we identified the three fundamental capabilities provided by web application frameworks:

    1. The ability to define routes
    2. The ability to process HTTP requests using middleware
    3. The ability to use a view engine to render dynamic templates
  • We've already defined routes and rendered dynamic templates.
  • In this lesson we complete the trifecta by processing requests using middleware.
  • A middleware is simply a function with the following signature:
function(req, res, next) {}

First thing we will do is make that annoying lazy middleware we have been using all this time finally do something for us

app.use((req, res, next) => {
  res.locals.data = {}
  next()
})

Moving Over To A Routes and Controllers Folder

Create a Controllers Folder routeController.js and add the following code

const express = require('express');
const router = express.Router();
const Fruit = require('../models/fruit.js');

// add routes
// Index
router.get('/', (req, res) => {
    // Use Fruits model to get all Fruits
    Fruit.find({}, (error, allFruits) => {
        res.render('fruits/Index', {
            fruits: allFruits
        })
    });

});

// New
router.get('/new', (req, res) => {
    res.render('fruits/New');
});

// Delete
router.delete('/:id', (req, res) => {
    // Delete document from collection
    Fruit.findByIdAndRemove(req.params.id, (err, fruit) => {
        res.redirect('/fruits');
    });
});

// Update
router.put('/:id', (req, res) => {
    req.body.readyToEat = req.body.readyToEat === "on" ? true : false;

    // Update the fruit document using our model
    Fruit.findByIdAndUpdate(req.params.id, req.body, { new: true }, (err, updatedModel) => {
        res.redirect('/fruits');
    });
});

// Create
router.post('/', (req, res) => {
    if (req.body.readyToEat === "on") {
        req.body.readyToEat = true;
    } else {
        req.body.readyToEat = false;
    }
    // Use Model to create Fruit Document
    Fruit.create(req.body, (error, createdFruit) => {
        // Once created - respond to client
        res.redirect('/fruits');
    });
});

// Edit
router.get('/:id/edit', (req, res) => {
    // Find our document from the collection - using mongoose model
    Fruit.findById(req.params.id, (err, foundFruit) => {
        // render the edit view and pass it the found fruit
        res.render('fruits/Edit', {
            fruit: foundFruit
        })
    });
});

// Show
router.get('/:id', (req, res) => {
    // Find the specific document
    Fruit.findById(req.params.id, (error, foundFruit) => {
        // render the Show route and pass it the foundFruit
        res.render('fruits/Show', {
            fruit: foundFruit
        });
    });
});


// export router
module.exports = router;

Then we can use this router inside of our server.js as Middleware

 app.use('/fruits', require('./controllers/routeController.js'))

We can also further decouple the functionality of our application by making a viewController.js inside of controllers and a dataController.js

const RESOURCE_PATH = '/fruits'
const viewController = {
  index(req, res, next){
    res.render('fruits/Index', res.locals.data)
  },
  show(req, res, next){
    res.render('fruits/Show', res.locals.data)
  },
  edit(req, res, next){
    res.render('fruits/Edit', res.locals.data)
  },
  newView(req, res, next){
    res.render('fruits/New')
  },
  redirectHome(req, res, next){
    res.redirect(RESOURCE_PATH)
  },
  redirectShow(req, res, next){
    res.redirect(RESOURCE_PATH + `/${req.params.id}`)
  }
}

module.exports = viewController

data Controller

const Fruit = require('../models/fruit.js');

const dataController = {
  index(req, res, next){
    Fruit.find({}, (error, allFruits) => {
      if(err){
        res.status(404).send({
          msg: err.message
        })
      }else {
        res.locals.data.fruits = allFruits
        next()
      }
    });
  },
  create(req, res, next){
    req.body.readyToEat = req.body.readyToEat === "on" ? true : false;
    // Use Model to create Fruit Document
    Fruit.create(req.body, (error, createdFruit) => {
        // Once created - respond to client
        if(err){
          res.status(404).send({
            msg: err.message
          })
        }else {
          res.locals.data.fruit = createdFruit
          next()
        }
    });
  },
  show(req, res, next){
    Fruit.findById(req.params.id, (err, foundFruit)=>{
      if(err){
        res.status(404).send({
          msg: err.message
        })
      } else {
        res.locals.data.fruit = foundFruit
        next()
      }
    })
  },
  update(req, res, next){
    req.body.readyToEat = req.body.readyToEat === "on" ? true : false;
    Fruit.findByIdAndUpdate(req.params.id, req.body, { new: true }, (err, updatedFruit) => {
      if(err){
        res.status(404).send({
          msg: err.message
        })
      } else {
        res.locals.data.fruit = updatedFruit
        next()
      }
    });
  },
  destroy(req, res, next){
    Fruit.findByIdAndRemove(req.params.id, (err, fruit) => {
      if(err){
        res.status(404).send({
          msg: err.message
        })
      } else {
        res.locals.data.fruit = fruit
        next()
      }
    });
  }
}

module.exports = dataController
const express = require('express');
const router = express.Router();
const viewController = require('./viewController.js')
const dataController = require('./dataController.js')
// add routes
// Index
router.get('/', dataController.index, viewController.index);
// New
router.get('/new', viewController.newView );
// Delete
router.delete('/:id', dataController.destroy, viewController.redirectHome);
// Update
router.put('/:id', dataController.update, viewController.redirectShow);
// Create
router.post('/', dataController.create, viewController.redirectHome);
// Edit
router.get('/:id/edit', dataController.show, viewController.edit);
// Show
router.get('/:id', dataController.show, viewController.show);
// export router
module.exports = router;

Now lets also clean up our data and put our database code into the models folder, into a file called db.js

/*******
Database Setup
******/
const mongoose = require('mongoose');
mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true,
  useFindAndModify: false
})

module.exports = mongoose.connection

New and Improved server.js

// Import Modules and set up vars
require('dotenv').config()
const express = require('express');
const methodOverride = require('method-override');
const app = express();
const PORT = process.env.PORT || 3000;

//connect to database
const db = require('./models/db')
db.once('open', () => {
  console.log('connected to mongo')
})

//Initialize View Engine
app.set('view engine', 'jsx');
app.engine('jsx', require('jsx-view-engine').createEngine())

// Mount Express Middleware
app.use((req, res, next) => {
  res.locals.data = {}
  next()
}) // Creates res.locals.data
app.use(express.urlencoded({ extended: true })) // Creates req.body
app.use(methodOverride('_method')); // Allows us to override methods
app.use(express.static('public')); // Allows us to have Static Files
app.use('/fruits', require('./controllers/routeController.js')); // Mounts our RESTFUL/INDUCES ROUTES at /fruits


// Listen on PORT
app.listen(PORT, () => {
  console.log('We in the building', PORT)
})

Future More Advanced Apps

  1. You would have a controller for each resource/model at minimum
  2. You would have controllers for special functionality like authentication controllers
  3. The seperation of concerns makes it easier for multiple teams to work in parallel

Review Questions - What is Node.js?

❓ Is Node.js a programming language?

❓ Is Express a programming language?

❓ What is the primary reason why Node/Express applications are so performant?

❓ Is...const el = document.getElementById('my-list');a valid JavaScript statement in a Node app?

❓ What is a Model?

❓ What is A View Engine?

❓ What is A DataController?

❓ What is A ViewController?

❓ What is A RouteController?

❓ What is Express Middleware?