Skip to content

A lightweight javascript / typescript MVC style library which helps you to create object oriented, modular and testable code

License

Notifications You must be signed in to change notification settings

coolgk/node-mvc

Repository files navigation

@coolgk/mvc

npm install @coolgk/mvc

A simple, lightweight javascript / typescript nodejs mvc framework that helps you to create object oriented, modular and testable code.

Build Status Coverage Status dependencies Status Known Vulnerabilities

Documentation

This framework routes HTTP requests to class methods.

e.g. "GET /shop/product/description/1" calls the "description" method in "/modules/shop/controllers/product.js" (see example code below)

In this example request, "shop" is a module (folder), "product" is a controller (file), "description" is an action (method) and "1" is a parameter. The format of the request is /module/[controller]/[action]/[param]

The framework looks for files from the folder structure below.

./index.js
./modules
    /shop
        /controllers
            product.js
            anothercontroller.js
        /models
            model.js
    /anothermodule
        /controllers
        ...

Controller

The controller module must export a "default" property which is a class that extends the base "Controller" class from @coolgk/mvc/controller. Folder (module), file (controller) and method (action) names must be in lowercase without special characters except for hyphens and numbers /[a-z0-9\-]/ or in camelCase if a request contains hyphens e.g. action-one is converted to actionOne

product.js controller example

const { Controller } = require('@coolgk/mvc/controller');

class Product extends Controller {
    /**
     * @param {object} dependencies - this param is destructured in this example
     * @param {object} dependencies.params - url param values based on the patterns configured in getRoutes()
     * @param {object} dependencies.globals - the object passed into the router's constructor
     * @param {*} dependencies.services - services returned by getServices()
     */
    description ({ params, services, globals }) {
        // globals contains global dependencies passed into the router class (see example below)
        globals.response.json(
            services.model.find(params.id)
        );
    }

    /**
     * setup valid routes to methods
     */
    getRoutes () {
        return {
            GET: {
                description: ':id' // allow GET request to access the description() method
            }
        }
    }

    /**
     * setup local dependencies
     */
    getServices () {
        return {
            model: new (require('../models/model.js'))()
        };
    }

    /**
     * setup permission callbacks for accessing methods
     */
    getPermissions () {
        return {
            // * the is default permission for all methods in this class
            // can be used for checking app level permissions e.g. login sessions etc.
            '*': () => false, // false = deny all by default
            // true or Promise<true>: skip permission check for the description() method
            'description': () => true
        };
    }
}

exports.default = Product;

Entry Point (Router)

index.js / server.js however you name it...

An example of using express with this framework

const express = require('express');
const { Router } = require('@coolgk/mvc/router');

const app = express();

app.use(async (request, response, next) => {

    // initialise router
    const router = new Router({
        rootDir: __dirname, // required
        url: request.originalUrl, // required
        method: request.method, // required
        response // you can pass anything into router, these variables are injected into controllers methods in globals
    });

    // router.route() returns the return value of the controller method if the return value is not falsy
    // otherwise it returns an object formatted by the "response" object (see the documention for @coolgk/mvc/response at the bottom)
    // e.g. { code: 200, text: 'SUCCESS' }, { code: 200, json: {...} }, { code: 200, file: { name: ..., path: ... } } etc.
    const result = (await router.route());

    // for handling 404 / 403 returned from the router
    result && result.code && response.status(result.code).send(result.text);

});

app.listen(3000);

Unit Test

Dependencies are injected into methods, you can easily mock them in your tests.

'use strict';

const sinon = require('sinon');
const expect = require('chai').expect;

describe('Test Example', function () {
    // this test is for the example code in https://github.com/coolgk/node-mvc/tree/master/src/examples
    // i.e. not the product.js controller above
    const ControllerClass = require(`../javascript/modules/example/controllers/extended`).default;

    let controller;
    let params;
    let response;
    let services;
    let globals;

    beforeEach(() => {
        // initialise controller for each test case
        controller = new ControllerClass();
        // setup dependencies
        params = { id: 123 };
        // create test spy on global dependency: response
        response = {
            json: sinon.spy()
        };
        // create test stub on local dependency: services
        services = {
            model: {
                getUser: sinon.stub().returns({ name: 'abc' })
            }
        };
        // create test stub on global dependency: globals
        globals = {
            session: {
                getAll: sinon.stub().returns({ session: 'data' })
            }
        };
    });

    it('should show user name and session', async () => {
        await controller.user({ params, response, services, globals });
        expect(services.model.getUser.calledWithExactly(params.id)).to.be.true;
        expect(globals.session.getAll.calledOnce).to.be.true;
        expect(response.json.calledWithExactly({
            user: { name: 'abc' },
            session: { session: 'data' }
        })).to.be.true;
    });

});

More Examples

JavaScript Examples

TypeScript Examples TypeScript version of the examples above

Also See

A javascript / typescript MongoDB modelling library which enables joins in collections, simplifies CRUD operations for sub / nested documents and implements schema based data validation.

A javascript / typescript utility library. Modules: array, string, base64, ampq, bcrypt, cache, captcha, csv, email, jwt, number, pdf, tmp, token, unit conversion, url params, session, form data

Report bugs here: https://github.com/coolgk/node-mvc/issues

Controller

Base controller class

Kind: global class

controller.getServices(dependencies) ⇒ object

Kind: instance method of Controller
Returns: object - - class dependencies, which is injected into the class methods by the router

Param Type Description
dependencies object global dependencies passed into the router's controller

Response

setting / getting standard responses in controllers

Kind: global class

response.getResponse() ⇒ object

Kind: instance method of Response
Returns: object - - last set response. format: { code: number, json?: any, status?: string, file?: { path: string, name?: string } }

response.send(data, [code]) ⇒ object

set arbitrary response

Kind: instance method of Response
Returns: object - - set response. format: { code: number, ...data }

Param Type Default Description
data object any json data
[code] number 200 http status code

response.json(json, [code]) ⇒ object

set a json response

Kind: instance method of Response
Returns: object - - set response. format: { code: number, json }

Param Type Default Description
json object any json data
[code] number 200 http status code

response.text([text], code) ⇒ object

set a http status response

Kind: instance method of Response
Returns: object - - set response. format: { code, status }

Param Type Default Description
[text] string text in response
code number 200 http status code

response.file(path, [name], [type], [code]) ⇒ object

set a file download response

Kind: instance method of Response
Returns: object - - set response. format: { file: { path, name }, code }

Param Type Default Description
path string file path
[name] string file name, if undefined require('path').basename(path) will be used
[type] string mime type
[code] number 200 http status code

About

A lightweight javascript / typescript MVC style library which helps you to create object oriented, modular and testable code

Resources

License

Stars

Watchers

Forks

Packages

No packages published