npm install @coolgk/mvc
A simple, lightweight javascript / typescript nodejs mvc framework that helps you to create object oriented, modular and testable code.
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
...
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;
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);
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;
});
});
- A simple app
- An example that decouples express from controllers
- An example with session and form data handlers
- A native node app without express
- An example of using RabbitMQ for managing requests
TypeScript Examples TypeScript version of the examples above
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
Base controller class
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 |
setting / getting standard responses in controllers
Kind: global class
- Response
- .getResponse() ⇒
object
- .send(data, [code]) ⇒
object
- .json(json, [code]) ⇒
object
- .text([text], code) ⇒
object
- .file(path, [name], [type], [code]) ⇒
object
- .getResponse() ⇒
Kind: instance method of Response
Returns: object
- - last set response. format: { code: number, json?: any, status?: string, file?: { path: string, name?: string } }
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 |
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 |
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 |
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 |