Skip to content

Code Routes

Jordan Lees edited this page Mar 3, 2020 · 6 revisions

Routes

Building a Route

Every route exports an instance of Express's Router class. For this example, let's build a basic route for user login, which will be called user.js.

const router = require('express').Router();
module.exports = router;

Tying Routes to URLs

Inside of app.js, we first load a route with require, and then tie it to its corresponding URL using app.use.

//Load ./routes/user.js
const user = require('./routes/user');
//Tie the route to the URL https://scoutradioz.com/user
app.use('/user', user);

Creating a method inside our route

For our router to respond to GET/POST requests, we use router.get and router.post, respectively. The first parameter is the URL, and the second parameter is a callback, which takes our req and res variables as parameters. See Function notes for info on req and res.

The URL parameter gets added on top of the "base" URL of our route, which we set inside app.js. If we want our login page to be at /user/login, we will make a GET handler for '/login':

const router = require('express').Router();

router.get('/login', (req, res) => {
  
});

module.exports = router;

Note that (req, res) => {} is arrow notation for function (req, res) {}. Arrow notation helps keep our code from being too verbose.

Rendering a page

Rendering a page is easy! See Code Views for notes on building views. Let's assume ./views/user/login.pug has already been made, and has a form that submits a POST request to '/user/login'.

The method to render a view is res.render. The first parameter is the relative path to our view (with respect to the ./views directory), and the second parameter is an object which contains variables that can be passed to our view. Our views all take a title, which sets the title of the page.

const router = require('express').Router();

router.get('/login', (req, res) => {
  res.render('./user/login', {
    title: "Log In"
  });
});

module.exports = router;

Accessing the database

We wrote a wrapper module called scoutradioz-utilities, which handles database connections for us. See Scoutradioz-Utilities for more details. To use it, we need to change our handler function to an async function. To access GET query parameters (/path/to/url?foo=bar), we use req.query (e.g. req.query.foo). To access POST body parameters, we use req.body (e.g. req.body.foo).

Please note that this is not a complete programming tutorial. There will be not be any error handling in these code snippets, in order to reduce the size of the sample code.

const router = require('express').Router();
const utilities = require('@firstteam102/scoutradioz-utilities');
const bcrypt = require('bcryptjs');

router.get('/login', async (req, res) => {
  //Access the database and find all users whose org_key matches the org_key inside our GET request
  // (e.g. /users/login?org_key=frc102)
  const users = await utilities.find('users', {org_key: req.query.org_key});

  res.render('./user/login', {
    title: "Log In",
    users: users,
  });
});

router.post('/login', async (req, res) => {
  //Find the user that matches the selected user's ID
  const user = await utilities.findOne('users', {_id: req.body.userId});

  //Compare password to hash stored in database
  var comparison = await bcrypt.compare(req.body.password, user.password);

  //If password check passed, then log in user.
  if (comparison == true) {
    req.logIn(user);
  } 
  //If password check failed, then throw an error.
  else {
    throw Error("Password check failed! Oh no!!");
  }
});

module.exports = router;

Error Handling

But unfortunately, here, you will find that we run into a problem. Express handles errors in normal functions just fine. But since async functions always return a Promise, you'll find that when we throw an error, you get a message like this:

(node:5916) UnhandledPromiseRejectionWarning: ReferenceError: foo is not defined
    at router.get (C:\ScoringApp-Serverless\primary\routes\index.js:162:14)
    at Layer.handle [as handle_request] (C:\ScoringApp-Serverless\primary\node_modules\express\lib\router\layer.js:95:5)
    at ......
(node:5916) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:5916) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. 

Express cannot handle promise rejections by itself. So in order for a promise rejection to be properly handled, and for our route to properly throw an Error that Express can handle, we need to put our method into a wrapper. Thankfully, the package Express Async Handler works for this. In order to keep our code from being too verbose, let's call it wrap when we require it.

const router = require('express').Router();
const utilities = require('@firstteam102/scoutradioz-utilities');
const bcrypt = require('bcryptjs');
const wrap = require('express-async-handler');

router.get('/login', wrap(async (req, res) => {
  //Access the database and find all users whose org_key matches the org_key inside our GET request
  // (e.g. /users/login?org_key=frc102)
  const users = await utilities.find('users', {org_key: req.query.org_key});

  res.render('./user/login', {
    title: "Log In",
    users: users,
  });
}));

router.post('/login', wrap(async (req, res) => {
  //Find the user that matches the selected user's ID
  const user = await utilities.findOne('users', {_id: req.body.userId});

  //Compare password to hash stored in database
  var comparison = await bcrypt.compare(req.body.password, user.password);

  //If password check passed, then log in user.
  if (comparison == true) {
    req.logIn(user);
  } 
  //If password check failed, then throw an error.
  else {
    throw Error("Password check failed! Oh no!!");
  }
}));

module.exports = router;