nails-boilerplate 2.0.10 → 2.0.11-0

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
@@ -1,345 +1,425 @@
1
- # Nails-Boilerplate: A Node Webservice Framework
2
-
3
- This framework is designed to provide a lightweight, configurable MVC backend
4
- for node developers. With minimal dependencies, Nails offers a greater
5
- syntactical familiarity than php alongside the creative freedom of well developed
6
- server framework solutions like Rails and Django.
7
-
8
- This boilerplate offers the basic necessities to get your MVC site off the ground.
9
- The modules used in Nails Boilerplate can be easily extended to produce the custom
10
- functionality to fit your needs, and you are encouraged to do so.
11
-
12
- ## Install
13
-
14
- ```
15
- sudo npm install -g nails-boilerplate
16
-
17
- nails init <app_name>
18
- ```
19
-
20
- This will initialize a barebones app in the directory of the same name. Take a
21
- look at the self-documented config files and example controller and view before
22
- getting started. Additional controllers and views will automatically be imported
23
- into nails. Now just hook the new controllers in with some new routes and you're
24
- off to a good start.
25
-
26
- ```
27
- cd app_name
28
-
29
- npm install
30
-
31
- node server
32
- ```
33
-
34
- ## Getting to know your Nails service
35
-
36
- For your convenience, here is a quick outline of the main components of a nails service.
37
- Remember: each object comes with an example file to use for reference when building your service.
38
-
39
- ### Config
40
- Your configuration files are stored in app_name/config/. There are three default config files:
41
-
42
- ```
43
- service.js
44
- routes.js
45
- db.js
46
- ```
47
-
48
- Each default config file is annotated with comments documenting each field to
49
- help you tailor your service to your needs.
50
-
51
- #### service.js
52
- service.js contains information necessary to run your server. By default, it
53
- specifies the port and the location of important libraries. To override these
54
- values in different runtime environments, add a child object.
55
- ```js
56
- export default {
57
- ...
58
- PORT: 3000,
59
- PROD: {
60
- PORT: 80
61
- }
62
- }
63
- ```
64
-
65
- Nails checks the NODE_ENV environment variable. If a matching child config
66
- object is present, then those values will override the parent config. In the
67
- above example, PORT will be overridden to 80 if NODE_ENV is set to PROD.
68
-
69
- While most of these values don't need to be changed, feel free to add custom
70
- fields. The resulting config will be available to your service through the nails
71
- module:
72
-
73
- ```js
74
- var service_config = require('nails-boilerplate').config
75
- ```
76
-
77
- If the config contains a custom field,
78
-
79
- ```js
80
- export default {
81
- ...
82
- PORT: 3000,
83
- yourCustomField: 'yourCustomValue'
84
- }
85
- ```
86
-
87
- then `service_config.yourCustomField` as defined above will be equal to
88
- `'yourCustomValue'`.
89
-
90
- #### routes.js
91
- *routes.js* is a list defining mappings from a url path to a *Controller* and
92
- *Action*. Each entry in the list is an array with three elements:
93
- `[method, path, options]`
94
-
95
- **method** is a string defining the HTTP request method of the route. Supported
96
- methods are *GET*, *PUT*, *POST*, *DELETE*, and *ALL*. All is a special case
97
- which matches all HTTP request methods.
98
-
99
- **path** is a string or regular expression which matches the path of the
100
- incoming request. If *path* is a string, then the request must match exactly*.
101
- You can use route parameters to dynamically match parts of the path and assign
102
- them to the *params* object. For example, if you define a route with the path:
103
- `'/users/:userId'`
104
- and your service receives a request with the path:
105
- `'/users/777'`
106
- then *userId* will be set in the params object:
107
- ```js
108
- { userId: 777 }
109
- ```
110
-
111
- You can define *:controller* and *:action* as route parameters as well. Not only
112
- will those values be set in the params object, but the request will be routed
113
- to the matching controller and action. See
114
- [express routes][express_routing_docs] for more information on how route
115
- parameters work.
116
-
117
- \*requests for static assets will only match the prefix of the path.
118
-
119
- **options** is a JSON object which modifies how the request will be routed:
120
- * *controller* a String indicating the controller to route to.
121
- * *action* a String indicating the action to route to.
122
- * *json* a boolean indicating whether to render a JSON response rather than an
123
- HTML response. If true, nails will not attempt to render a view for this
124
- route. Instead, your service will respond with JSON for this route.
125
- * *public* a boolean indicating whether this route is for static assets. If
126
- true, the router will only attempt to match the prefix of the request path.
127
- The child portion of the path will be forwarded to the *public/* folder in
128
- your service directory. For route:
129
- `['GET', '/public_url_path', {public: true}]`
130
- if your service receives a request to:
131
- `/public_url_path/js/index.js`
132
- then the response will be the file:
133
- `your_service_root_path/public/js/index.js`
134
- * *0, 1, 2...* a string which gives regex captures named keys in the params
135
- object. This will give your regex captures more meaningful named keys rather
136
- than indices. You can name your regex captures "controller" and/or "action"
137
- to dynamically route your request to the appropriate handler.
138
-
139
- #### db.js
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.
150
-
151
- ## Controller
152
-
153
- Controllers are defined in app/controllers/. Each controller module should
154
- define a Controller subclass. The name will be used to match routes defined in
155
- config/routes.js for incoming requests. Methods on the controller can be used as
156
- actions, receiving **params**, **request**, and **response** as arguments.
157
-
158
- For Example:
159
- ``` js
160
- // const Controller = requre("nails-boilerplate").Controller
161
- import nails from 'nails-boilerplate';
162
-
163
- class HomeController extends nails.Controller {
164
- index(params, request, response) {
165
- // default action
166
- }
167
-
168
- signin(params, request, response) {
169
- // does something then renders a view
170
- }
171
- }
172
- export default HomeController;
173
-
174
- function helperMethod() {
175
- // does something but does not have access to response
176
- }
177
- ```
178
-
179
- defines a controller which will match any route to *home#\<action\>*. **index**
180
- and **signin** are actions which can be used to render a response to the client.
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
-
218
- ### Actions
219
- Actions are used to define how nails should respond to an incoming request.
220
- If no action has been defined for a route, nails will default to the index
221
- action.*
222
-
223
- For example, HomeController#index will attempt to render the view defined in
224
- //app/views/home/index.jsx
225
-
226
- The view each action searches for will always follow the pattern:
227
- //app/views/*\[controller name\]*/*\[action name\]*.jsx
228
-
229
- The file extension may differ based on which template engine you configure.
230
-
231
- Depending on the return value, Nails will pass a different set of parameters to
232
- the view engine:
233
- * **undefined** If there is no return statement in the action, Nails will pass
234
- the *params* obect to the rendering engine.
235
- * **Object** If a generic object is returned, Nails will attempt to autorender
236
- the view immediately using the returned object instead of *params*.**
237
- * **Promise** If a promise is returned, Nails will wait to autorender the view
238
- until the *Promise* resolves. If it resolves with no return value, the view
239
- is rendered using *params*. Otherwise, the view is rendered using the
240
- resolved value of the *Promise*\**
241
-
242
- \*If a response has already been sent to the client, autorender will be skipped.
243
- \*\*For JSON routes, the returned object will be rendered as stringified JSON.
244
-
245
- #### Params
246
- Params is a generic JSON object which represents the request details. Usually,
247
- Params will correspond to the query portion of your request.
248
-
249
- For example, a GET request to *//some/path?item0=a&item1=b* will generate the
250
- params object:
251
- ``` js
252
- {
253
- item0: "a",
254
- item1: "b"
255
- }
256
- ```
257
-
258
- #### Request
259
- An [express Request object][express_request_docs].
260
-
261
- #### Response
262
- The response object provided by *express.js*. The *#render()* method has been
263
- overridden to allow for the rendering of views by name.
264
-
265
- ## Model
266
-
267
- Models are programmatic representations of data you wish to persist in a
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
297
- [Mongoose Models][mongoose_model_docs], and come with the `save()`, `find()`,
298
- and `where()` methods, to name a few. You can define your own models by
299
- extending an instance of the `Model` class provided by Nails:
300
-
301
- ``` js
302
- // const Model = require("nails-boilerplate").Model;
303
- import nails from 'nails-boilerplate';
304
- const userSchema = {name: String, email: String};
305
- export default class User extends new Model("User", {schema: userSchema}) {
306
- // Define your helper methods here
307
- };
308
- ```
309
-
310
- The `schema` option for Mongoose Models accepts a schema field that is used
311
- to define how documents are stored in MongoDB.
312
-
313
- ### Database Connectors
314
-
315
- Database connectors are intermediaries which define how a Model interacts with
316
- a database. Database connector modules need to export two methods:
317
- * _connect(db_config)_ uses the db config defined in *db.js* to connect to
318
- a database. This function will be called once by Nails.
319
- * _generateModelSuperclass(name, options)_ uses the provided Model name and
320
- options to generate a Model prototype for use as an interface. A Model
321
- interface is generated for each of your models, allowing them to interact with
322
- a database. Ideally, interfaces will define save() and find() methods, but
323
- these methods and their implementations are up to the individual connector.
324
-
325
- ## View
326
- Views are basically templates used to render an html response for a browser.
327
- Nails comes prepackaged with React.js serverside templating, and EJS templates.
328
- If no template engine is specified in the service config, Nails will Default to
329
- EJS. Nails will always attempt to autorender your views unless a response has
330
- already been sent to the client.
331
-
332
- Stay tuned as nails evolves:
333
-
334
- * Server/client redirects
335
- * Custom Request middleware
336
- * Fancy Logging
337
- * Sessions
338
- * Server security
339
-
340
- Enjoy! Feature requests, bug reports, and comments are welcome on github.
341
-
342
- [express_routing_docs]: https://expressjs.com/en/guide/routing.html
343
- [express_request_docs]: https://expressjs.com/en/5x/api.html#req
344
- [mongoose_model_docs]: https://mongoosejs.com/docs/api/model.html
345
- [sequelize_model_docs]: https://sequelize.org/docs/v6/core-concepts/model-basics/
1
+ # Nails-Boilerplate: A Node Webservice Framework
2
+
3
+ This framework is designed to provide a lightweight, configurable MVC backend
4
+ for node developers. With minimal dependencies, Nails offers a greater
5
+ syntactical familiarity than php alongside the creative freedom of well developed
6
+ server framework solutions like Rails and Django.
7
+
8
+ This boilerplate offers the basic necessities to get your MVC site off the ground.
9
+ The modules used in Nails Boilerplate can be easily extended to produce the custom
10
+ functionality to fit your needs, and you are encouraged to do so.
11
+
12
+ ## Install
13
+
14
+ ```
15
+ sudo npm install -g nails-boilerplate
16
+
17
+ nails init <app_name>
18
+ ```
19
+
20
+ This will initialize a barebones app in the directory of the same name. Take a
21
+ look at the self-documented config files and example controller and view before
22
+ getting started. Additional controllers and views will automatically be imported
23
+ into nails. Now just hook the new controllers in with some new routes and you're
24
+ off to a good start.
25
+
26
+ ```
27
+ cd app_name
28
+
29
+ npm install
30
+
31
+ npm start
32
+ ```
33
+
34
+ ## Getting to know your Nails service
35
+
36
+ For your convenience, here is a quick outline of the main components of a nails service.
37
+ Remember: each object comes with an example file to use for reference when building your service.
38
+
39
+ ### Config
40
+ Your configuration files are stored in app_name/config/. There are three default config files:
41
+
42
+ ```
43
+ service.js
44
+ routes.js
45
+ db.js
46
+ ```
47
+
48
+ Each default config file is annotated with comments documenting each field to
49
+ help you tailor your service to your needs.
50
+
51
+ #### service.js
52
+ service.js contains information necessary to run your server. By default, it
53
+ specifies the port and the location of important libraries. To override these
54
+ values in different runtime environments, add a child object.
55
+ ```js
56
+ export default {
57
+ ...
58
+ PORT: 3000,
59
+ PROD: {
60
+ PORT: 80
61
+ }
62
+ }
63
+ ```
64
+
65
+ Nails checks the NODE_ENV environment variable. If a matching child config
66
+ object is present, then those values will override the parent config. In the
67
+ above example, PORT will be overridden to 80 if NODE_ENV is set to PROD.
68
+
69
+ While most of these values don't need to be changed, feel free to add custom
70
+ fields. The resulting config will be available to your service through the nails
71
+ module:
72
+
73
+ ```js
74
+ import nails from 'nails-boilerplate';
75
+
76
+ const service_config = nails.config
77
+ ```
78
+
79
+ If the config contains a custom field,
80
+
81
+ ```js
82
+ export default {
83
+ ...
84
+ PORT: 3000,
85
+ yourCustomField: 'yourCustomValue'
86
+ }
87
+ ```
88
+
89
+ then `service_config.yourCustomField` as defined above will be equal to
90
+ `'yourCustomValue'`.
91
+
92
+ #### routes.js
93
+ *routes.js* is a list defining mappings from a url path to a *Controller* and
94
+ *Action*. Each entry in the list is an array with three elements:
95
+ `[method, path, options]`
96
+
97
+ **method** is a string defining the HTTP request method of the route. Supported
98
+ methods are *GET*, *PUT*, *POST*, *DELETE*, and *ALL*. All is a special case
99
+ which matches all HTTP request methods. Lastly, *WS* routes will handle WebSocket connections.
100
+
101
+ **path** is a string or regular expression which matches the path of the
102
+ incoming request. If *path* is a string, then the request must match exactly*.
103
+ You can use route parameters to dynamically match parts of the path and assign
104
+ them to the *params* object. For example, if you define a route with the path:
105
+ `'/users/:userId'`
106
+ and your service receives a request with the path:
107
+ `'/users/777'`
108
+ then *userId* will be set in the params object:
109
+ ```js
110
+ { userId: 777 }
111
+ ```
112
+
113
+ You can define *:controller* and *:action* as route parameters as well. Not only
114
+ will those values be set in the params object, but the request will be routed
115
+ to the matching controller and action. See
116
+ [express routes][express_routing_docs] for more information on how route
117
+ parameters work.
118
+
119
+ \*requests for static assets will only match the prefix of the path.
120
+
121
+ **options** is a JSON object which modifies how the request will be routed:
122
+ * *controller* a String indicating the controller to route to.
123
+ * *action* a String indicating the action to route to.
124
+ * *json* a boolean indicating whether to render a JSON response rather than an
125
+ HTML response. If true, nails will not attempt to render a view for this
126
+ route. Instead, your service will respond with JSON for this route.
127
+ * *public* a boolean indicating whether this route is for static assets. If
128
+ true, the router will only attempt to match the prefix of the request path.
129
+ The child portion of the path will be forwarded to the *public/* folder in
130
+ your service directory. For route:
131
+ `['GET', '/public_url_path', {public: true}]`
132
+ if your service receives a request to:
133
+ `/public_url_path/js/index.js`
134
+ then the response will be the file:
135
+ `your_service_root_path/public/js/index.js`
136
+ * *0, 1, 2...* a string which gives regex captures named keys in the params
137
+ object. This will give your regex captures more meaningful named keys rather
138
+ than indices. You can name your regex captures "controller" and/or "action"
139
+ to dynamically route your request to the appropriate handler.
140
+
141
+ #### db.js
142
+
143
+ Quickly configure your database connection here. Nails comes pre-configured to
144
+ use the sequelize connector, giving your models sequelize support. The initial setup
145
+ uses a *sqlite3* database file `config/development.db` and an in-memory database in the test environment. Change the address to change the location and
146
+ version of your desired sql database. Check out [Sequelize](https://sequelize.org)
147
+ for more info.
148
+
149
+ Alternatively, you can configure a connection to MongoDB using the mongoose_connector.js.
150
+ If enabled, models will accept [Mongoose](https://mongoosejs.com/docs/) schemas and will
151
+ be backed by the desired MongoDB. Consider using the in-memory DB during development.
152
+
153
+ ## Controller
154
+
155
+ Controllers are defined in app/controllers/. Each controller module should
156
+ define a Controller subclass. The name will be used to match routes defined in
157
+ config/routes.js for incoming requests. Methods on the controller can be used as
158
+ actions, receiving **params**, **request**, and **response** as arguments.
159
+
160
+ For Example:
161
+ ``` js
162
+ // const Controller = requre("nails-boilerplate").Controller
163
+ import nails from 'nails-boilerplate';
164
+
165
+ class HomeController extends nails.Controller {
166
+ index(params, request, response) {
167
+ // default action
168
+ }
169
+
170
+ signin(params, request, response) {
171
+ // does something then renders a view
172
+ }
173
+ }
174
+ export default HomeController;
175
+
176
+ function helperMethod() {
177
+ // does something but does not have access to response
178
+ }
179
+ ```
180
+
181
+ defines a controller which will match any route to *home#\<action\>*. **index**
182
+ and **signin** are actions which can be used to render a response to the client.
183
+
184
+ ### Local Routes
185
+
186
+ You can define a local routing table directly in the controller.
187
+ Local routes take precidence over global routes. All local routes
188
+ are prefixed with the controller name unless they start with '/'.
189
+ For example, in HomeController the following route:
190
+
191
+ `["get", "data", {action: 'getData', json: true}],`
192
+
193
+ will accept GET requests to /home/data and respond with the json
194
+ object returned by the getData function. If the route is changed to:
195
+
196
+ `["get", "/data", {action: 'getData', json: true}],`
197
+
198
+ it will accept GET requests to /data instead. All local routes are
199
+ implicitly routed to their respective parent controllers.
200
+
201
+ ```js
202
+ export default class UsersController extends nails.Controller {
203
+ routes = [
204
+ // Routes requests to /absolute/path
205
+ ['get', '/absolute/path', {action: 'actionA'}],
206
+ // Routes requests to /users/relative/path
207
+ ['get', './relative/path', {action: 'actionB'}],
208
+ // Routes requests to /users/relative/path
209
+ ['get', 'relative/path', {action: 'actionB'}],
210
+ // If no action is provided, the last path segment
211
+ // is used as the action name.
212
+ ['get', 'actionC']
213
+ ]
214
+
215
+ // Handles requests to /absolute/path
216
+ actionA(request, response, params) {}
217
+
218
+ // Handles requests to /users/relative/path
219
+ actionB(request, response, params) {}
220
+
221
+ // Handles requests to /users/actionC
222
+ actionC(request, response, params) {}
223
+ }
224
+ ```
225
+
226
+ ### Actions
227
+ Actions are used to define how nails should respond to an incoming request.
228
+ If no action has been defined for a route, nails will default to the index
229
+ action.*
230
+
231
+ For example, HomeController#index will attempt to render the view defined in
232
+ //app/views/home/index.jsx
233
+
234
+ The view each action searches for will always follow the pattern:
235
+ //app/views/*\[controller name\]*/*\[action name\]*.jsx
236
+
237
+ The file extension may differ based on which template engine you configure.
238
+
239
+ Depending on the return value, Nails will pass a different set of parameters to
240
+ the view engine:
241
+ * **undefined** If there is no return statement in the action, Nails will pass
242
+ the *params* obect to the rendering engine.
243
+ * **Object** If a generic object is returned, Nails will attempt to autorender
244
+ the view immediately using the returned object instead of *params*.**
245
+ * **Promise** If a promise is returned, Nails will wait to autorender the view
246
+ until the *Promise* resolves. If it resolves with no return value, the view
247
+ is rendered using *params*. Otherwise, the view is rendered using the
248
+ resolved value of the *Promise*\**
249
+
250
+ \*If a response has already been sent to the client, autorender will be skipped.
251
+ \*\*For JSON routes, the returned object will be rendered as stringified JSON.
252
+
253
+ #### Params
254
+ Params is a generic JSON object which represents the request details. Usually,
255
+ Params will correspond to the query portion of your request.
256
+
257
+ For example, a GET request to *//some/path?item0=a&item1=b* will generate the
258
+ params object:
259
+ ``` js
260
+ {
261
+ item0: "a",
262
+ item1: "b"
263
+ }
264
+ ```
265
+
266
+ #### Request
267
+ An [express Request object][express_request_docs].
268
+
269
+ #### Response
270
+ The response object provided by *express.js*. The *#render()* method has been
271
+ overridden to allow for the rendering of views by name.
272
+
273
+ #### JSON Actions
274
+ JSON actions only return JSON objects in the response instead of text or HTML. These actions are ideal for building an API Server. There are three ways to designate JSON Actions:
275
+
276
+ ##### Express Response Object
277
+ ```js
278
+ response.json({your: 'jsonresponse'})
279
+ ```
280
+ Simply use the express Response object directly
281
+
282
+ ##### JSON Routes
283
+ You can configure an individual route to respond with JSON by setting the `json` option to `true`.
284
+ ```js
285
+ ['get', '/your/json/route', {json: true}],
286
+ ```
287
+
288
+ ##### API Controllers
289
+ By setting `json` to `true`, all actions in a controller will respond with JSON.
290
+
291
+ ```js
292
+ class YourApiController extends nails.Controller {
293
+ json = true;
294
+
295
+ action(params, request, response) {
296
+ return {your: 'jsonresponse'};
297
+ }
298
+ }
299
+ ```
300
+
301
+ ## Model
302
+
303
+ Models are programmatic representations of data you wish to persist in a
304
+ database. The constructor for Model accepts two arguments: the `modelName` and an
305
+ `options` object which is passed to the database connector module.
306
+
307
+ ### Sequelize Models
308
+
309
+ Sequelize models are subclasses of
310
+ [Sequelize Models][sequelize_model_docs], and come with the `count()`, `findAll()`,
311
+ and `create()` methods, to name a few. You can define your own models by
312
+ extending an instance of the `Model` class provided by Nails:
313
+
314
+ ```js
315
+ // const Model = require("nails-boilerplate").Model;
316
+ import nails from 'nails-boilerplate';
317
+ import {DataTypes} from 'sequelize';
318
+ schema = {
319
+ name: {type: DataTypes.STRING, allowNull: false},
320
+ email: {type: DataTypes.STRING, allowNull: false}
321
+ };
322
+
323
+ options = {
324
+ indexes: [
325
+ {
326
+ unique: true,
327
+ fields: ['email'],
328
+ },
329
+ ],
330
+ };
331
+
332
+ export default class User extends new Model("User", {schema, options}) {
333
+ someHelperMethod() {
334
+ // This method will be available on all instances of User and is
335
+ // an ideal way to simplify data manipulation.
336
+ }
337
+ };
338
+
339
+ ```
340
+
341
+ ### Mongoose Models
342
+ Mongoose models are subclasses of
343
+ [Mongoose Models][mongoose_model_docs], and come with the `save()`, `find()`,
344
+ and `where()` methods, to name a few. You can define your own models by
345
+ extending an instance of the `Model` class provided by Nails:
346
+
347
+ ``` js
348
+ // const Model = require("nails-boilerplate").Model;
349
+ import nails from 'nails-boilerplate';
350
+ const userSchema = {name: String, email: String};
351
+ export default class User extends new Model("User", {schema: userSchema}) {
352
+ // Define your helper methods here
353
+ };
354
+ ```
355
+
356
+ The `schema` option for Mongoose Models accepts a schema field that is used
357
+ to define how documents are stored in MongoDB.
358
+
359
+ ### Model Library
360
+ Nails will store all instantialized models in a single object called `MODELS`. By accessing these models via the library, you can avoid circular dependencies and ensure all models have been fully initialized.
361
+
362
+ ```js
363
+ class User extends nails.Model("User", {schema, options}) {
364
+ // A helper method which depends on anoher model using the
365
+ // Nails Model Library rather than directly importing the model.
366
+ async findFriends() {
367
+ return nails.MODELS.Friend.findByUserId(this.id);
368
+ }
369
+ }
370
+ ```
371
+ This design pattern is not always necessary, but will help avoid circular dependencies.
372
+
373
+ ### Database Connectors
374
+
375
+ Database connectors are intermediaries which define how a Model interacts with
376
+ a database. Database connector modules need to export two methods:
377
+ * _connect(db_config)_ uses the db config defined in *db.js* to connect to
378
+ a database. This function will be called once by Nails.
379
+ * _generateModelSuperclass(name, options)_ uses the provided Model name and
380
+ options to generate a Model prototype for use as an interface. A Model
381
+ interface is generated for each of your models, allowing them to interact with
382
+ a database. Ideally, interfaces will define save() and find() methods, but
383
+ these methods and their implementations are up to the individual connector.
384
+
385
+ ## View
386
+ Views are dynamic templates used to render an html response for a browser.
387
+ Nails comes prepackaged with EJS templates.
388
+ If no template engine is specified in the service config, Nails will default to
389
+ EJS. Nails will always attempt to autorender your views unless a response has
390
+ already been sent to the client.
391
+
392
+ ### React Frontend with Vite
393
+
394
+ This project uses Vite to build the frontend React application. The source files for the React app are located in the `src` directory. The server is pre-configured to serve the built React application.
395
+
396
+ To rebuild the frontend application, you can run the following command:
397
+
398
+ ```bash
399
+ npm run build
400
+ ```
401
+
402
+ ## Testing
403
+
404
+ This project uses [Vitest](https://vitest.dev/) for running tests. All test files are located in the `spec` folder. To run the tests, use the following command:
405
+
406
+ ```bash
407
+ npm test
408
+ ```
409
+
410
+ For more information on how to write tests with Vitest, please refer to the [official documentation](https://vitest.dev/guide/).
411
+
412
+ Stay tuned as nails evolves:
413
+
414
+ * Server/client redirects
415
+ * Custom Request middleware
416
+ * Fancy Logging
417
+ * Sessions
418
+ * Server security
419
+
420
+ Enjoy! Feature requests, bug reports, and comments are welcome on github.
421
+
422
+ [express_routing_docs]: https://expressjs.com/en/guide/routing.html
423
+ [express_request_docs]: https://expressjs.com/en/5x/api.html#req
424
+ [mongoose_model_docs]: https://mongoosejs.com/docs/api/model.html
425
+ [sequelize_model_docs]: https://sequelize.org/docs/v6/core-concepts/model-basics/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nails-boilerplate",
3
- "version": "2.0.10",
3
+ "version": "2.0.11-0",
4
4
  "description": "A node.js webserver scaffold",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -13,4 +13,4 @@ SCRIPT_PATH=$(readlink -f "$0")
13
13
  SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
14
14
  echo "Script directory (absolute, resolved): $SCRIPT_DIR"
15
15
 
16
- npm --prefix=$SCRIPT_DIR/.. run $COMMAND
16
+ npm --prefix=$SCRIPT_DIR/.. run $COMMAND -- $2 $3 $4 $5 $6 $7 $8 $9
@@ -1,12 +1,7 @@
1
- export default {
2
- /**
3
- * For the sqlite3 connector. Unless a filename is defined, an in-memory
4
- * database is used. In-memory databases are not persisted, and will be lost
5
- * when the server is restarted.
6
- */
7
- // connector: 'sqlite3_connector.js',
8
- // filename: ':memory:'
1
+ let DB_ADDRESS = process.env.NAILS_SQLITE_DB_FILE;
2
+ if (!DB_ADDRESS) DB_ADDRESS = 'sqlite://' + import.meta.dirname + '/development.db';
9
3
 
4
+ export default {
10
5
  /** Mongoose Connector */
11
6
  // connector: 'mongoose_connector.js',
12
7
  // url: 'mongodb://localhost',
@@ -18,5 +13,7 @@ export default {
18
13
 
19
14
  /** Sequelize Connector */
20
15
  connector: 'sequelize_connector.js',
21
- address: 'sqlite::memory:',
16
+ address: process.env.NAILS_RELEASE_STAGE == 'test'
17
+ ? 'sqlite::memory:'
18
+ : DB_ADDRESS,
22
19
  }
@@ -20,7 +20,7 @@ off to a good start.</p>
20
20
 
21
21
  npm install
22
22
 
23
- node server
23
+ npm start
24
24
  </code></pre>
25
25
  <h2 id="gettingtoknowyournailsservice">Getting to know your Nails service</h2>
26
26
  <p>For your convenience, here is a quick outline of the main components of a nails service.
@@ -51,7 +51,9 @@ above example, PORT will be overridden to 80 if NODE</em>ENV is set to PROD.</p>
51
51
  <p>While most of these values don't need to be changed, feel free to add custom
52
52
  fields. The resulting config will be available to your service through the nails
53
53
  module:</p>
54
- <pre><code class="js language-js">var service_config = require('nails-boilerplate').config
54
+ <pre><code class="js language-js">import nails from 'nails-boilerplate';
55
+
56
+ const service_config = nails.config
55
57
  </code></pre>
56
58
  <p>If the config contains a custom field,</p>
57
59
  <pre><code class="js language-js">export default {
@@ -68,7 +70,7 @@ module:</p>
68
70
  <code>[method, path, options]</code></p>
69
71
  <p><strong>method</strong> is a string defining the HTTP request method of the route. Supported
70
72
  methods are <em>GET</em>, <em>PUT</em>, <em>POST</em>, <em>DELETE</em>, and <em>ALL</em>. All is a special case
71
- which matches all HTTP request methods.</p>
73
+ which matches all HTTP request methods. Lastly, <em>WS</em> routes will handle WebSocket connections.</p>
72
74
  <p><strong>path</strong> is a string or regular expression which matches the path of the
73
75
  incoming request. If <em>path</em> is a string, then the request must match exactly*.
74
76
  You can use route parameters to dynamically match parts of the path and assign
@@ -109,7 +111,7 @@ to dynamically route your request to the appropriate handler.</li>
109
111
  <h4 id="dbjs">db.js</h4>
110
112
  <p>Quickly configure your database connection here. Nails comes pre-configured to
111
113
  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
114
+ uses a <em>sqlite3</em> database file <code>config/development.db</code> and an in-memory database in the test environment. Change the address to change the location and
113
115
  version of your desired sql database. Check out <a href="https://sequelize.org">Sequelize</a>
114
116
  for more info.</p>
115
117
  <p>Alternatively, you can configure a connection to MongoDB using the mongoose_connector.js.
@@ -160,6 +162,9 @@ implicitly routed to their respective parent controllers.</p>
160
162
  ['get', './relative/path', {action: 'actionB'}],
161
163
  // Routes requests to /users/relative/path
162
164
  ['get', 'relative/path', {action: 'actionB'}],
165
+ // If no action is provided, the last path segment
166
+ // is used as the action name.
167
+ ['get', 'actionC']
163
168
  ]
164
169
 
165
170
  // Handles requests to /absolute/path
@@ -167,6 +172,9 @@ implicitly routed to their respective parent controllers.</p>
167
172
 
168
173
  // Handles requests to /users/relative/path
169
174
  actionB(request, response, params) {}
175
+
176
+ // Handles requests to /users/actionC
177
+ actionC(request, response, params) {}
170
178
  }
171
179
  </code></pre>
172
180
  <h3 id="actions">Actions</h3>
@@ -207,6 +215,26 @@ params object:</p>
207
215
  <h4 id="response">Response</h4>
208
216
  <p>The response object provided by <em>express.js</em>. The <em>#render()</em> method has been
209
217
  overridden to allow for the rendering of views by name.</p>
218
+ <h4 id="jsonactions">JSON Actions</h4>
219
+ <p>JSON actions only return JSON objects in the response instead of text or HTML. These actions are ideal for building an API Server. There are three ways to designate JSON Actions:</p>
220
+ <h5 id="expressresponseobject">Express Response Object</h5>
221
+ <pre><code class="js language-js"> response.json({your: 'jsonresponse'})
222
+ </code></pre>
223
+ <p>Simply use the express Response object directly</p>
224
+ <h5 id="jsonroutes">JSON Routes</h5>
225
+ <p>You can configure an individual route to respond with JSON by setting the <code>json</code> option to <code>true</code>.</p>
226
+ <pre><code class="js language-js">['get', '/your/json/route', {json: true}],
227
+ </code></pre>
228
+ <h5 id="apicontrollers">API Controllers</h5>
229
+ <p>By setting <code>json</code> to <code>true</code>, all actions in a controller will respond with JSON.</p>
230
+ <pre><code class="js language-js">class YourApiController extends nails.Controller {
231
+ json = true;
232
+
233
+ action(params, request, response) {
234
+ return {your: 'jsonresponse'};
235
+ }
236
+ }
237
+ </code></pre>
210
238
  <h2 id="model">Model</h2>
211
239
  <p>Models are programmatic representations of data you wish to persist in a
212
240
  database. The constructor for Model accepts two arguments: the <code>modelName</code> and an
@@ -219,15 +247,25 @@ extending an instance of the <code>Model</code> class provided by Nails:</p>
219
247
  <pre><code class="js language-js">// const Model = require("nails-boilerplate").Model;
220
248
  import nails from 'nails-boilerplate';
221
249
  import {DataTypes} from 'sequelize';
222
- userSchema = {
250
+ schema = {
223
251
  name: {type: DataTypes.STRING, allowNull: false},
224
252
  email: {type: DataTypes.STRING, allowNull: false}
225
253
  };
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
- */
254
+
255
+ options = {
256
+ indexes: [
257
+ {
258
+ unique: true,
259
+ fields: ['email'],
260
+ },
261
+ ],
262
+ };
263
+
264
+ export default class User extends new Model("User", {schema, options}) {
265
+ someHelperMethod() {
266
+ // This method will be available on all instances of User and is
267
+ // an ideal way to simplify data manipulation.
268
+ }
231
269
  };
232
270
  </code></pre>
233
271
  <h3 id="mongoosemodels">Mongoose Models</h3>
@@ -244,6 +282,17 @@ export default class User extends new Model("User", {schema: userSchema}) {
244
282
  </code></pre>
245
283
  <p>The <code>schema</code> option for Mongoose Models accepts a schema field that is used
246
284
  to define how documents are stored in MongoDB.</p>
285
+ <h3 id="modellibrary">Model Library</h3>
286
+ <p>Nails will store all instantialized models in a single object called <code>MODELS</code>. By accessing these models via the library, you can avoid circular dependencies and ensure all models have been fully initialized.</p>
287
+ <pre><code class="js language-js">class User extends nails.Model("User", {schema, options}) {
288
+ // A helper method which depends on anoher model using the
289
+ // Nails Model Library rather than directly importing the model.
290
+ async findFriends() {
291
+ return nails.MODELS.Friend.findByUserId(this.id);
292
+ }
293
+ }
294
+ </code></pre>
295
+ <p>This design pattern is not always necessary, but will help avoid circular dependencies.</p>
247
296
  <h3 id="databaseconnectors">Database Connectors</h3>
248
297
  <p>Database connectors are intermediaries which define how a Model interacts with
249
298
  a database. Database connector modules need to export two methods:</p>
@@ -257,11 +306,21 @@ a database. Ideally, interfaces will define save() and find() methods, but
257
306
  these methods and their implementations are up to the individual connector.</li>
258
307
  </ul>
259
308
  <h2 id="view">View</h2>
260
- <p>Views are basically templates used to render an html response for a browser.
261
- Nails comes prepackaged with React.js serverside templating, and EJS templates.
262
- If no template engine is specified in the service config, Nails will Default to
309
+ <p>Views are dynamic templates used to render an html response for a browser.
310
+ Nails comes prepackaged with EJS templates.
311
+ If no template engine is specified in the service config, Nails will default to
263
312
  EJS. Nails will always attempt to autorender your views unless a response has
264
313
  already been sent to the client.</p>
314
+ <h3 id="reactfrontendwithvite">React Frontend with Vite</h3>
315
+ <p>This project uses Vite to build the frontend React application. The source files for the React app are located in the <code>src</code> directory. The server is pre-configured to serve the built React application.</p>
316
+ <p>To rebuild the frontend application, you can run the following command:</p>
317
+ <pre><code class="bash language-bash">npm run build
318
+ </code></pre>
319
+ <h2 id="testing">Testing</h2>
320
+ <p>This project uses <a href="https://vitest.dev/">Vitest</a> for running tests. All test files are located in the <code>spec</code> folder. To run the tests, use the following command:</p>
321
+ <pre><code class="bash language-bash">npm test
322
+ </code></pre>
323
+ <p>For more information on how to write tests with Vitest, please refer to the <a href="https://vitest.dev/guide/">official documentation</a>.</p>
265
324
  <p>Stay tuned as nails evolves:</p>
266
325
  <ul>
267
326
  <li>Server/client redirects</li>
@@ -5,6 +5,7 @@ import { beforeAll, test, expect } from "vitest";
5
5
  const TEST_USER_EMAIL = "test@test.com";
6
6
  const TEST_USER_NAME = "JohnDoe";
7
7
  beforeAll(async () => {
8
+ // Only initialize the Models.
8
9
  await nails.MODELS.init( service_config );
9
10
  });
10
11
 
@@ -7,6 +7,7 @@ import { chai, beforeAll, test, expect } from "vitest";
7
7
  let express_app;
8
8
 
9
9
  beforeAll(async () => {
10
+ // Initialize the application and start the server
10
11
  (await nails( service_config )).startServer();
11
12
  express_app = nails.application;
12
13
  chai.use(chaiHttp);
@@ -16,7 +16,10 @@ export default defineConfig({
16
16
  test: {
17
17
  globals: true,
18
18
  // environment: 'jsdom',
19
- setupFiles: './spec/setupTests.js',
19
+ // setupFiles: './spec/setupTests.js',
20
+ env: {
21
+ NAILS_RELEASE_STAGE: "test",
22
+ }
20
23
  },
21
24
  build: {
22
25
  lib: {