Flexible controllers for express.js apps

Usage no npm install needed!

<script type="module">
  import exctrl from 'https://cdn.skypack.dev/exctrl';



Flexible controllers for express.js apps.

Easily load your controllers, no matter where they are, and mount their actions/routes to your app.


npm install exctrl


File app.js:

var express = require('express'),
    app = express(),
    exctrl = require('exctrl');

// configuring express app...

exctrl.load(app, {pattern: __dirname + '/controllers/*.js'});

File /controllers/user.js:

exports.create = function (req, res) {
  // ...
  res.send('user created');
exports.read = function (req, res) {
  // ...
  res.send('user read');
exports.update = function (req, res) {
  // ...
  res.send('user updated');
exports.del = function (req, res) {
  // ...
  res.send('user deleted');
exports.index = function (req, res) {
  // ...
  res.send('all users');

Results in:

POST /user
GET /user/:id
PUT /user/:id
DELETE /user/:id
GET /user

Generated URLs/routes

An URL generated by exctrl has the format: [/<prefix>]/<controller name>[/<method name>][/<...>]

Detailed description of URL parts


An optional prefix to add to all mounted routes, and is provided via the options hash, see the load function below.

<controller name>

The main mount point for each controller and can be provided in three different ways:

  1. extracted from the controller file name, when using the load function (see options hash for options on how to extract the name)
  2. as the name property of the controller if it's a string
  3. as the first argument to the mount function, see below

<method name>

Magic method names / REST inspired

Some method names are considered "magic", in that way that it may imply both HTTP method and URL:

  • index, list or search
    • http method: GET
    • url: mounted directly at the controller mount point (i.e. the <method name> is left empty), e.g. "/user"
  • create
    • http method: POST
    • url: directly at controller mount point
  • read
    • http method: GET
    • params: :id
    • url: "/<controller name>/:id"
  • update
    • http method: PUT
    • params: :id
    • url: "/<controller name>/:id"
  • del
    • http method: DELETE
    • params: :id
    • url: "/<controller name>/:id"
Other method names

If a method name starts with a known HTTP method, i.e. get, head, post, put and delete, it is mounted as that method in the provided express.js app, otherwise it's considered a get method.

The HTTP method part from the method name is then cut off, and the remainings are dasherized (e.g. "getBestFriends" -> "best-friends") and added to the url.


  • controller: "user", action: "getBestFriends" --> route: GET /user/best-friends
  • controller: "products", action: "get_top_sellers" --> route: GET /products/top-sellers
  • controller: "products", action: "postLike" --> route: POST /products/like
  • controller: "user", action: "get/:id/friends" --> route: GET /user/:id/friends

What about middlewares or params you say? That's what the <...> is for..

A controller action mustn't be just a function, e.g:

var basicController = {
  name: 'basic',
  getStuff: function (req, res) {
    // ROUTE: GET /basic/stuff

For more advanced usage you can declare a controller action as an array (like AngularJS dependency injection syntax), e.g:

var middleware = function (req, res, next) { /* ... */ };
var advController = {
  name: 'adv',
  getStuff: [':type', middleware, ':anotherparam', 'chunk', function (req, res) {
    // ROUTE: GET /adv/stuff/:type/:anotherparam/chunk
    if (req.params.type === 'other') {
      this.otherStuff(req, res); // Yes, `this` here is the actual controller
    } else {
      /* do something else */
  otherStuff: function (req, res) {
    // ROUTE: GET /adv/other-stuff
    res.send(/* other stuff */);




  • Function app - Your express.js app
  • Object options - A hash of options
    • String pattern
      • A glob pattern for finding controllers, e.g. "controller/*.js" or "**/*.ctrl.js"
    • RegExp nameRegExp
      • A regexp used for extracting controller name/mount point from the filename, e.g. with pattern "**/*.ctrl.js" a nameRegExp as /([^\/\\]+).ctrl.js$/ can be used to extract "user" from /var/tmp/dir/user.ctrl.js. The first match group will be used as extractor, and full path filename will be used when matching.
    • String prefix
      • If provided it will be prefixed to all mounted controller routes, e.g. with prefix api, controller name user and action/method get the mounted route will be: GET /api/user


load() loads your controllers, and does so syncronously to make sure the routes are loaded at the time you want, to not interfere with other express.js configurations.



  • Function app - Your express.js app


bind() binds your app to exctrl, so that you can skip first param to the load function above.





  • String name - The controller name, used as mount point
  • Object controller - A controller with methods as actions


mount() is mainly used internally by exctrl for mounting a controller at a mount point, but is available publicly for convenience, and will stay in the API.

Note if the controller object has a name property with type String the first parameter to this function can be omitted, and the controller.name will be used as mount point instead.


var name = 'user',
    controller = {
      index: function (req, res) {
        res.send(/* all users */);

      .mount(name, controller);

// or

controller.name = name;



exctrl uses Semantic Versioning as versioning model.


Please feel free to contribute and send pull requests!


MIT, see ./LICENSE file.