Node/Express Controller inheritance

Okafor Emmanuel
CloudBoost
Published in
5 min readNov 15, 2017

--

This article shows a possible solution using object inheritance and polymorphic approach to avoid repetitive pattern in a typical express application routes controllers.

Sample Use case

The repetitive pattern of controllers functions of an express application with multiple CRUD restful endpoint might sometimes be redundant, functions for an existing restful endpoint maybe duplicated and trivial changes made to achieve the same CRUD endpoints for a new model.

Optional Requirements

  1. Prior knowledge of node/express using ES6 and babel.
  2. Express starter for ES6. the one used for this article can be found here.
  3. Mongoose and Validationjs modules to store and validate respectively (Optional).

Let get started

  1. Clone the ES6 starter kit from here and rename the folder to your preference, then install dependencies with npm install
  2. Create a controller, model and routes folder within the src folder of the project folder. check below for a sample image.

3. Create a parent controller in this case I will call it the AppController where we can define base function that can be overridden only when implementation is different from the functions in the parent controller.

import ....
/**
* The App controller class where other controller inherits or
* overrides pre defined and existing properties
*/
class AppController {
/**
*
@param {Model} model The default model object
* for the controller. Will be required to create
* an instance of the controller
*/
constructor(model) {
this._model = model;
this.create = this.create.bind(this);
}

/**
*
@param {Object} req The request object
*
@param {Object} res The response object
*
@param {function} next The callback to the next program handler
*
@return {Object} res The response object
*/
create(req, res, next) {
let obj = req.body;
const validator = this._model.validateCreate(obj);
if (validator.passes()) {
let object = new this._model(obj);
object.save()
.then((savedObject) => {
const meta = getSuccessMeta();
return res.status(OK).json(formatResponse(meta, savedObject));
}, (err) => {
return next(err);
});
} else {
const appError = new AppError('input errors',
BAD_REQUEST, validator.errors.all());
return next(appError);
}
}
}

export default AppController;

Note: The constructor function will instantiate the model that will be used within the controller, this allows children controllers to override this property with the desired model object.

4. Lets test the implementation with a sample todo model, we are going to create a todo model within the models directory.

import mongoose from 'mongoose';
import Validator from 'validatorjs';

/**
* Todo Schema
*/
const TodoSchema = new mongoose.Schema({
name: {
type: String,
},
description: {
type: String,
required: true,
},
}, {
timestamps: true,
});

/**
*
@param {Object} obj The object to perform validation on
*
@return {Validator} The validator object with the specified rules.
*/
TodoSchema.statics.validateCreate = (obj) => {
let rules = {
name: 'required',
detail: 'required',
};
return new Validator(obj, rules);
};

/**
*
@typedef TodoSchema
*/
export default mongoose.model('Todo', TodoSchema);

In the above sample code the TodoSchema’s static methods will be used to validate the object to be created before saving to the data-store. The model function to be called will depend on how the controller instance was created.

5. Lets create the TodoController which will extend the AppController but this time it will not override an parent function/methods.

import AppController from '../controllers/app';
/**
* The App controller class where other controller inherits or
* overrides pre defined and existing properties
*/
class TodoController extends AppController {
/**
*
@param {Model} model The default model object
* for the controller. Will be required to create
* an instance of the controller
*/
constructor(model) {
super(model);
}
}

export default TodoController;

6. To set up an endpoint for creating a todo we need to create an instance TodoController and pass the todo model object as the constructor argument and hence the desired model has been specified by this child controller

import {Router} from 'express';
import TodoModel from '../models/todo';
import TodoController from '../controllers/todo';
const router = Router();
const todoCtrl = new TodoController(TodoModel);

router.route('/todos')
.post(todoCtrl.create);

export default router;

You will need to add the route in our express app in the app.js

import todo from '../src/routes/todo'

const app = express();
......app.use(todo);

Lets try it now

the controller function used to create a todo is defined in the app controller, so for custom implementation where the parent function does not have the desired implementation a function override can be done on just the specific function to meet the desired result. Lets override the create function in the TodoController to see how it plays out.

import AppController from '../controllers/app';
/**
* The App controller class where other controller inherits or
* overrides pre defined and existing properties
*/
class TodoController extends AppController {
/**
*
@param {Model} model The default model object
* for the controller. Will be required to create
* an instance of the controller
*/
constructor(model) {
super(model);
}
/**
*
@param {Object} req The request object
*
@param {Object} res The response object
*
@param {function} next The callback to the next program handler
*
@return {Object} res The response object
*/
create(req, res, next) {
return res.status(200).json('i have been overridden');
}
}

export default TodoController;

The function called as we can see was defined in the TodoController, so this makes it possible to use the power of class inheritance in es6 to achieve a lot with minimum code and reduce redundant functions. Although this can be done with the javascript object literals using the prototyping approach to achieve the same controller inheritance.

Thanks for reading, I hoped this helped in one way or the other. The source code can be found here.

--

--