Roosevelt

web framework — coding apps

Making model files

Place a file named dataModel.js in mvc/models.

Here's a simple example dataModel.js data model:

// dataModel.js
module.exports = () => {
  return { some: 'data' }
}

In more complex apps, you might query a database to get your data instead.

Making view files

Views by default are Teddy templates. See the Teddy documentation for information about how to write Teddy templates.

You can also configure Roosevelt to use any templating system supported by Express for your views.

Making controller files

Controller files are places to write Express routes. A route is the term Express uses for URL endpoints, such as https://yoursite/blog or https://yoursite/about.

Controllers bind models and views together.

To make a new controller, make a new file in the controllers directory, then follow one of the examples below.

Example GET route

// someController.js
module.exports = (router, app) => {
  // router is an Express router
  // and app is the Express app created by Roosevelt

  // standard Express route
  router.route('/about').get((req, res) => {
    // load a data model
    const model = require('models/dataModel')()

    // render a template and pass it the model
    res.render('about', model)
  })
}

Example POST route

In Roosevelt, Cross-Site Request Forgery protection is enabled by default. That means in order to make requests to any POST route, you will need to pass a CSRF token from the server to the browser, and then return that token when making a request from the browser to the server.

To do that, include the CSRF token in your HTML form as a hidden input _csrf:

// someController.js
module.exports = (router, app) => {
  router.route('/form').get((req, res) => {
    const model = require('models/dataModel')()
    model.token = req.csrfToken() // add CSRF token to the model
    res.render('about', model)
  })
}
<!-- form that include the token in a request body -->
<form action="/some-protected-endpoint" method="post">
  <input type="hidden" name="_csrf" value="{token}">
</form>

You can also add the token as a request header when performing fetch requests by setting the x-csrf-token header:

// request that includes the token in headers
const response = await fetch('/some-protected-endpoint', {
  method: 'POST',
  headers: {
    'X-CSRF-TOKEN': token
  }
})

You can also exempt certain routes from CSRF protection or disable the feature entirely by configuration. See configuration section for details.

Reusable controllers

Sometimes it is also useful to separate controller logic from your routing. This can be done by creating a reusable controller module. Reusable controller modules differ from standard controller modules in that they are meant to be called from within other controllers and do not define routes.

To create a reusable controller, put a file in your controllers directory that accepts app, req, and res arguments with logic that is meant to execute from within a predefined route.

An example of when this might be needed would be having a reusable controller for "404 Not Found" pages:

// notFound.js — reusable controller
module.exports = (app, req, res) => {
  const model = { content: 'Cannot find this page' }
  res.status(404)
  res.render('404', model)
}

You can then call the reusable controller in any other controller when needed:

// someController.js
module.exports = (router, app) => {
  router.route('/whatever').get((req, res) => {
    // test some logic that could fail
    // thus triggering the need for the 404 controller
    if (something) {
      // logic didn't fail
      // so render the page normally
      let model = require('models/dataModel')
      res.render('whatever', model)
    }
    else {
      // logic failed
      // so throw the 404 by executing your reusable controller
      require('controllers/notFound')(app, req, res)
    }
  })
}

Any controller file that has no arguments or more than two arguments will be considered a reusable controller.

Making static pages

You can also generate static files from templates and models as well.

Templates for static pages go in your statics folder (staticsRoot) under the HTML source path (html.sourcePath), which is statics/pages by default.

Data models for the templates will be merged from different possible locations to source them from in the following order of precedence:

  1. * model in htmlModels.
  2. File-level override in htmlModels.
  3. Model file.

Setting static page models

To declare a global model in htmlModels, use the * character:

// build.js
(async () => {
  await require('roosevelt')({
    html: {
      models: {
        '*': {
          hello: 'world!'
        }
      }
    }
  }).init()
})()

To declare a model for a specific file in htmlModels:

// build.js
(async () => {
  await require('roosevelt')({
    html: {
      models: {
        'index.html': {
          hello: 'world!'
        }
      }
    }
  }).init()
})()

You can also declare a model for a specific page by placing a JS file with the same name alongside the template.

For example if an index.js file exists next to index.html, the JS file will be used to set the model so long as it exports either an object or a function that returns an object.