nails-boilerplate 2.0.10 → 2.0.13

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 (51) hide show
  1. package/README.md +419 -345
  2. package/lib/database_connector.js +1 -0
  3. package/lib/model_v2.js +10 -0
  4. package/lib/nails.js +7 -1
  5. package/lib/sequelize_connector.js +4 -2
  6. package/package.json +6 -6
  7. package/templates/bin/start.sh +1 -1
  8. package/templates/config/db.js +6 -9
  9. package/templates/default/bin/promote.sh +20 -0
  10. package/templates/default/bin/rollout.sh +74 -0
  11. package/templates/default/bin/server.js +6 -0
  12. package/templates/default/bin/start.sh +16 -0
  13. package/templates/default/common/readme_fetcher.js +4 -0
  14. package/templates/default/config/db.js +19 -0
  15. package/templates/default/config/mimes.js +59 -0
  16. package/templates/default/config/routes.js +38 -0
  17. package/templates/default/config/service.js +45 -0
  18. package/templates/default/config/ssl/certificate.pem +22 -0
  19. package/templates/default/config/ssl/csr.csr +17 -0
  20. package/templates/default/config/ssl/key.pem +28 -0
  21. package/templates/default/config/ssl/private_key.pem +30 -0
  22. package/templates/default/config/ssl/public_key.pem +9 -0
  23. package/templates/default/package-lock.json +9048 -0
  24. package/templates/default/package.json +43 -0
  25. package/templates/default/public/README.xml +332 -0
  26. package/templates/default/public/css/styles.css +17 -0
  27. package/templates/default/public/download.jpg +0 -0
  28. package/templates/default/public/favicon.ico +0 -0
  29. package/templates/default/public/index.html +9 -0
  30. package/templates/default/public/js/client.js +1 -0
  31. package/templates/default/server/controllers/home_controller.js +34 -0
  32. package/templates/default/server/models/User.js +18 -0
  33. package/templates/default/server/views/home/index.ejs +14 -0
  34. package/templates/default/server/views/partials/javascripts.ejs +1 -0
  35. package/templates/default/server/views/partials/reactapp.ejs +1 -0
  36. package/templates/default/server/views/partials/styles.ejs +3 -0
  37. package/templates/default/spec/User.test.js +20 -0
  38. package/templates/default/spec/home_controller.test.js +28 -0
  39. package/templates/default/spec/setupTests.js +0 -0
  40. package/templates/default/src/AboutPage.jsx +9 -0
  41. package/templates/default/src/HomePage.jsx +9 -0
  42. package/templates/default/src/Layout.jsx +78 -0
  43. package/templates/default/src/ReadmePage.jsx +7 -0
  44. package/templates/default/src/app.jsx +29 -0
  45. package/templates/default/src/components/ReadmeLoader.jsx +13 -0
  46. package/templates/default/src/styles/appstyles.css +3 -0
  47. package/templates/default/vite.config.ts +42 -0
  48. package/templates/public/README.xml +72 -13
  49. package/templates/spec/User.test.js +1 -0
  50. package/templates/spec/home_controller.test.js +1 -0
  51. package/templates/vite.config.ts +4 -1
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
4
+
5
+ import Layout from './Layout';
6
+ import Home from './HomePage';
7
+ import About from './AboutPage';
8
+ import Readme from './ReadmePage';
9
+
10
+ import './styles/appstyles.css';
11
+
12
+ function App() {
13
+ console.log("rendering app");
14
+ return (
15
+ <Router>
16
+ <Routes>
17
+ <Route path="/" element={<Layout />}>
18
+ <Route index element={<Home />} />
19
+ <Route path="about" element={<About />} />
20
+ <Route path="readme" element={<Readme />} />
21
+ </Route>
22
+ </Routes>
23
+ </Router>
24
+ );
25
+ }
26
+
27
+ const container = document.getElementById('AppRoot');
28
+ const root = createRoot(container);
29
+ root.render(<App />);
@@ -0,0 +1,13 @@
1
+ import { useEffect, useState } from "react"
2
+ import { fetchReadme } from "@common/readme_fetcher";
3
+
4
+ export default function ReadmeLoader() {
5
+ const [readmeHtmlSnippet, setReadmeHtmlSnippet] = useState(null);
6
+ useEffect(() => {
7
+ fetchReadme().then(setReadmeHtmlSnippet);
8
+ }, []);
9
+ return (
10
+ <div dangerouslySetInnerHTML={{__html: readmeHtmlSnippet}}>
11
+ </div>
12
+ )
13
+ }
@@ -0,0 +1,3 @@
1
+ body {
2
+ background-color: aliceblue;
3
+ }
@@ -0,0 +1,42 @@
1
+ /// <reference types="vitest" />
2
+
3
+ import react from '@vitejs/plugin-react'
4
+ import { defineConfig } from 'vite'
5
+ import path from 'path'
6
+
7
+ // https://vitejs.dev/config/
8
+ export default defineConfig({
9
+ publicDir: false,
10
+ define: {
11
+ 'process.env': {}
12
+ },
13
+ plugins: [
14
+ react(),
15
+ ],
16
+ test: {
17
+ globals: true,
18
+ // environment: 'jsdom',
19
+ // setupFiles: './spec/setupTests.js',
20
+ env: {
21
+ NAILS_RELEASE_STAGE: "test",
22
+ }
23
+ },
24
+ build: {
25
+ lib: {
26
+ entry: './src/app.jsx', // Your library's main entry file
27
+ name: 'NailsReactApp',
28
+ fileName: 'nails-react-app',
29
+ },
30
+ outDir: './public/dist',
31
+ },
32
+ esbuild: {
33
+ minifyIdentifiers: false,
34
+ keepNames: true,
35
+ },
36
+ resolve: {
37
+ alias: {
38
+ '@common': path.resolve(__dirname, './common'), // Alias '@' to the 'common' directory
39
+ '@server': path.resolve(__dirname, './server'), // Alias '@' to the 'server' directory
40
+ },
41
+ },
42
+ })
@@ -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: {