Skip to content

Running Development

Glad JS edited this page Oct 14, 2017 · 1 revision

Let us begin with a brief overview of how Glad.JS works by taking a high level look at the request lifecycle.

  • A request comes in to /foo
  • The router locates the /foo route.
  • The router checks the route entry to see if there is a specific body parser it should use for this request. If not, it uses the default body parser defined in your config.js file. Either way, the body gets parsed.
  • The router then checks to see if there is a policy specified on the route entry.
  • If there is a policy, it routes the request to the policy and if it is accepted, the policy hands the request off to the correct controller/action combo.
  • If there is no policy, The router routes the request off to the correct controller/action combo.

There's quite a lot happening behind the scenes to facilitate this flow, but that's basically the gist of it. You have routes, models, controllers, and (if you'd like) views. Routes point to controllers, controllers pull in models and complete the request with various types of data/views.

Now lets take a look at getting set up. After installation, it's a good idea to take a look at the config file and make any neccesary adjustments. The config file contains all of the settings you'll need to change when either developing or deploying your application. It's safe to assume that you might want to read environment variables or dynamically change different configuration options based on any number of factors. You'll most likely want to do that here.

An overview of the config file

{

  port : 4242, // <-- The port that the http server will listen to

  host : '0.0.0.0', // <-- The host that the http server will listen on, (Can be /tmp/sock)

  logHTTP: true, // <-- Log HTTP request info to stdout

  defaultBodyParser : { // <-- The default body parser
    limit : '0.3mb',
    type : 'json'
  },

  exposeModelsGlobally : true, // <-- Makes your models available in the global namespace

  redis : {
    host: "localhost", // <-- The Redis Host
    port: 6379 // <-- The Redis Port
  },

  cookie : {
    name : 'glad.sid',     // <-- The name of your cookie
    secret: 'change-this', // <-- The cookie secret
    maxAge : 3600000 * 24, // <-- Cookie expiration date
  },

  session : {
    storage: 'redis' // <-- Where sessions are stored. See the section on Sessions for more options
  },

  defaultViewEngine : 'pug', // <-- The default view engine to use

  orm: "mongoose" // <-- This tells Glad CLI to use the mongoose blueprint when generating APIs

}

Next we'll have a look at policies. A policy is an asynchronous function that receives the request & response objects as well as accept and reject methods. If accept is invoked, the request gets routed to the controller action. If reject is invoked, the request is terminated and the onFailure method of the policies object is called. The reject method takes an optional argument. This argument can be anything, but it can be useful to extend the onFailure method. Maybe you want to pass functions into the onFailure method and have a default responder combined with custom responders. See fig.p-1 for an example of this.

Policies

  module.exports = {

    /*
     The onFailure method is a default that you can set for denying requests.
     This method receives the req & res objects, as well as an optional rejection reason that you pass to the reject method.
    */
    onFailure (req, res, rejectMessage) {
      res.status(403).json({error: rejectMessage});
    },

    /*
    This method is intentionally overly verbose
    to make it clear what is happening.
    */
    authenticated (req, res, accept, reject) {
      if (req.session) {
        if (req.session.authenticated) { // <--- What value on the session says they are logged in?
          accept(); // Accept the request. All is good
        } else {
          reject("You must be logged in to do that"); // Reject the request. This will end up calling the above on failure method.
        }
      } else {
        reject();
      }
    },

    // <--- Add additional policies if needed. Examples below....
    isDeveloper (req, res, accept, reject) {
      if (req.session && req.session.developer) {
        accept();
      } else {
        reject("You are not allowed to access this API");
      }
    },

    // Note: This policy requires you to follow the convention /api/resource/:id
    resourceOwnerOrAdmin (req, res, accept, reject) {

      if (!req.params) {
        return reject("Incorrect Parameters: Missing Parameters");
      } else if (!req.params.id) {
        return reject("Incorrect Parameters: Missing ID");
      }

      if (req.session && req.session.authenticated) {
        if (req.session.user.admin) {
          accept();
        } else if (req.session.user.id === req.params.id) {
          accept();
        } else {
          reject("You don't have access to this content");
        }
      } else {
        reject("You must be logged in to do that.");
      }
    }
  };

fig.p-1 An example of a flexible approach:

module.exports = {
  onFailure (req, res, custom = false) {
    if (custom && typeof custom === 'function') {
      custom();
    } else {
      res.status(403).end()
    }
  },

  authenticated (req, res, accept, reject) {
    if (req.session.authenticated) {
      accept();
    } else {
      reject(() => res.status(403).json({error: 'You must be logged in to do that'}));
    }
  },

  never (req, res, accept, reject) {
    reject();
  }
}

Moving right along to routing. A route file is an object and the keys consist of HTTP verbs. (Any valid HTTP verb can be used) Each verb is an array route entries. This is how the router knows what controller action to route the request to, what policy to check, and if there is a specific body parser to use or the default. Each controller in your app will have it's own route file. You can think of it as resource based routing. Given a resource called user you should have the following files.

  • controllers/user.js
  • models/user.js
  • routes/user.js

If you plan to render views, you should also have views/user/some-view.pug

A route entry

  • path : matching url
  • action : the controller method to call when this route is matched
  • policy : the policy method to call in order to determine if the action is allowed. * see policies.js
  • bodyParser: A body parser configuration (See Below)

A resource

  • model
  • route
  • controller
  • view folder

Clone this wiki locally