nails-boilerplate 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  This framework is designed to provide a lightweight, configurable MVC backend
4
4
  for node developers. With minimal dependencies, Nails offers a greater
5
- syntactical familiarity than php alongside the creative freedom of bleeding edge
6
- solutions like Rails and Django.
5
+ syntactical familiarity than php alongside the creative freedom of well developed
6
+ server framework solutions like Rails and Django.
7
7
 
8
8
  This boilerplate offers the basic necessities to get your MVC site off the ground.
9
9
  The modules used in Nails Boilerplate can be easily extended to produce the custom
@@ -53,7 +53,7 @@ service.js contains information necessary to run your server. By default, it
53
53
  specifies the port and the location of important libraries. To override these
54
54
  values in different runtime environments, add a child object.
55
55
  ```js
56
- module.exports = {
56
+ export default {
57
57
  ...
58
58
  PORT: 3000,
59
59
  PROD: {
@@ -77,7 +77,7 @@ var service_config = require('nails-boilerplate').config
77
77
  If the config contains a custom field,
78
78
 
79
79
  ```js
80
- module.exports = {
80
+ export default {
81
81
  ...
82
82
  PORT: 3000,
83
83
  yourCustomField: 'yourCustomValue'
@@ -137,7 +137,16 @@ parameters work.
137
137
  to dynamically route your request to the appropriate handler.
138
138
 
139
139
  #### db.js
140
- -- Coming soon... For now, take a look at the files in the example config directory --
140
+
141
+ Quickly configure your database connection here. Nails comes pre-configured to
142
+ use the sequelize connector, giving your models sequelize support. The initial setup
143
+ uses an in-memory *sqlite3* database. Change the address to change the location and
144
+ version of your desired sql database. Check out [Sequelize](https://sequelize.org)
145
+ for more info.
146
+
147
+ Alternatively, you can configure a connection to MongoDB using the mongoose_connector.js.
148
+ If enabled, models will accept [Mongoose](https://mongoosejs.com/docs/) schemas and will
149
+ be backed by the desired MongoDB. Consider using the in-memory DB during development.
141
150
 
142
151
  ## Controller
143
152
 
@@ -148,8 +157,10 @@ actions, receiving **params**, **request**, and **response** as arguments.
148
157
 
149
158
  For Example:
150
159
  ``` js
151
- const Controller = requre("nails-boilerplate").Controller
152
- class HomeController extends Controller {
160
+ // const Controller = requre("nails-boilerplate").Controller
161
+ import nails from 'nails-boilerplate';
162
+
163
+ class HomeController extends nails.Controller {
153
164
  index(params, request, response) {
154
165
  // default action
155
166
  }
@@ -158,7 +169,7 @@ class HomeController extends Controller {
158
169
  // does something then renders a view
159
170
  }
160
171
  }
161
- module.exports = HomeController;
172
+ export default HomeController;
162
173
 
163
174
  function helperMethod() {
164
175
  // does something but does not have access to response
@@ -168,6 +179,42 @@ function helperMethod() {
168
179
  defines a controller which will match any route to *home#\<action\>*. **index**
169
180
  and **signin** are actions which can be used to render a response to the client.
170
181
 
182
+ ### Local Routes
183
+
184
+ You can define a local routing table directly in the controller.
185
+ Local routes take precidence over global routes. All local routes
186
+ are prefixed with the controller name unless they start with '/'.
187
+ For example, in HomeController the following route:
188
+
189
+ `["get", "data", {action: 'getData', json: true}],`
190
+
191
+ will accept GET requests to /home/data and respond with the json
192
+ object returned by the getData function. If the route is changed to:
193
+
194
+ `["get", "/data", {action: 'getData', json: true}],`
195
+
196
+ it will accept GET requests to /data instead. All local routes are
197
+ implicitly routed to their respective parent controllers.
198
+
199
+ ```js
200
+ export default class UsersController extends nails.Controller {
201
+ routes = [
202
+ // Routes requests to /absolute/path
203
+ ['get', '/absolute/path', {action: 'actionA'}],
204
+ // Routes requests to /users/relative/path
205
+ ['get', './relative/path', {action: 'actionB'}],
206
+ // Routes requests to /users/relative/path
207
+ ['get', 'relative/path', {action: 'actionB'}],
208
+ ]
209
+
210
+ // Handles requests to /absolute/path
211
+ actionA(request, response, params) {}
212
+
213
+ // Handles requests to /users/relative/path
214
+ actionB(request, response, params) {}
215
+ }
216
+ ```
217
+
171
218
  ### Actions
172
219
  Actions are used to define how nails should respond to an incoming request.
173
220
  If no action has been defined for a route, nails will default to the index
@@ -218,23 +265,50 @@ overridden to allow for the rendering of views by name.
218
265
  ## Model
219
266
 
220
267
  Models are programmatic representations of data you wish to persist in a
221
- database. By default, models are subclasses of
268
+ database. The constructor for Model accepts two arguments: the `modelName` and an
269
+ `options` object which is passed to the database connector module.
270
+
271
+ ### Sequelize Models
272
+
273
+ Sequelize models are subclasses of
274
+ [Sequelize Models][sequelize_model_docs], and come with the `count()`, `findAll()`,
275
+ and `create()` methods, to name a few. You can define your own models by
276
+ extending an instance of the `Model` class provided by Nails:
277
+
278
+ ```js
279
+ // const Model = require("nails-boilerplate").Model;
280
+ import nails from 'nails-boilerplate';
281
+ import {DataTypes} from 'sequelize';
282
+ userSchema = {
283
+ name: {type: DataTypes.STRING, allowNull: false},
284
+ email: {type: DataTypes.STRING, allowNull: false}
285
+ };
286
+ export default class User extends new Model("User", userSchema) {
287
+ /**
288
+ * It is not recommended to add helper methods to Sequelize models. Define
289
+ * them in the schema instead.
290
+ */
291
+ };
292
+
293
+ ```
294
+
295
+ ### Mongoose Models
296
+ Mongoose models are subclasses of
222
297
  [Mongoose Models][mongoose_model_docs], and come with the `save()`, `find()`,
223
298
  and `where()` methods, to name a few. You can define your own models by
224
299
  extending an instance of the `Model` class provided by Nails:
225
300
 
226
301
  ``` js
227
- const Model = require("nails-boilerplate").Model;
302
+ // const Model = require("nails-boilerplate").Model;
303
+ import nails from 'nails-boilerplate';
228
304
  const userSchema = {name: String, email: String};
229
- module.exports = class User extends new Model("User", {schema: userSchema}) {
305
+ export default class User extends new Model("User", {schema: userSchema}) {
230
306
  // Define your helper methods here
231
307
  };
232
308
  ```
233
309
 
234
- The constructor for Model accepts two arguments: the `modelName` and an
235
- `options` object which is passed to the database connector module. The
236
- Mongoose connector accepts a schema field that is used to describe how
237
- documents are stored in MongoDB.
310
+ The `schema` option for Mongoose Models accepts a schema field that is used
311
+ to define how documents are stored in MongoDB.
238
312
 
239
313
  ### Database Connectors
240
314
 
@@ -268,3 +342,4 @@ Enjoy! Feature requests, bug reports, and comments are welcome on github.
268
342
  [express_routing_docs]: https://expressjs.com/en/guide/routing.html
269
343
  [express_request_docs]: https://expressjs.com/en/5x/api.html#req
270
344
  [mongoose_model_docs]: https://mongoosejs.com/docs/api/model.html
345
+ [sequelize_model_docs]: https://sequelize.org/docs/v6/core-concepts/model-basics/
package/lib/controller.js CHANGED
@@ -21,8 +21,8 @@ const DISABLE_AUTORENDER = new (class DisableAutorender {});
21
21
  class Controller {
22
22
  constructor() {
23
23
  var subclassName = this.constructor.name;
24
- var controllerName =
25
- subclassName.toLowerCase().replace(/controller$/, '');
24
+ var controllerName = this._getControllerName();
25
+ // subclassName.toLowerCase().replace(/controller$/, '');
26
26
  router.removeAllListeners('dispatchTo:' + controllerName);
27
27
  router.on('dispatchTo:' + controllerName, this._do.bind(this));
28
28
  }
@@ -55,6 +55,29 @@ class Controller {
55
55
  return constructed;
56
56
  }
57
57
 
58
+ _getControllerName() {
59
+ return this.constructor.name.toLowerCase().replace(/controller$/, '');
60
+ }
61
+
62
+ /** Initializes local and global routes defined on the Controller subclass */
63
+ _registerControllerRoutes() {
64
+ const controllerName = this._getControllerName();
65
+ if (this.routes && this.routes.length) {
66
+ const localizedRoutes = this.routes.map(route => {
67
+ // TODO: throw an error if :controller is present in local routes
68
+ // TODO: also account for other malformed local routes
69
+ const routePrefix = `/${controllerName}/`;
70
+ const modifiedDestination = route[1][0] == '/'
71
+ ? route[1]
72
+ : route[1].startsWith('./') ? route[1].replace('./', routePrefix) : routePrefix + route[1];
73
+ const modifiedOptions = {...route[2]};
74
+ modifiedOptions.controller = controllerName;
75
+ return [route[0], modifiedDestination, modifiedOptions];
76
+ });
77
+ router.addRoutes(localizedRoutes);
78
+ }
79
+ }
80
+
58
81
  /**** Network Methods *****/
59
82
  /**
60
83
  * The main entry function of the controller.
package/lib/nails.js CHANGED
@@ -66,7 +66,8 @@ async function configure( app_config ) {
66
66
  // set up router and controllers
67
67
  express_app.set("public_root", app_config.config.PUBLIC_ROOT);
68
68
  console.log("Initializing Router...");
69
- application.router = new Router( app_config.routes || [] );
69
+ // application.router = new Router( app_config.routes || [] );
70
+ application.router = new Router( [] );
70
71
  console.log("Application Router initialized");
71
72
 
72
73
  // init models
@@ -76,7 +77,7 @@ async function configure( app_config ) {
76
77
  // TODO: deprecate the old Model style
77
78
  if (app_config.config.MODELS_ROOT) {
78
79
  Model.set_connector(DBConnector, app_config.db);
79
- init_models(app_config.config.MODELS_ROOT);
80
+ await init_models(app_config.config.MODELS_ROOT);
80
81
  }
81
82
  // TODO: make this Model style mandatory
82
83
  console.log("Connecting to DB...");
@@ -84,7 +85,7 @@ async function configure( app_config ) {
84
85
  console.log("Generating model superclass...");
85
86
  await DBConnector.connect(app_config.db);
86
87
  ModelV2.setConnector(DBConnector);
87
- init_models_v2(app_config.config.MODELS_ROOT);
88
+ await init_models_v2(app_config.config.MODELS_ROOT);
88
89
  DBConnector.afterInitialization();
89
90
  } else {
90
91
  console.log("Instantiating DBConnector...");
@@ -99,7 +100,8 @@ async function configure( app_config ) {
99
100
  Controller.setRouter(application.router);
100
101
  application.controller = Controller.extend(ApplicationController);
101
102
  console.log('initializing controllers: ', app_config.config.CONTROLLERS_ROOT);
102
- init_controllers(app_config.config.CONTROLLERS_ROOT);
103
+ await init_controllers(app_config.config.CONTROLLERS_ROOT);
104
+ application.router.addRoutes(app_config.routes);
103
105
  };
104
106
 
105
107
  function startServer(config) {
@@ -145,11 +147,11 @@ function startServer(config) {
145
147
  }
146
148
 
147
149
  // TODO: create an initializer lib file.
148
- function init_controllers(controller_lib) {
149
- init_app_lib(Controller, controller_lib);
150
+ async function init_controllers(controller_lib) {
151
+ await init_app_lib(Controller, controller_lib);
150
152
  }
151
- function init_models(model_lib) {
152
- init_app_lib(Model, model_lib);
153
+ async function init_models(model_lib) {
154
+ await init_app_lib(Model, model_lib);
153
155
  }
154
156
  async function init_app_lib(superclass, abs_path) {
155
157
  console.log('attempting to import:', abs_path);
@@ -161,11 +163,15 @@ async function init_app_lib(superclass, abs_path) {
161
163
  if (!superclass.isPrototypeOf(subclass))
162
164
  return superclass.extend(subclass);
163
165
  // ES6 Class was provided
164
- return new subclass();
166
+ const subInstance = new subclass();
167
+ if (superclass == Controller) {
168
+ subInstance._registerControllerRoutes();
169
+ }
170
+ return subInstance;
171
+ }
172
+ for (const rel_path of fs.readdirSync(abs_path)) {
173
+ await init_app_lib(superclass, path.join(abs_path, rel_path));
165
174
  }
166
- fs.readdirSync(abs_path).forEach(function(rel_path) {
167
- init_app_lib(superclass, path.join(abs_path, rel_path));
168
- });
169
175
  }
170
176
 
171
177
  async function init_models_v2(abs_path) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nails-boilerplate",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "A node.js webserver scaffold",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -8,7 +8,7 @@
8
8
  "nails": "./bin/lib/nails.js"
9
9
  },
10
10
  "scripts": {
11
- "copyreadme": "mammoth README.md templates/client/README.html",
11
+ "copyreadme": "node bin/convertreadmetohtml.js",
12
12
  "test": "mocha --exit spec",
13
13
  "debug": "mocha --exit debug spec"
14
14
  },
@@ -1,6 +1,11 @@
1
1
  import nails from "../../../../../index.js";
2
2
  // require("../../../../../index.js").Controller;
3
3
  export default class ClassbasedController extends nails.Controller {
4
+ routes = [
5
+ ["get", "./arbi/trary/testLocalRoutes", {action: 'testLocalRoutes', json: true}],
6
+ ["get", "arbi00/trary00/testLocalRoutes", {action: 'testLocalRoutes', json: true}],
7
+ ["get", "/cl4ssb4sed/arbi/trary/testLocalRoutes", {action: 'testLocalRoutes', json: true}],
8
+ ];
4
9
  // DO NOT OVERRIDE CONSTRUCTOR
5
10
  index(params, request, response) {
6
11
  response.json({
@@ -19,4 +24,10 @@ export default class ClassbasedController extends nails.Controller {
19
24
  classbased_testpromise: true
20
25
  });
21
26
  }
27
+
28
+ testLocalRoutes(params, request, response) {
29
+ return {
30
+ testLocalRoutes: true
31
+ };
32
+ }
22
33
  }
@@ -1,6 +1,6 @@
1
1
  // Import the dependencies for testing
2
2
  import * as chai from 'chai';
3
- import {default as chaiHttp, request} from "chai-http";
3
+ import { default as chaiHttp, request } from "chai-http";
4
4
  // import chaiHttp from 'chai-http';
5
5
  import { assert } from 'chai';
6
6
  // import { WebSocket } from 'ws';
@@ -15,110 +15,139 @@ let mongod = null;
15
15
  chai.use(chaiHttp);
16
16
  chai.should();
17
17
 
18
- describe("Integration", function() {
19
- before(async function() {
20
- // this.timeout(30000);
21
- try {
22
- var nails = (await import('./services/integration/server.js')).default;
23
- } catch (e) {
24
- console.log("could not import server");
25
- console.log(e);
26
- }
27
- console.log("got here");
28
- express_app = nails.application;
29
- return new Promise((resolve, reject) => {
30
- nails.events.on("ready", () => {
31
- console.log("ready was emitted!");
32
- resolve();
33
- });
18
+ describe("Integration", function () {
19
+ before(async function () {
20
+ // this.timeout(30000);
21
+ try {
22
+ var nails = (await import('./services/integration/server.js')).default;
23
+ } catch (e) {
24
+ console.log("could not import server");
25
+ console.log(e);
26
+ }
27
+ console.log("got here");
28
+ express_app = nails.application;
29
+ return new Promise((resolve, reject) => {
30
+ nails.events.on("ready", () => {
31
+ console.log("ready was emitted!");
32
+ resolve();
34
33
  });
34
+ });
35
35
  // })
36
36
  // MongoMemoryServer.create({instance: service_config.db}).then((mongodb) => {
37
37
  // mongod = mongodb;
38
-
38
+
39
39
  // });
40
40
  });
41
- describe("GET /", function() {
42
- it('should return the expected JSON from index', function(done) {
41
+ describe("GET /", function () {
42
+ it('should return the expected JSON from index', function (done) {
43
43
  request.execute(express_app)
44
- .get('/')
45
- .end((err, res) => {
46
- res.should.have.status(200);
47
- assert(res.text == JSON.stringify({home_index: true}));
48
- done();
49
- });
44
+ .get('/')
45
+ .end((err, res) => {
46
+ res.should.have.status(200);
47
+ assert(res.text == JSON.stringify({ home_index: true }));
48
+ done();
49
+ });
50
50
  });
51
51
  });
52
- describe("GET /classbased", function() {
53
- it('should return the expected JSON from index', function(done) {
52
+ describe("GET /classbased", function () {
53
+ it('should return the expected JSON from index', function (done) {
54
54
  request.execute(express_app)
55
- .get('/classbased')
56
- .end((err, res) => {
57
- res.should.have.status(200);
58
- assert(res.text == JSON.stringify({classbased_index: true}));
59
- done();
60
- });
55
+ .get('/classbased')
56
+ .end((err, res) => {
57
+ res.should.have.status(200);
58
+ assert(res.text == JSON.stringify({ classbased_index: true }));
59
+ done();
60
+ });
61
61
  });
62
62
  });
63
- describe("GET /^\\/(\\w+)\\/(\\w+)$/i", function() {
64
- it ('should route to home_controller#testaction', function(done) {
63
+ describe("Get /classbased/arbi/trary/testLocalRoutes", function () {
64
+ it("Should respect the defined local route and return the expected JSON", function (done) {
65
65
  request.execute(express_app)
66
- .get('/home/testaction')
67
- .end((err, res) => {
68
- res.should.have.status(200);
69
- assert(res.text == JSON.stringify({home_testaction: true}));
70
- done();
71
- });
66
+ .get('/classbased/arbi00/trary00/testLocalRoutes')
67
+ .end((err, res) => {
68
+ res.should.have.status(200);
69
+ assert(res.text == JSON.stringify({ testLocalRoutes: true }));
70
+ done();
71
+ });
72
72
  });
73
- it('should route to classbased_controller#testaction', function(done) {
73
+ it("Should not rewrite local route prefix and return the expected JSON", function (done) {
74
74
  request.execute(express_app)
75
- .get('/classbased/testaction')
76
- .end((err, res) => {
77
- res.should.have.status(200);
78
- assert(res.text == JSON.stringify({classbased_testaction: true}));
79
- done();
80
- });
75
+ .get('/cl4ssb4sed/arbi/trary/testLocalRoutes')
76
+ .end((err, res) => {
77
+ res.should.have.status(200);
78
+ assert(res.text == JSON.stringify({ testLocalRoutes: true }));
79
+ done();
80
+ });
81
81
  });
82
- it('should render correctly when a promise is returned', function(done) {
82
+ it("Should rewrite local route prefix, './' and return the expected JSON", function (done) {
83
83
  request.execute(express_app)
84
- .get('/classbased/testpromise')
85
- .end((err, res) => {
86
- res.should.have.status(200);
87
- assert(res.text == JSON.stringify({classbased_testpromise: true}));
88
- done();
89
- });
84
+ .get('/classbased/arbi/trary/testLocalRoutes')
85
+ .end((err, res) => {
86
+ res.should.have.status(200);
87
+ assert(res.text == JSON.stringify({ testLocalRoutes: true }));
88
+ done();
89
+ });
90
+ });
91
+ });
92
+ describe("GET /^\\/(\\w+)\\/(\\w+)$/i", function () {
93
+ it('should route to home_controller#testaction', function (done) {
94
+ request.execute(express_app)
95
+ .get('/home/testaction')
96
+ .end((err, res) => {
97
+ res.should.have.status(200);
98
+ assert(res.text == JSON.stringify({ home_testaction: true }));
99
+ done();
100
+ });
101
+ });
102
+ it('should route to classbased_controller#testaction', function (done) {
103
+ request.execute(express_app)
104
+ .get('/classbased/testaction')
105
+ .end((err, res) => {
106
+ res.should.have.status(200);
107
+ assert(res.text == JSON.stringify({ classbased_testaction: true }));
108
+ done();
109
+ });
110
+ });
111
+ it('should render correctly when a promise is returned', function (done) {
112
+ request.execute(express_app)
113
+ .get('/classbased/testpromise')
114
+ .end((err, res) => {
115
+ res.should.have.status(200);
116
+ assert(res.text == JSON.stringify({ classbased_testpromise: true }));
117
+ done();
118
+ });
90
119
  })
91
120
  });
92
121
 
93
- describe("GET /error/fivehundred", function() {
94
- it('should log the stack trace', function(done) {
122
+ describe("GET /error/fivehundred", function () {
123
+ it('should log the stack trace', function (done) {
95
124
  request.execute(express_app)
96
- .get('/error/fivehundred/500')
97
- .end((err, res) => {
98
- res.should.have.status(500);
99
- //console.log(res.text);
100
- //console.log(res);
101
- //console.log(err);
102
- done();
103
- })
125
+ .get('/error/fivehundred/500')
126
+ .end((err, res) => {
127
+ res.should.have.status(500);
128
+ //console.log(res.text);
129
+ //console.log(res);
130
+ //console.log(err);
131
+ done();
132
+ })
104
133
  });
105
- it('should return meaningful JSON', function(done) {
134
+ it('should return meaningful JSON', function (done) {
106
135
  request.execute(express_app)
107
- .get('/error/fivehundred')
108
- .end((err, res) => {
109
- res.should.have.status(500);
110
- console.log("ZZZZZZZZZZ");
111
- console.log(res.text);
112
- //console.log(res.text);
113
- //console.log(res);
114
- //console.log(err);
115
- done();
116
- })
136
+ .get('/error/fivehundred')
137
+ .end((err, res) => {
138
+ res.should.have.status(500);
139
+ console.log("ZZZZZZZZZZ");
140
+ console.log(res.text);
141
+ //console.log(res.text);
142
+ //console.log(res);
143
+ //console.log(err);
144
+ done();
145
+ })
117
146
  });
118
147
  });
119
148
 
120
- describe("WebSockets", function() {
121
- it("should listen on /", function(done) {
149
+ describe("WebSockets", function () {
150
+ it("should listen on /", function (done) {
122
151
  this.timeout(2000);
123
152
  var requester = request.execute(express_app).keepOpen();
124
153
  var wsClient = new WebSocket("ws://localhost:3333/");
@@ -128,17 +157,17 @@ describe("Integration", function() {
128
157
  assert(message == "It worked");
129
158
  });
130
159
  });
131
- it("should listen on /voodoo", function(done) {
160
+ it("should listen on /voodoo", function (done) {
132
161
  this.timeout(2000);
133
162
  var requester = request.execute(express_app).keepOpen();
134
163
  var wsClient = new WebSocket("ws://localhost:3333/voodoo");
135
164
 
136
165
  wsClient.on('message', (message) => {
137
- assert(message == "Voodoo worked");
166
+ assert(message == "Voodoo worked");
138
167
  });
139
168
  wsClient.on('close', (code, reason) => done());
140
169
  });
141
- it("should not listen on /voodootwo", function(done) {
170
+ it("should not listen on /voodootwo", function (done) {
142
171
  // TODO: this doesn't fail. Figure out why.
143
172
  //done();
144
173
  this.timeout(2000);
@@ -157,9 +186,9 @@ describe("Integration", function() {
157
186
  it("should correctly handle dynamic controller and action");
158
187
  });
159
188
 
160
- describe("GET /json/:action", function() {
189
+ describe("GET /json/:action", function () {
161
190
  it('should render params if nothing is returned by the action',
162
- function(done) {
191
+ function (done) {
163
192
  request.execute(express_app)
164
193
  // Route to index action.
165
194
  .get('/json/testparams?testkey=testvalue')
@@ -170,79 +199,79 @@ describe("Integration", function() {
170
199
  });
171
200
  }
172
201
  );
173
- it('should render the returned object as json', function(done) {
202
+ it('should render the returned object as json', function (done) {
174
203
  request.execute(express_app)
175
- .get('/json/testaction')
204
+ .get('/json/testaction')
205
+ .end((err, res) => {
206
+ res.should.have.status(200);
207
+ assert(res.text == JSON.stringify({ json_testaction: true }));
208
+ done();
209
+ });
210
+ });
211
+ it('should asynchronously render the resolved promise as json',
212
+ function (done) {
213
+ request.execute(express_app)
214
+ .get('/json/testpromise')
176
215
  .end((err, res) => {
177
216
  res.should.have.status(200);
178
- assert(res.text == JSON.stringify({json_testaction: true}));
217
+ assert(res.text == JSON.stringify({ json_testpromise: true }));
179
218
  done();
180
219
  });
181
- });
182
- it('should asynchronously render the resolved promise as json',
183
- function(done) {
184
- request.execute(express_app)
185
- .get('/json/testpromise')
186
- .end((err, res) => {
187
- res.should.have.status(200);
188
- assert(res.text == JSON.stringify({json_testpromise: true}));
189
- done();
190
- });
191
220
  }
192
221
  );
193
222
  });
194
- describe("Get /ManualRenderAsync", function() {
223
+ describe("Get /ManualRenderAsync", function () {
195
224
  it("./testmanualrenderasync Should not throw an exception after manually"
196
- + " rendering json asynchronously",
225
+ + " rendering json asynchronously",
197
226
  done => {
198
227
  request.execute(express_app)
199
- .get("/manualrenderasync/testmanualrenderasync")
200
- .end((err, res) => {
201
- res.should.have.status(200);
202
- assert(res.text ==
203
- JSON.stringify({json_testmanualrenderasync: true}));
204
- done();
205
- });
228
+ .get("/manualrenderasync/testmanualrenderasync")
229
+ .end((err, res) => {
230
+ res.should.have.status(200);
231
+ assert(res.text ==
232
+ JSON.stringify({ json_testmanualrenderasync: true }));
233
+ done();
234
+ });
206
235
  });
207
236
 
208
- it("./testmanualrenderexplicitasync Should not throw an exception after"
209
- + " manually rendering json asynchronously using the async function"
210
- + " tag",
211
- done => {
212
- request.execute(express_app)
213
- .get("/manualrenderasync/testmanualrenderexplicitasync")
214
- .end((err, res) => {
215
- res.should.have.status(200);
216
- assert(res.text ==
217
- JSON.stringify({json_testmanualrenderexplicitasync: true}));
218
- done();
219
- });
220
- });
237
+ it("./testmanualrenderexplicitasync Should not throw an exception after"
238
+ + " manually rendering json asynchronously using the async function"
239
+ + " tag",
240
+ done => {
241
+ request.execute(express_app)
242
+ .get("/manualrenderasync/testmanualrenderexplicitasync")
243
+ .end((err, res) => {
244
+ res.should.have.status(200);
245
+ assert(res.text ==
246
+ JSON.stringify({ json_testmanualrenderexplicitasync: true }));
247
+ done();
248
+ });
249
+ });
221
250
 
222
251
  });
223
- describe("Mongoose Model", function() {
224
- beforeEach(async function() {
252
+ describe("Mongoose Model", function () {
253
+ beforeEach(async function () {
225
254
  // Used to initialize mongod here
226
255
  });
227
- afterEach(async function() {
256
+ afterEach(async function () {
228
257
  //await mongod.stop();
229
258
  });
230
- it('should save to the correct database', function(done) {
259
+ it('should save to the correct database', function (done) {
231
260
  let dogName = "Penny";
232
261
  let dogId = null;
233
262
  request.execute(express_app)
234
- .get(`/modeltest/createdog?name=${dogName}`)
235
- .end((err, res) => {
236
- res.should.have.status(200);
237
- dogId = res.text.replaceAll('"', '');
238
- request.execute(express_app)
239
- .get(`/modeltest/getdogbyid?id=${dogId}`)
240
- .end((err, res) => {
241
- assert.equal(
242
- res.text, JSON.stringify({name: dogName, good: true}));
243
- done();
244
- });
245
- });
263
+ .get(`/modeltest/createdog?name=${dogName}`)
264
+ .end((err, res) => {
265
+ res.should.have.status(200);
266
+ dogId = res.text.replaceAll('"', '');
267
+ request.execute(express_app)
268
+ .get(`/modeltest/getdogbyid?id=${dogId}`)
269
+ .end((err, res) => {
270
+ assert.equal(
271
+ res.text, JSON.stringify({ name: dogName, good: true }));
272
+ done();
273
+ });
274
+ });
246
275
  });
247
276
 
248
277
  });
@@ -1,8 +1,8 @@
1
1
  <h1 id="nailsboilerplateanodewebserviceframework">Nails-Boilerplate: A Node Webservice Framework</h1>
2
2
  <p>This framework is designed to provide a lightweight, configurable MVC backend
3
3
  for node developers. With minimal dependencies, Nails offers a greater
4
- syntactical familiarity than php alongside the creative freedom of bleeding edge
5
- solutions like Rails and Django.</p>
4
+ syntactical familiarity than php alongside the creative freedom of well developed
5
+ server framework solutions like Rails and Django.</p>
6
6
  <p>This boilerplate offers the basic necessities to get your MVC site off the ground.
7
7
  The modules used in Nails Boilerplate can be easily extended to produce the custom
8
8
  functionality to fit your needs, and you are encouraged to do so.</p>
@@ -37,7 +37,7 @@ help you tailor your service to your needs.</p>
37
37
  <p>service.js contains information necessary to run your server. By default, it
38
38
  specifies the port and the location of important libraries. To override these
39
39
  values in different runtime environments, add a child object.</p>
40
- <pre><code class="js language-js">module.exports = {
40
+ <pre><code class="js language-js">export default {
41
41
  ...
42
42
  PORT: 3000,
43
43
  PROD: {
@@ -54,7 +54,7 @@ module:</p>
54
54
  <pre><code class="js language-js">var service_config = require('nails-boilerplate').config
55
55
  </code></pre>
56
56
  <p>If the config contains a custom field,</p>
57
- <pre><code class="js language-js">module.exports = {
57
+ <pre><code class="js language-js">export default {
58
58
  ...
59
59
  PORT: 3000,
60
60
  yourCustomField: 'yourCustomValue'
@@ -107,15 +107,24 @@ than indices. You can name your regex captures "controller" and/or "action"
107
107
  to dynamically route your request to the appropriate handler.</li>
108
108
  </ul>
109
109
  <h4 id="dbjs">db.js</h4>
110
- <p>-- Coming soon… For now, take a look at the files in the example config directory --</p>
110
+ <p>Quickly configure your database connection here. Nails comes pre-configured to
111
+ use the sequelize connector, giving your models sequelize support. The initial setup
112
+ uses an in-memory <em>sqlite3</em> database. Change the address to change the location and
113
+ version of your desired sql database. Check out <a href="https://sequelize.org">Sequelize</a>
114
+ for more info.</p>
115
+ <p>Alternatively, you can configure a connection to MongoDB using the mongoose_connector.js.
116
+ If enabled, models will accept <a href="https://mongoosejs.com/docs/">Mongoose</a> schemas and will
117
+ be backed by the desired MongoDB. Consider using the in-memory DB during development.</p>
111
118
  <h2 id="controller">Controller</h2>
112
119
  <p>Controllers are defined in app/controllers/. Each controller module should
113
120
  define a Controller subclass. The name will be used to match routes defined in
114
121
  config/routes.js for incoming requests. Methods on the controller can be used as
115
122
  actions, receiving <strong>params</strong>, <strong>request</strong>, and <strong>response</strong> as arguments.</p>
116
123
  <p>For Example:</p>
117
- <pre><code class="js language-js">const Controller = requre("nails-boilerplate").Controller
118
- class HomeController extends Controller {
124
+ <pre><code class="js language-js">// const Controller = requre("nails-boilerplate").Controller
125
+ import nails from 'nails-boilerplate';
126
+
127
+ class HomeController extends nails.Controller {
119
128
  index(params, request, response) {
120
129
  // default action
121
130
  }
@@ -124,7 +133,7 @@ class HomeController extends Controller {
124
133
  // does something then renders a view
125
134
  }
126
135
  }
127
- module.exports = HomeController;
136
+ export default HomeController;
128
137
 
129
138
  function helperMethod() {
130
139
  // does something but does not have access to response
@@ -132,6 +141,34 @@ function helperMethod() {
132
141
  </code></pre>
133
142
  <p>defines a controller which will match any route to <em>home#\<action></em>. <strong>index</strong>
134
143
  and <strong>signin</strong> are actions which can be used to render a response to the client.</p>
144
+ <h3 id="localroutes">Local Routes</h3>
145
+ <p>You can define a local routing table directly in the controller.
146
+ Local routes take precidence over global routes. All local routes
147
+ are prefixed with the controller name unless they start with '/'.
148
+ For example, in HomeController the following route:</p>
149
+ <p><code>["get", "data", {action: 'getData', json: true}],</code></p>
150
+ <p>will accept GET requests to /home/data and respond with the json
151
+ object returned by the getData function. If the route is changed to:</p>
152
+ <p><code>["get", "/data", {action: 'getData', json: true}],</code></p>
153
+ <p>it will accept GET requests to /data instead. All local routes are
154
+ implicitly routed to their respective parent controllers.</p>
155
+ <pre><code class="js language-js">export default class UsersController extends nails.Controller {
156
+ routes = [
157
+ // Routes requests to /absolute/path
158
+ ['get', '/absolute/path', {action: 'actionA'}],
159
+ // Routes requests to /users/relative/path
160
+ ['get', './relative/path', {action: 'actionB'}],
161
+ // Routes requests to /users/relative/path
162
+ ['get', 'relative/path', {action: 'actionB'}],
163
+ ]
164
+
165
+ // Handles requests to /absolute/path
166
+ actionA(request, response, params) {}
167
+
168
+ // Handles requests to /users/relative/path
169
+ actionB(request, response, params) {}
170
+ }
171
+ </code></pre>
135
172
  <h3 id="actions">Actions</h3>
136
173
  <p>Actions are used to define how nails should respond to an incoming request.
137
174
  If no action has been defined for a route, nails will default to the index
@@ -172,20 +209,41 @@ params object:</p>
172
209
  overridden to allow for the rendering of views by name.</p>
173
210
  <h2 id="model">Model</h2>
174
211
  <p>Models are programmatic representations of data you wish to persist in a
175
- database. By default, models are subclasses of
212
+ database. The constructor for Model accepts two arguments: the <code>modelName</code> and an
213
+ <code>options</code> object which is passed to the database connector module.</p>
214
+ <h3 id="sequelizemodels">Sequelize Models</h3>
215
+ <p>Sequelize models are subclasses of
216
+ <a href="https://sequelize.org/docs/v6/core-concepts/model-basics/">Sequelize Models</a>, and come with the <code>count()</code>, <code>findAll()</code>,
217
+ and <code>create()</code> methods, to name a few. You can define your own models by
218
+ extending an instance of the <code>Model</code> class provided by Nails:</p>
219
+ <pre><code class="js language-js">// const Model = require("nails-boilerplate").Model;
220
+ import nails from 'nails-boilerplate';
221
+ import {DataTypes} from 'sequelize';
222
+ userSchema = {
223
+ name: {type: DataTypes.STRING, allowNull: false},
224
+ email: {type: DataTypes.STRING, allowNull: false}
225
+ };
226
+ export default class User extends new Model("User", userSchema) {
227
+ /**
228
+ * It is not recommended to add helper methods to Sequelize models. Define
229
+ * them in the schema instead.
230
+ */
231
+ };
232
+ </code></pre>
233
+ <h3 id="mongoosemodels">Mongoose Models</h3>
234
+ <p>Mongoose models are subclasses of
176
235
  <a href="https://mongoosejs.com/docs/api/model.html">Mongoose Models</a>, and come with the <code>save()</code>, <code>find()</code>,
177
236
  and <code>where()</code> methods, to name a few. You can define your own models by
178
237
  extending an instance of the <code>Model</code> class provided by Nails:</p>
179
- <pre><code class="js language-js">const Model = require("nails-boilerplate").Model;
238
+ <pre><code class="js language-js">// const Model = require("nails-boilerplate").Model;
239
+ import nails from 'nails-boilerplate';
180
240
  const userSchema = {name: String, email: String};
181
- module.exports = class User extends new Model("User", {schema: userSchema}) {
241
+ export default class User extends new Model("User", {schema: userSchema}) {
182
242
  // Define your helper methods here
183
243
  };
184
244
  </code></pre>
185
- <p>The constructor for Model accepts two arguments: the <code>modelName</code> and an
186
- <code>options</code> object which is passed to the database connector module. The
187
- Mongoose connector accepts a schema field that is used to describe how
188
- documents are stored in MongoDB.</p>
245
+ <p>The <code>schema</code> option for Mongoose Models accepts a schema field that is used
246
+ to define how documents are stored in MongoDB.</p>
189
247
  <h3 id="databaseconnectors">Database Connectors</h3>
190
248
  <p>Database connectors are intermediaries which define how a Model interacts with
191
249
  a database. Database connector modules need to export two methods:</p>
@@ -1,10 +1,34 @@
1
1
  import nails from "nails-boilerplate";
2
2
 
3
3
  export default class HomeController extends nails.Controller {
4
- index(params, request, response) {
5
- return {
6
- title: "My first Nails Service",
7
- welcome_message: "Welcome to Nails"
8
- };
9
- }
10
- };
4
+ /**
5
+ * You can define a local routing table directly in the controller.
6
+ * Local routes take precidence over global routes. All local routes
7
+ * are prefixed with the controller name unless they start with '/'.
8
+ * For example, in HomeController the following route:
9
+ *
10
+ * ["get", "data", {action: 'getData', json: true}],
11
+ *
12
+ * will accept GET requests to /home/data and respond with the json
13
+ * object returned by the getData function. If the route is changed to:
14
+ *
15
+ * ["get", "/data", {action: 'getData', json: true}],
16
+ *
17
+ * it will accept GET requests to /data instead. All local routes are
18
+ * implicitly routed to their respective parent controllers.
19
+ */
20
+ routes = [
21
+ ["get", "data", {action: 'getData', json: true}],
22
+ ];
23
+
24
+ index(params, request, response) {
25
+ return {
26
+ title: "My first Nails Service",
27
+ welcome_message: "Welcome to Nails"
28
+ };
29
+ }
30
+
31
+ getData(params, request, response) {
32
+ return {testData: Math.floor(Math.random() * 10000000)}
33
+ }
34
+ };