For want of an /admin backend

Most web apps will need a CMS or some non-technical scheme for managing site data using a web based UI.

Unfortunately, finding a simple, configurable scheme for plugging this functionality into a loopback app has proven to be elusive.

Ok. No problem. We'll make one.

digitopia-admin

digitopia-admin is an app module we are building for our loopback projects.

Currently available as a technology preview which mostly works for our use cases but may not cover all loopback configuration edge cases. We are actively seeking requests for desired features. Try it out - if you have an issue or can't figure it out open a ticket on github and we will see what we can do.

The module uses the schema as defined in loopback as the starting point for editing form behavior. The module pokes around the innards of loopback, figuring out the defaults, but you can override most things by editing the common/model.json file for customization at the model and property level.

All data relationships are exposed so that when you are viewing a row that has a 'hasMany' relationship to another table, the UI will list the rows and allow adding editing and deleting of related data.

TL;DR - see the screen shots at the bottom of the page to get the gist of this modules capabilities

Installing the module

add the module to package.config

"dependencies": {
  "digitopia-admin": "git+https://github.com/mediapolis/digitopia-admin.git"
}

run npm install

Authentication

You need to supply middleware for authenticating users.

More on some authentication schemes here.

By way of example the middleware for performing this function in our app reads the current user, stores it in the app context and then checks the roles for the user. If the user is not allowed they are redirected to a login page.

Mounting the /admin routes

Mount the app by adding a boot script which configures the authentication scheme, the name of the user model (for login/logout) and the models to expose in the /admin menu.

server/boot/01-admin.js

var admin = require('digitopia-admin');  
var getCurrentUser = require('../middleware/context-currentUser');  
var ensureAdminUser = require('../middleware/context-ensureAdminUser');

module.exports = function (server) {  
    var userAuth = [getCurrentUser(), ensureAdminUser()];
    admin.adminBoot(server, userAuth, 'MyUsers', ['MyUser', 'TypeTestLookup']);
};

Exposing the loopback REST api

The /admin pages use the loopback api to perform updates. Any models that are to be edited using /admin need to be 'public' but you probably don't want all of them to be accessible for the public web app.

Normally you would run the /admin instance on a dedicated hostname "backend.xxx.com" that has stricter firewall controls.

While model-config.json is static, Loopback has a scheme for configuring the models conditionally based on the environment using a javascript file.
model-config.localdev.js

More on environments here.

Using the js file configuration scheme we can set rules to expose the API based on the presence of the environment variable 'ADMIN'

'TypeTest': {  
  'dataSource': 'db',
  'public': process.env.ADMIN ? true : false
}

Now when NODE_ENV=localdev and ADMIN-true the api endpoints will be available.

ACL access

All the models will need access for the admin role which is defined in the model config.json

"acls": [
    {
        "principalType": "ROLE",
        "principalId": "$everyone",
        "permission": "DENY"
    },
    {
        "principalType": "ROLE",
        "principalId": "admin",
        "permission": "ALLOW",
        }
]

Configuring Form Behavior

At this point the admin is up and running using the defaults but typically you would want to configure some features such as client side field validation and which properties appear in each view (list/view/edit)

You can override the defaults at the model and the property level by editing the model definition json file.

common/models/type-test.json

use the "admin" object to configure lists of fields that will be exposed in various views

"admin": {
  "defaultProperty": "aString", // property to display in relation links and for search
  "listProperties": [], // list of properties on list view
    "viewProperties": [], // list of properties on instance view
  "editProperties": []  // list of properties on edit view
}

Define how the editing forms will behave at the property level.

"aString": {
    "type": "string",
    "required": true,
    "admin": {
        "validate": {
            "min-length": 5,
            "max-length": 30
        }
    }
},
"someText": {
    "type": "string",
    "admin": {
        "inputType": "textarea"
    }
},
"anEmail": {
    "type": "string",
    "admin": {
        "validate": {
            "email": true
        }
    }
},
"aUrl": {
    "type": "string",
    "admin": {
        "validate": {
            "url": true
        }
    }
},
"aNumber": {
    "type": "number",
    "admin": {
        "validate": {
            "integer": true,
            "min": 100,
            "max": 200
        }
    }
}

To set the input type

"admin": {
  "inputType":"textarea"
}

The "admin":"validate" object at the property level has the following configuration options:

  • required:true // property is required
  • integer:true // property must be an integer
  • float:true // property must be a float
  • url:true // property must be a valid url
  • email:true // property must be a valid email
  • cc-number:true // property must be a valid cc
  • cc-exp:true // property must be a valid cc expiry date
  • cc-cvc:true // property must be a valid cc cvc code
  • mask: 'regex' // property must match regex
  • min-length: integer // minimum length
  • max-length: integer // maximum length
  • min: float // minimum numeric value
  • max: float // maximum numeric value
  • match: '#password' // jquery selector of input to confirm eg: password & password confirm fields

Screenshots

List view w/search

View user table row (with uploads)

View test bench table with various data types

Edit test bench table


Photo: Ushuaia, Argentina (2005)
Document version 1.0