nails-boilerplate 2.0.9 → 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 +425 -345
- package/lib/sequelize_connector.js +3 -0
- package/package.json +1 -1
- package/spec/sequelize_connector.spec.js +54 -2
- package/templates/bin/start.sh +8 -1
- package/templates/config/db.js +6 -9
- package/templates/public/README.xml +72 -13
- package/templates/spec/User.test.js +1 -0
- package/templates/spec/home_controller.test.js +1 -0
- package/templates/vite.config.ts +4 -1
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
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`'/users
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* *
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Routes requests to /
|
|
205
|
-
['get', '
|
|
206
|
-
// Routes requests to /users/relative/path
|
|
207
|
-
['get', 'relative/path', {action: 'actionB'}],
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
to
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
[
|
|
344
|
-
|
|
345
|
-
|
|
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/
|
|
@@ -16,6 +16,9 @@ export default class SequelizeConnector extends DbConnector {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
generateModelSuperclass(name, options) {
|
|
19
|
+
if (options.schema) {
|
|
20
|
+
return this.sequelize.define(name, options.schema, options.options);
|
|
21
|
+
}
|
|
19
22
|
return this.sequelize.define(name, options);
|
|
20
23
|
}
|
|
21
24
|
|
package/package.json
CHANGED
|
@@ -14,6 +14,22 @@ const TEST_SCHEMA = {
|
|
|
14
14
|
cash: DataTypes.INTEGER,
|
|
15
15
|
}
|
|
16
16
|
let TestSequelizeModel = null;
|
|
17
|
+
let TestSequelizeIndexedModel = null;
|
|
18
|
+
|
|
19
|
+
const TEST_OPTIONS = {
|
|
20
|
+
schema: {
|
|
21
|
+
label: DataTypes.STRING,
|
|
22
|
+
value: DataTypes.STRING,
|
|
23
|
+
},
|
|
24
|
+
options: {
|
|
25
|
+
indexes: [
|
|
26
|
+
{
|
|
27
|
+
unique: true,
|
|
28
|
+
fields: ['label', 'value'],
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
};
|
|
17
33
|
|
|
18
34
|
describe('ModelV2 using SequelizeConnector', function() {
|
|
19
35
|
let util;
|
|
@@ -22,8 +38,9 @@ describe('ModelV2 using SequelizeConnector', function() {
|
|
|
22
38
|
const connector = await util.getTestConnector();
|
|
23
39
|
Model.setConnector(connector);
|
|
24
40
|
TestSequelizeModel =
|
|
25
|
-
class TestSequelizeModel extends new Model("TestSequelizeModel", TEST_SCHEMA) {
|
|
26
|
-
|
|
41
|
+
class TestSequelizeModel extends new Model("TestSequelizeModel", TEST_SCHEMA) {};
|
|
42
|
+
TestSequelizeIndexedModel =
|
|
43
|
+
class TestSequelizeIndexedModel extends new Model("TestSequelizeIndexedModel", TEST_OPTIONS) {};
|
|
27
44
|
await connector.sequelize.sync({ force: true });
|
|
28
45
|
});
|
|
29
46
|
|
|
@@ -36,4 +53,39 @@ describe('ModelV2 using SequelizeConnector', function() {
|
|
|
36
53
|
assert(models[0].name == MODEL_NAME, "Name should be consistent");
|
|
37
54
|
assert(models[0] instanceof TestSequelizeModel);
|
|
38
55
|
});
|
|
56
|
+
|
|
57
|
+
it("Should be able to create, save, and retrieve a complex model", async function() {
|
|
58
|
+
const MODEL_LABEL = "First test model LABEL";
|
|
59
|
+
const MODEL_VALUE = "First test model VALUE";
|
|
60
|
+
const testModel = await TestSequelizeIndexedModel.create({label: MODEL_LABEL, value: MODEL_VALUE});
|
|
61
|
+
const models = await TestSequelizeIndexedModel.findAll();
|
|
62
|
+
assert(models.length == 1, "Should have one model");
|
|
63
|
+
assert(models[0].label == MODEL_LABEL, "label should be persisted");
|
|
64
|
+
assert(models[0].value == MODEL_VALUE, "value should be persisted");
|
|
65
|
+
assert(models[0] instanceof TestSequelizeIndexedModel);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("Should be able to respect unique indexes", async function() {
|
|
69
|
+
const MODEL_LABEL_0 = "MODEL_LABEL_0";
|
|
70
|
+
const MODEL_VALUE_0 = "MODEL_VALUE_0";
|
|
71
|
+
const MODEL_LABEL_1 = "MODEL_LABEL_1";
|
|
72
|
+
const MODEL_VALUE_1 = "MODEL_VALUE_1";
|
|
73
|
+
const testModels = await TestSequelizeIndexedModel.bulkCreate(
|
|
74
|
+
[
|
|
75
|
+
{label: MODEL_LABEL_0, value: MODEL_VALUE_0},
|
|
76
|
+
{label: MODEL_LABEL_0, value: MODEL_VALUE_1},
|
|
77
|
+
{label: MODEL_LABEL_1, value: MODEL_VALUE_0},
|
|
78
|
+
{label: MODEL_LABEL_1, value: MODEL_VALUE_1},
|
|
79
|
+
]
|
|
80
|
+
);
|
|
81
|
+
assert(testModels.length == 4, "Should creaete 4 models");
|
|
82
|
+
let indexWasRespected = true;
|
|
83
|
+
try {
|
|
84
|
+
await TestSequelizeIndexedModel.create({label: MODEL_LABEL_0, value: MODEL_VALUE_0});
|
|
85
|
+
indexWasRespected = false;
|
|
86
|
+
} catch(e) {
|
|
87
|
+
console.log("Error was thrown as expected");
|
|
88
|
+
}
|
|
89
|
+
assert(indexWasRespected, "Should have thrown an error")
|
|
90
|
+
});
|
|
39
91
|
});
|
package/templates/bin/start.sh
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
SCRIPT_DIR=$(dirname "$0")
|
|
2
2
|
echo "Script directory (relative): $SCRIPT_DIR"
|
|
3
3
|
|
|
4
|
+
COMMAND=$1
|
|
5
|
+
if [[ -z "${COMMAND}" ]]; then
|
|
6
|
+
COMMAND=start
|
|
7
|
+
else
|
|
8
|
+
echo "COMMAND is NOT empty."
|
|
9
|
+
fi
|
|
10
|
+
|
|
4
11
|
|
|
5
12
|
SCRIPT_PATH=$(readlink -f "$0")
|
|
6
13
|
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
|
|
7
14
|
echo "Script directory (absolute, resolved): $SCRIPT_DIR"
|
|
8
15
|
|
|
9
|
-
npm --prefix=$SCRIPT_DIR/..
|
|
16
|
+
npm --prefix=$SCRIPT_DIR/.. run $COMMAND -- $2 $3 $4 $5 $6 $7 $8 $9
|
package/templates/config/db.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
|
|
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: '
|
|
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
|
-
|
|
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">
|
|
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
|
|
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
|
-
|
|
250
|
+
schema = {
|
|
223
251
|
name: {type: DataTypes.STRING, allowNull: false},
|
|
224
252
|
email: {type: DataTypes.STRING, allowNull: false}
|
|
225
253
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
261
|
-
Nails comes prepackaged with
|
|
262
|
-
If no template engine is specified in the service config, Nails will
|
|
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>
|
package/templates/vite.config.ts
CHANGED