@projectinvicta/nails 2.0.15

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.
Files changed (164) hide show
  1. package/README.md +419 -0
  2. package/bin/lib/init.js +103 -0
  3. package/bin/lib/nails.c +81 -0
  4. package/bin/lib/nails.js +23 -0
  5. package/bin/lib/test.js +4 -0
  6. package/index.js +6 -0
  7. package/lib/application.js +24 -0
  8. package/lib/collection.js +6 -0
  9. package/lib/controller.js +182 -0
  10. package/lib/database_connector.js +12 -0
  11. package/lib/firebase_connector.js +94 -0
  12. package/lib/model_v2.js +24 -0
  13. package/lib/mongoose_connector.js +49 -0
  14. package/lib/mongoose_mem_connector.js +21 -0
  15. package/lib/nails.js +244 -0
  16. package/lib/router.js +202 -0
  17. package/lib/sequelize_connector.js +31 -0
  18. package/lib/server.js +1 -0
  19. package/package.json +60 -0
  20. package/scripts/install.js +57 -0
  21. package/scripts/uninstall.js +23 -0
  22. package/spec/bin/init.spec.js +0 -0
  23. package/spec/controller.spec.js +105 -0
  24. package/spec/model_v2.spec.js +75 -0
  25. package/spec/mongodb_connector.util.js +30 -0
  26. package/spec/mongoose_connector.util.js +20 -0
  27. package/spec/nails.spec.js +0 -0
  28. package/spec/router.spec.js +101 -0
  29. package/spec/sequelize_connector.spec.js +91 -0
  30. package/spec/sequelize_connector.util.js +18 -0
  31. package/spec/services/integration/README.md +5 -0
  32. package/spec/services/integration/client/css/styles.css +0 -0
  33. package/spec/services/integration/client/download.jpg +0 -0
  34. package/spec/services/integration/client/favicon.ico +0 -0
  35. package/spec/services/integration/client/index.html +9 -0
  36. package/spec/services/integration/client/js/client.js +0 -0
  37. package/spec/services/integration/client/js/components/app.jsx +15 -0
  38. package/spec/services/integration/config/db.js +14 -0
  39. package/spec/services/integration/config/mimes.js +61 -0
  40. package/spec/services/integration/config/routes.js +41 -0
  41. package/spec/services/integration/config/service.js +48 -0
  42. package/spec/services/integration/config/ssl/certificate.pem +22 -0
  43. package/spec/services/integration/config/ssl/csr.csr +17 -0
  44. package/spec/services/integration/config/ssl/key.pem +28 -0
  45. package/spec/services/integration/config/ssl/private_key.pem +30 -0
  46. package/spec/services/integration/config/ssl/public_key.pem +9 -0
  47. package/spec/services/integration/package.json +23 -0
  48. package/spec/services/integration/server/controllers/classbased_controller.js +33 -0
  49. package/spec/services/integration/server/controllers/default_json_controller.js +20 -0
  50. package/spec/services/integration/server/controllers/error_controller.js +27 -0
  51. package/spec/services/integration/server/controllers/home_controller.js +39 -0
  52. package/spec/services/integration/server/controllers/json_controller.js +15 -0
  53. package/spec/services/integration/server/controllers/manualrenderasync_controller.js +14 -0
  54. package/spec/services/integration/server/controllers/mjs_controller.mjs +13 -0
  55. package/spec/services/integration/server/controllers/modeltest_controller.js +18 -0
  56. package/spec/services/integration/server/controllers/websocket_controller.js +13 -0
  57. package/spec/services/integration/server/models/dog.js +6 -0
  58. package/spec/services/integration/server/views/defaultjson/testnojson.ejs +1 -0
  59. package/spec/services/integration/server/views/testreact/testreact.ejs +15 -0
  60. package/spec/services/integration/server.js +9 -0
  61. package/spec/services/integration_sequelize/README.md +5 -0
  62. package/spec/services/integration_sequelize/client/css/styles.css +0 -0
  63. package/spec/services/integration_sequelize/client/download.jpg +0 -0
  64. package/spec/services/integration_sequelize/client/favicon.ico +0 -0
  65. package/spec/services/integration_sequelize/client/index.html +9 -0
  66. package/spec/services/integration_sequelize/client/js/client.js +0 -0
  67. package/spec/services/integration_sequelize/client/js/components/app.jsx +15 -0
  68. package/spec/services/integration_sequelize/config/db.js +4 -0
  69. package/spec/services/integration_sequelize/config/mimes.js +61 -0
  70. package/spec/services/integration_sequelize/config/routes.js +25 -0
  71. package/spec/services/integration_sequelize/config/service.js +48 -0
  72. package/spec/services/integration_sequelize/config/ssl/certificate.pem +22 -0
  73. package/spec/services/integration_sequelize/config/ssl/csr.csr +17 -0
  74. package/spec/services/integration_sequelize/config/ssl/key.pem +28 -0
  75. package/spec/services/integration_sequelize/config/ssl/private_key.pem +30 -0
  76. package/spec/services/integration_sequelize/config/ssl/public_key.pem +9 -0
  77. package/spec/services/integration_sequelize/package.json +23 -0
  78. package/spec/services/integration_sequelize/server/controllers/default_json_controller.js +21 -0
  79. package/spec/services/integration_sequelize/server/controllers/home_controller.js +39 -0
  80. package/spec/services/integration_sequelize/server/models/dog.js +7 -0
  81. package/spec/services/integration_sequelize/server/models/owner.js +10 -0
  82. package/spec/services/integration_sequelize/server/views/defaultjson/testnojson.ejs +1 -0
  83. package/spec/services/integration_sequelize/server/views/testreact/testreact.ejs +15 -0
  84. package/spec/services/integration_sequelize/server.js +9 -0
  85. package/spec/services.integration.spec.js +296 -0
  86. package/spec/services.integration_sequelize.spec.js +60 -0
  87. package/templates/bin/promote.sh +20 -0
  88. package/templates/bin/rollout.sh +74 -0
  89. package/templates/bin/server.js +6 -0
  90. package/templates/bin/start.sh +16 -0
  91. package/templates/common/readme_fetcher.js +4 -0
  92. package/templates/config/db.js +19 -0
  93. package/templates/config/mimes.js +59 -0
  94. package/templates/config/routes.js +38 -0
  95. package/templates/config/service.js +45 -0
  96. package/templates/config/ssl/certificate.pem +22 -0
  97. package/templates/config/ssl/csr.csr +17 -0
  98. package/templates/config/ssl/key.pem +28 -0
  99. package/templates/config/ssl/private_key.pem +30 -0
  100. package/templates/config/ssl/public_key.pem +9 -0
  101. package/templates/default/bin/promote.sh +20 -0
  102. package/templates/default/bin/rollout.sh +74 -0
  103. package/templates/default/bin/server.js +6 -0
  104. package/templates/default/bin/start.sh +16 -0
  105. package/templates/default/common/readme_fetcher.js +4 -0
  106. package/templates/default/config/db.js +19 -0
  107. package/templates/default/config/mimes.js +59 -0
  108. package/templates/default/config/routes.js +38 -0
  109. package/templates/default/config/service.js +45 -0
  110. package/templates/default/config/ssl/certificate.pem +22 -0
  111. package/templates/default/config/ssl/csr.csr +17 -0
  112. package/templates/default/config/ssl/key.pem +28 -0
  113. package/templates/default/config/ssl/private_key.pem +30 -0
  114. package/templates/default/config/ssl/public_key.pem +9 -0
  115. package/templates/default/package-lock.json +9048 -0
  116. package/templates/default/package.json +43 -0
  117. package/templates/default/public/README.xml +332 -0
  118. package/templates/default/public/css/styles.css +17 -0
  119. package/templates/default/public/download.jpg +0 -0
  120. package/templates/default/public/favicon.ico +0 -0
  121. package/templates/default/public/index.html +9 -0
  122. package/templates/default/public/js/client.js +1 -0
  123. package/templates/default/server/controllers/home_controller.js +34 -0
  124. package/templates/default/server/models/User.js +18 -0
  125. package/templates/default/server/views/home/index.ejs +14 -0
  126. package/templates/default/server/views/partials/javascripts.ejs +1 -0
  127. package/templates/default/server/views/partials/reactapp.ejs +1 -0
  128. package/templates/default/server/views/partials/styles.ejs +3 -0
  129. package/templates/default/spec/User.test.js +20 -0
  130. package/templates/default/spec/home_controller.test.js +28 -0
  131. package/templates/default/spec/setupTests.js +0 -0
  132. package/templates/default/src/AboutPage.jsx +9 -0
  133. package/templates/default/src/HomePage.jsx +9 -0
  134. package/templates/default/src/Layout.jsx +78 -0
  135. package/templates/default/src/ReadmePage.jsx +7 -0
  136. package/templates/default/src/app.jsx +29 -0
  137. package/templates/default/src/components/ReadmeLoader.jsx +13 -0
  138. package/templates/default/src/styles/appstyles.css +3 -0
  139. package/templates/default/vite.config.ts +42 -0
  140. package/templates/package-lock.json +9048 -0
  141. package/templates/package.json +43 -0
  142. package/templates/public/README.xml +332 -0
  143. package/templates/public/css/styles.css +17 -0
  144. package/templates/public/download.jpg +0 -0
  145. package/templates/public/favicon.ico +0 -0
  146. package/templates/public/index.html +9 -0
  147. package/templates/public/js/client.js +1 -0
  148. package/templates/server/controllers/home_controller.js +34 -0
  149. package/templates/server/models/User.js +18 -0
  150. package/templates/server/views/home/index.ejs +14 -0
  151. package/templates/server/views/partials/javascripts.ejs +1 -0
  152. package/templates/server/views/partials/reactapp.ejs +1 -0
  153. package/templates/server/views/partials/styles.ejs +3 -0
  154. package/templates/spec/User.test.js +20 -0
  155. package/templates/spec/home_controller.test.js +28 -0
  156. package/templates/spec/setupTests.js +0 -0
  157. package/templates/src/AboutPage.jsx +9 -0
  158. package/templates/src/HomePage.jsx +9 -0
  159. package/templates/src/Layout.jsx +78 -0
  160. package/templates/src/ReadmePage.jsx +7 -0
  161. package/templates/src/app.jsx +29 -0
  162. package/templates/src/components/ReadmeLoader.jsx +13 -0
  163. package/templates/src/styles/appstyles.css +3 -0
  164. package/templates/vite.config.ts +42 -0
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "nails_app",
3
+ "version": "0.0.0",
4
+ "description": "A basic nails application",
5
+ "main": "server.js",
6
+ "type": "module",
7
+ "bin": "./bin/start.sh",
8
+ "scripts": {
9
+ "test": "vitest run",
10
+ "start": "node bin/server.js & vite build",
11
+ "build": "vite build",
12
+ "rollout": "NAME=$npm_package_name ./bin/rollout.sh",
13
+ "promote": "./bin/promote.sh"
14
+ },
15
+ "author": "",
16
+ "license": "BSD",
17
+ "bugs": {
18
+ "url": "https://github.com/stantonwjones/nails-boilerplate/issues"
19
+ },
20
+ "config": {
21
+ "mongodbMemoryServer": {
22
+ "version": "latest"
23
+ }
24
+ },
25
+ "dependencies": {
26
+ "@mui/icons-material": "^7.3.1",
27
+ "@mui/material": "^7.3.1",
28
+ "@vitejs/plugin-legacy": "^6.1.1",
29
+ "@vitejs/plugin-react": "^4.7.0",
30
+ "nails-boilerplate": ">=1.1.0",
31
+ "react": "^19.1.0",
32
+ "react-dom": "^19.1.0",
33
+ "react-router-dom": "^7.7.0",
34
+ "sequelize": "^6.37.7",
35
+ "vite": "^6.3.5"
36
+ },
37
+ "devDependencies": {
38
+ "@emotion/react": "^11.14.0",
39
+ "@emotion/styled": "^11.14.1",
40
+ "@mui/types": "^7.4.5",
41
+ "vitest": "^3.2.4"
42
+ }
43
+ }
@@ -0,0 +1,332 @@
1
+ <h1 id="nailsboilerplateanodewebserviceframework">Nails-Boilerplate: A Node Webservice Framework</h1>
2
+ <p>This framework is designed to provide a lightweight, configurable MVC backend
3
+ for node developers. With minimal dependencies, Nails offers a greater
4
+ syntactical familiarity than php alongside the creative freedom of well developed
5
+ server framework solutions like Rails and Django.</p>
6
+ <p>This boilerplate offers the basic necessities to get your MVC site off the ground.
7
+ The modules used in Nails Boilerplate can be easily extended to produce the custom
8
+ functionality to fit your needs, and you are encouraged to do so.</p>
9
+ <h2 id="install">Install</h2>
10
+ <pre><code>sudo npm install -g nails-boilerplate
11
+
12
+ nails init &lt;app_name&gt;
13
+ </code></pre>
14
+ <p>This will initialize a barebones app in the directory of the same name. Take a
15
+ look at the self-documented config files and example controller and view before
16
+ getting started. Additional controllers and views will automatically be imported
17
+ into nails. Now just hook the new controllers in with some new routes and you're
18
+ off to a good start.</p>
19
+ <pre><code>cd app_name
20
+
21
+ npm install
22
+
23
+ npm start
24
+ </code></pre>
25
+ <h2 id="gettingtoknowyournailsservice">Getting to know your Nails service</h2>
26
+ <p>For your convenience, here is a quick outline of the main components of a nails service.
27
+ Remember: each object comes with an example file to use for reference when building your service.</p>
28
+ <h3 id="config">Config</h3>
29
+ <p>Your configuration files are stored in app_name/config/. There are three default config files:</p>
30
+ <pre><code>service.js
31
+ routes.js
32
+ db.js
33
+ </code></pre>
34
+ <p>Each default config file is annotated with comments documenting each field to
35
+ help you tailor your service to your needs.</p>
36
+ <h4 id="servicejs">service.js</h4>
37
+ <p>service.js contains information necessary to run your server. By default, it
38
+ specifies the port and the location of important libraries. To override these
39
+ values in different runtime environments, add a child object.</p>
40
+ <pre><code class="js language-js">export default {
41
+ ...
42
+ PORT: 3000,
43
+ PROD: {
44
+ PORT: 80
45
+ }
46
+ }
47
+ </code></pre>
48
+ <p>Nails checks the NODE<em>ENV environment variable. If a matching child config
49
+ object is present, then those values will override the parent config. In the
50
+ above example, PORT will be overridden to 80 if NODE</em>ENV is set to PROD.</p>
51
+ <p>While most of these values don't need to be changed, feel free to add custom
52
+ fields. The resulting config will be available to your service through the nails
53
+ module:</p>
54
+ <pre><code class="js language-js">import nails from 'nails-boilerplate';
55
+
56
+ const service_config = nails.config
57
+ </code></pre>
58
+ <p>If the config contains a custom field,</p>
59
+ <pre><code class="js language-js">export default {
60
+ ...
61
+ PORT: 3000,
62
+ yourCustomField: 'yourCustomValue'
63
+ }
64
+ </code></pre>
65
+ <p>then <code>service_config.yourCustomField</code> as defined above will be equal to
66
+ <code>'yourCustomValue'</code>.</p>
67
+ <h4 id="routesjs">routes.js</h4>
68
+ <p><em>routes.js</em> is a list defining mappings from a url path to a <em>Controller</em> and
69
+ <em>Action</em>. Each entry in the list is an array with three elements:
70
+ <code>[method, path, options]</code></p>
71
+ <p><strong>method</strong> is a string defining the HTTP request method of the route. Supported
72
+ methods are <em>GET</em>, <em>PUT</em>, <em>POST</em>, <em>DELETE</em>, and <em>ALL</em>. All is a special case
73
+ which matches all HTTP request methods. Lastly, <em>WS</em> routes will handle WebSocket connections.</p>
74
+ <p><strong>path</strong> is a string or regular expression which matches the path of the
75
+ incoming request. If <em>path</em> is a string, then the request must match exactly*.
76
+ You can use route parameters to dynamically match parts of the path and assign
77
+ them to the *params* object. For example, if you define a route with the path:
78
+ <code>'/users/:userId'</code>
79
+ and your service receives a request with the path:
80
+ <code>'/users/777'</code>
81
+ then <em>userId</em> will be set in the params object:</p>
82
+ <pre><code class="js language-js">{ userId: 777 }
83
+ </code></pre>
84
+ <p>You can define <em>:controller</em> and <em>:action</em> as route parameters as well. Not only
85
+ will those values be set in the params object, but the request will be routed
86
+ to the matching controller and action. See
87
+ <a href="https://expressjs.com/en/guide/routing.html">express routes</a> for more information on how route
88
+ parameters work.</p>
89
+ <p>*requests for static assets will only match the prefix of the path.</p>
90
+ <p><strong>options</strong> is a JSON object which modifies how the request will be routed:</p>
91
+ <ul>
92
+ <li><em>controller</em> a String indicating the controller to route to.</li>
93
+ <li><em>action</em> a String indicating the action to route to.</li>
94
+ <li><em>json</em> a boolean indicating whether to render a JSON response rather than an
95
+ HTML response. If true, nails will not attempt to render a view for this
96
+ route. Instead, your service will respond with JSON for this route.</li>
97
+ <li><em>public</em> a boolean indicating whether this route is for static assets. If
98
+ true, the router will only attempt to match the prefix of the request path.
99
+ The child portion of the path will be forwarded to the <em>public/</em> folder in
100
+ your service directory. For route:
101
+ <code>['GET', '/public_url_path', {public: true}]</code>
102
+ if your service receives a request to:
103
+ <code>/public_url_path/js/index.js</code>
104
+ then the response will be the file:
105
+ <code>your_service_root_path/public/js/index.js</code></li>
106
+ <li><em>0, 1, 2…</em> a string which gives regex captures named keys in the params
107
+ object. This will give your regex captures more meaningful named keys rather
108
+ than indices. You can name your regex captures "controller" and/or "action"
109
+ to dynamically route your request to the appropriate handler.</li>
110
+ </ul>
111
+ <h4 id="dbjs">db.js</h4>
112
+ <p>Quickly configure your database connection here. Nails comes pre-configured to
113
+ use the sequelize connector, giving your models sequelize support. The initial setup
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
115
+ version of your desired sql database. Check out <a href="https://sequelize.org">Sequelize</a>
116
+ for more info.</p>
117
+ <p>Alternatively, you can configure a connection to MongoDB using the mongoose_connector.js.
118
+ If enabled, models will accept <a href="https://mongoosejs.com/docs/">Mongoose</a> schemas and will
119
+ be backed by the desired MongoDB. Consider using the in-memory DB during development.</p>
120
+ <h2 id="controller">Controller</h2>
121
+ <p>Controllers are defined in app/controllers/. Each controller module should
122
+ define a Controller subclass. The name will be used to match routes defined in
123
+ config/routes.js for incoming requests. Methods on the controller can be used as
124
+ actions, receiving <strong>params</strong>, <strong>request</strong>, and <strong>response</strong> as arguments.</p>
125
+ <p>For Example:</p>
126
+ <pre><code class="js language-js">// const Controller = requre("nails-boilerplate").Controller
127
+ import nails from 'nails-boilerplate';
128
+
129
+ class HomeController extends nails.Controller {
130
+ index(params, request, response) {
131
+ // default action
132
+ }
133
+
134
+ signin(params, request, response) {
135
+ // does something then renders a view
136
+ }
137
+ }
138
+ export default HomeController;
139
+
140
+ function helperMethod() {
141
+ // does something but does not have access to response
142
+ }
143
+ </code></pre>
144
+ <p>defines a controller which will match any route to <em>home#\<action></em>. <strong>index</strong>
145
+ and <strong>signin</strong> are actions which can be used to render a response to the client.</p>
146
+ <h3 id="localroutes">Local Routes</h3>
147
+ <p>You can define a local routing table directly in the controller.
148
+ Local routes take precidence over global routes. All local routes
149
+ are prefixed with the controller name unless they start with '/'.
150
+ For example, in HomeController the following route:</p>
151
+ <p><code>["get", "data", {action: 'getData', json: true}],</code></p>
152
+ <p>will accept GET requests to /home/data and respond with the json
153
+ object returned by the getData function. If the route is changed to:</p>
154
+ <p><code>["get", "/data", {action: 'getData', json: true}],</code></p>
155
+ <p>it will accept GET requests to /data instead. All local routes are
156
+ implicitly routed to their respective parent controllers.</p>
157
+ <pre><code class="js language-js">export default class UsersController extends nails.Controller {
158
+ routes = [
159
+ // Routes requests to /absolute/path
160
+ ['get', '/absolute/path', {action: 'actionA'}],
161
+ // Routes requests to /users/relative/path
162
+ ['get', './relative/path', {action: 'actionB'}],
163
+ // Routes requests to /users/relative/path
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']
168
+ ]
169
+
170
+ // Handles requests to /absolute/path
171
+ actionA(request, response, params) {}
172
+
173
+ // Handles requests to /users/relative/path
174
+ actionB(request, response, params) {}
175
+
176
+ // Handles requests to /users/actionC
177
+ actionC(request, response, params) {}
178
+ }
179
+ </code></pre>
180
+ <h3 id="actions">Actions</h3>
181
+ <p>Actions are used to define how nails should respond to an incoming request.
182
+ If no action has been defined for a route, nails will default to the index
183
+ action.*</p>
184
+ <p>For example, HomeController#index will attempt to render the view defined in
185
+ //app/views/home/index.jsx</p>
186
+ <p>The view each action searches for will always follow the pattern:
187
+ //app/views/<em>[controller name]</em>/<em>[action name]</em>.jsx</p>
188
+ <p>The file extension may differ based on which template engine you configure.</p>
189
+ <p>Depending on the return value, Nails will pass a different set of parameters to
190
+ the view engine:</p>
191
+ <ul>
192
+ <li><strong>undefined</strong> If there is no return statement in the action, Nails will pass
193
+ the <em>params</em> obect to the rendering engine.</li>
194
+ <li><strong>Object</strong> If a generic object is returned, Nails will attempt to autorender
195
+ the view immediately using the returned object instead of <em>params</em>.**</li>
196
+ <li><strong>Promise</strong> If a promise is returned, Nails will wait to autorender the view
197
+ until the <em>Promise</em> resolves. If it resolves with no return value, the view
198
+ is rendered using <em>params</em>. Otherwise, the view is rendered using the
199
+ resolved value of the <em>Promise</em>**</li>
200
+ </ul>
201
+ <p>*If a response has already been sent to the client, autorender will be skipped.
202
+ **For JSON routes, the returned object will be rendered as stringified JSON.</p>
203
+ <h4 id="params">Params</h4>
204
+ <p>Params is a generic JSON object which represents the request details. Usually,
205
+ Params will correspond to the query portion of your request.</p>
206
+ <p>For example, a GET request to <em>//some/path?item0=a&item1=b</em> will generate the
207
+ params object:</p>
208
+ <pre><code class="js language-js">{
209
+ item0: "a",
210
+ item1: "b"
211
+ }
212
+ </code></pre>
213
+ <h4 id="request">Request</h4>
214
+ <p>An <a href="https://expressjs.com/en/5x/api.html#req">express Request object</a>.</p>
215
+ <h4 id="response">Response</h4>
216
+ <p>The response object provided by <em>express.js</em>. The <em>#render()</em> method has been
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>
238
+ <h2 id="model">Model</h2>
239
+ <p>Models are programmatic representations of data you wish to persist in a
240
+ database. The constructor for Model accepts two arguments: the <code>modelName</code> and an
241
+ <code>options</code> object which is passed to the database connector module.</p>
242
+ <h3 id="sequelizemodels">Sequelize Models</h3>
243
+ <p>Sequelize models are subclasses of
244
+ <a href="https://sequelize.org/docs/v6/core-concepts/model-basics/">Sequelize Models</a>, and come with the <code>count()</code>, <code>findAll()</code>,
245
+ and <code>create()</code> methods, to name a few. You can define your own models by
246
+ extending an instance of the <code>Model</code> class provided by Nails:</p>
247
+ <pre><code class="js language-js">// const Model = require("nails-boilerplate").Model;
248
+ import nails from 'nails-boilerplate';
249
+ import {DataTypes} from 'sequelize';
250
+ schema = {
251
+ name: {type: DataTypes.STRING, allowNull: false},
252
+ email: {type: DataTypes.STRING, allowNull: false}
253
+ };
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
+ }
269
+ };
270
+ </code></pre>
271
+ <h3 id="mongoosemodels">Mongoose Models</h3>
272
+ <p>Mongoose models are subclasses of
273
+ <a href="https://mongoosejs.com/docs/api/model.html">Mongoose Models</a>, and come with the <code>save()</code>, <code>find()</code>,
274
+ and <code>where()</code> methods, to name a few. You can define your own models by
275
+ extending an instance of the <code>Model</code> class provided by Nails:</p>
276
+ <pre><code class="js language-js">// const Model = require("nails-boilerplate").Model;
277
+ import nails from 'nails-boilerplate';
278
+ const userSchema = {name: String, email: String};
279
+ export default class User extends new Model("User", {schema: userSchema}) {
280
+ // Define your helper methods here
281
+ };
282
+ </code></pre>
283
+ <p>The <code>schema</code> option for Mongoose Models accepts a schema field that is used
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>
296
+ <h3 id="databaseconnectors">Database Connectors</h3>
297
+ <p>Database connectors are intermediaries which define how a Model interacts with
298
+ a database. Database connector modules need to export two methods:</p>
299
+ <ul>
300
+ <li><em>connect(db</em>config)_ uses the db config defined in <em>db.js</em> to connect to
301
+ a database. This function will be called once by Nails.</li>
302
+ <li><em>generateModelSuperclass(name, options)</em> uses the provided Model name and
303
+ options to generate a Model prototype for use as an interface. A Model
304
+ interface is generated for each of your models, allowing them to interact with
305
+ a database. Ideally, interfaces will define save() and find() methods, but
306
+ these methods and their implementations are up to the individual connector.</li>
307
+ </ul>
308
+ <h2 id="view">View</h2>
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
312
+ EJS. Nails will always attempt to autorender your views unless a response has
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>
324
+ <p>Stay tuned as nails evolves:</p>
325
+ <ul>
326
+ <li>Server/client redirects</li>
327
+ <li>Custom Request middleware</li>
328
+ <li>Fancy Logging</li>
329
+ <li>Sessions</li>
330
+ <li>Server security</li>
331
+ </ul>
332
+ <p>Enjoy! Feature requests, bug reports, and comments are welcome on github.</p>
@@ -0,0 +1,17 @@
1
+ #AppRoot {
2
+ height: 100vh;
3
+ overflow-y: hidden;
4
+ display: flex;
5
+ flex-direction: column;
6
+ }
7
+
8
+ main, footer {
9
+ flex-direction: column;
10
+ display: flex;
11
+ }
12
+
13
+ main {
14
+ overflow-y: scroll;
15
+ flex-grow: 1;
16
+ padding-top: 1em;
17
+ }
Binary file
Binary file
@@ -0,0 +1,9 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ </head>
5
+ <body>
6
+ <h1>Welcome</h1>
7
+ <div></div>
8
+ </body>
9
+ </html>
@@ -0,0 +1 @@
1
+ console.log("vanila javascript engaged");
@@ -0,0 +1,34 @@
1
+ import nails from "nails-boilerplate";
2
+
3
+ export default class HomeController extends nails.Controller {
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
+ };
@@ -0,0 +1,18 @@
1
+ import nails from "nails-boilerplate";
2
+ import { DataTypes } from 'sequelize';
3
+ const Model = nails.Model;
4
+
5
+ const sequelizeUserSchema = {
6
+ name: DataTypes.STRING,
7
+ verified: DataTypes.BOOLEAN,
8
+ email: DataTypes.STRING
9
+ }
10
+ export default class User extends new Model("User", sequelizeUserSchema) {};
11
+
12
+ /** If using a mongoose connector */
13
+ // const mongooseUserSchema = {
14
+ // name: String,
15
+ // verified: Boolean,
16
+ // email: String
17
+ // };
18
+ // export default class User extends new Model("User", {schema: mongooseUserSchema}) {};
@@ -0,0 +1,14 @@
1
+ <html>
2
+ <head>
3
+ <%- include('../partials/styles'); -%>
4
+ <%- include('../partials/javascripts'); -%>
5
+ <title><%= title; %></title>
6
+ <script language="javascript">
7
+ window.WELCOME_MESSAGE = "<%= welcome_message %>";
8
+ </script>
9
+ </head>
10
+ <body>
11
+ <div id="AppRoot"></div>
12
+ <%- include('../partials/reactapp'); -%>
13
+ </body>
14
+ </html>
@@ -0,0 +1 @@
1
+ <script src="/public/js/client.js"></script>
@@ -0,0 +1 @@
1
+ <script src="/public/dist/nails-react-app.js" type="module"></script>
@@ -0,0 +1,3 @@
1
+ <link rel='shortcut icon' href='/public/favicon.ico' type='image/x-icon' />
2
+ <link rel="stylesheet" href="/public/css/styles.css" />
3
+ <link rel="stylesheet" href="/public/dist/nails-react-app.css" />
@@ -0,0 +1,20 @@
1
+ import nails from "nails-boilerplate";
2
+ import service_config from '../config/service.js';
3
+ import { beforeAll, test, expect } from "vitest";
4
+
5
+ const TEST_USER_EMAIL = "test@test.com";
6
+ const TEST_USER_NAME = "JohnDoe";
7
+ beforeAll(async () => {
8
+ // Only initialize the Models.
9
+ await nails.MODELS.init( service_config );
10
+ });
11
+
12
+ test("Can create a User", async () => {
13
+ const user = await nails.MODELS.User.create({name: TEST_USER_NAME, email: TEST_USER_EMAIL});
14
+
15
+ expect(user.id).toBeDefined();
16
+ const retrievedUser = await nails.MODELS.User.findByPk(user.id);
17
+ expect(retrievedUser.id).toBeDefined();
18
+ expect(retrievedUser.name).toEqual(TEST_USER_NAME);
19
+ expect(retrievedUser.email).toEqual(TEST_USER_EMAIL);
20
+ });
@@ -0,0 +1,28 @@
1
+ import nails from 'nails-boilerplate';
2
+ // import chai from 'chai';
3
+ import {default as chaiHttp, request} from 'chai-http';
4
+ import service_config from '../config/service.js';
5
+ import { chai, beforeAll, test, expect } from "vitest";
6
+
7
+ let express_app;
8
+
9
+ beforeAll(async () => {
10
+ // Initialize the application and start the server
11
+ (await nails( service_config )).startServer();
12
+ express_app = nails.application;
13
+ chai.use(chaiHttp);
14
+ chai.should();
15
+ })
16
+
17
+
18
+ test("GET / renders the home page", async () => {
19
+ return await new Promise((resolve, reject) => {
20
+ request.execute(express_app)
21
+ .get('/')
22
+ .end((err, res) => {
23
+ res.should.have.status(200);
24
+ expect(res.text).toContain("Welcome to Nails");
25
+ resolve();
26
+ });
27
+ });
28
+ });
File without changes
@@ -0,0 +1,9 @@
1
+
2
+
3
+ export default function About() {
4
+ return (<p>
5
+ This framework is designed to provide a lightweight, configurable MVC backend for node developers. With minimal dependencies, Nails offers a greater syntactical familiarity than php alongside the creative freedom of bleeding edge solutions like Rails and Django.
6
+
7
+ This boilerplate offers the basic necessities to get your MVC site off the ground. The modules used in Nails Boilerplate can be easily extended to produce the custom functionality to fit your needs, and you are encouraged to do so.
8
+ </p>)
9
+ }
@@ -0,0 +1,9 @@
1
+ export default function Home() {
2
+ return (
3
+ <div>
4
+ <h2>
5
+ {window.WELCOME_MESSAGE}
6
+ </h2>
7
+ </div>
8
+ );
9
+ }
@@ -0,0 +1,78 @@
1
+ import { Link, Outlet, useNavigate } from 'react-router-dom';
2
+
3
+ import AppBar from '@mui/material/AppBar';
4
+ import Toolbar from '@mui/material/Toolbar';
5
+ import Stack from '@mui/material/Stack';
6
+ import Typography from '@mui/material/Typography';
7
+ import CssBaseline from '@mui/material/CssBaseline';
8
+ import Container from "@mui/material/Container";
9
+ import BottomNavigation from "@mui/material/BottomNavigation";
10
+ import BottomNavigationAction from "@mui/material/BottomNavigationAction";
11
+
12
+ import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined';
13
+ import InfoIcon from '@mui/icons-material/Info';
14
+ import MenuBookIcon from '@mui/icons-material/MenuBook';
15
+ import IconButton from "@mui/material/IconButton";
16
+ import LogoutIcon from '@mui/icons-material/Logout';
17
+
18
+ export default function Layout() {
19
+
20
+ let navigate = useNavigate();
21
+ return (
22
+ <>
23
+ <CssBaseline />
24
+ <AppBar position="static">
25
+ <Toolbar>
26
+ <Typography variant="h5"
27
+ component="div"
28
+ sx={{
29
+ mr: 2,
30
+ display: 'flex',
31
+ fontFamily: 'monospace',
32
+ fontWeight: 700,
33
+ letterSpacing: '.1rem',
34
+ color: 'inherit',
35
+ textDecoration: 'none',
36
+ }}>
37
+ Nails App
38
+ </Typography>
39
+ <Stack spacing={2} direction="row"
40
+ sx={{
41
+ display: 'flex',
42
+ flexGrow: 1,
43
+ alignItems: "center",
44
+ justifyContent: "end",
45
+ }}>
46
+ <Typography variant="subtitle1" component="div" >
47
+ User
48
+ </Typography>
49
+ <IconButton onClick={signOut}>
50
+ <LogoutIcon />
51
+ </IconButton>
52
+ </Stack>
53
+ </Toolbar>
54
+ </AppBar>
55
+ <main>
56
+ <Container>
57
+ <Outlet />
58
+ </Container>
59
+ </main>
60
+ <footer>
61
+ <BottomNavigation
62
+ showLabels
63
+ onChange={(event, newValue) => {
64
+ navigate(`/${newValue}`);
65
+ }}
66
+ >
67
+ <BottomNavigationAction value="" label="Home" icon={<HomeOutlinedIcon />} />
68
+ <BottomNavigationAction value="readme" label="ReadMe" icon={<MenuBookIcon />} />
69
+ <BottomNavigationAction value="about" label="About Nails" icon={<InfoIcon />} />
70
+ </BottomNavigation>
71
+ </footer>
72
+ </>
73
+ );
74
+ }
75
+
76
+ function signOut() {
77
+ alert("Signing out");
78
+ }
@@ -0,0 +1,7 @@
1
+ import ReadmeLoader from "./components/ReadmeLoader";
2
+
3
+ export default function ReadmePage() {
4
+ return (
5
+ <ReadmeLoader />
6
+ )
7
+ }