express-hbs 2.5.0 → 3.0.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.
@@ -0,0 +1,73 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - 'package.json'
9
+ workflow_dispatch:
10
+ inputs:
11
+ dry-run:
12
+ description: 'Preview what would be published without actually publishing'
13
+ required: false
14
+ type: boolean
15
+ default: true
16
+
17
+ concurrency:
18
+ group: ${{ github.workflow }}-${{ github.ref }}
19
+
20
+ jobs:
21
+ publish:
22
+ name: Publish to npm
23
+ runs-on: ubuntu-latest
24
+
25
+ permissions:
26
+ # OIDC trusted publishing to npm. Provenance is enabled because this
27
+ # repository is public; public repos can generate a provenance
28
+ # attestation at publish time.
29
+ id-token: write
30
+ contents: read
31
+
32
+ env:
33
+ FORCE_COLOR: 1
34
+ CI: true
35
+ NPM_CONFIG_PROVENANCE: true
36
+ NPM_CONFIG_REGISTRY: 'https://registry.npmjs.org'
37
+
38
+ steps:
39
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
40
+ with:
41
+ persist-credentials: false
42
+
43
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
44
+ with:
45
+ node-version: '24.14.1'
46
+ registry-url: 'https://registry.npmjs.org'
47
+
48
+ - name: Install Corepack
49
+ run: npm install --global corepack@0.34.6
50
+ - name: Enable corepack
51
+ run: corepack enable
52
+
53
+ - name: Install dependencies
54
+ run: pnpm install --frozen-lockfile --prefer-offline
55
+
56
+ - name: Determine whether to publish
57
+ id: pub
58
+ run: |
59
+ name=$(jq -r .name package.json)
60
+ version=$(jq -r .version package.json)
61
+ if [ -n "$(npm view "$name@$version" version 2>/dev/null)" ]; then
62
+ echo "$name@$version is already published; nothing to do"
63
+ echo "publish=false" >> "$GITHUB_OUTPUT"
64
+ else
65
+ echo "queueing $name@$version"
66
+ echo "publish=true" >> "$GITHUB_OUTPUT"
67
+ fi
68
+
69
+ - name: Publish to npm
70
+ if: steps.pub.outputs.publish == 'true'
71
+ # --no-git-checks is required because actions/checkout leaves the repo on
72
+ # a detached HEAD, which pnpm's default publish branch check rejects.
73
+ run: pnpm publish --no-git-checks ${{ (github.event.inputs['dry-run'] == 'true' && '--dry-run') || '' }}
@@ -1,24 +1,48 @@
1
1
  name: Test
2
+
2
3
  on:
3
4
  pull_request:
4
5
  push:
5
6
  branches:
6
7
  - main
7
- - 'renovate/*'
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
14
+ cancel-in-progress: true
8
15
 
9
16
  jobs:
10
17
  build:
11
18
  runs-on: ubuntu-latest
12
- if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))
19
+ timeout-minutes: 20
13
20
  strategy:
21
+ fail-fast: false
14
22
  matrix:
15
- node: [ 14, 16, 18 ]
23
+ node: [20, 22, 24]
16
24
  name: Node ${{ matrix.node }}
17
25
  steps:
18
- - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4
19
- - uses: actions/setup-node@v3
26
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27
+ with:
28
+ persist-credentials: false
29
+ - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
30
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
20
31
  with:
21
32
  node-version: ${{ matrix.node }}
22
- - run: yarn
23
- - run: yarn lint
24
- - run: yarn test
33
+ cache: pnpm
34
+ - run: pnpm install --frozen-lockfile
35
+ - run: pnpm lint
36
+ - name: Run tests with coverage
37
+ run: pnpm coverage
38
+
39
+ required-checks-pass:
40
+ name: Required checks pass
41
+ if: always()
42
+ needs: [build]
43
+ runs-on: ubuntu-latest
44
+ timeout-minutes: 5
45
+ steps:
46
+ - name: Verify all required jobs succeeded
47
+ if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
48
+ run: exit 1
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 20
package/.nycrc.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "all": true,
3
+ "include": [
4
+ "index.js",
5
+ "lib/**/*.js"
6
+ ],
7
+ "branches": 80,
8
+ "functions": 80,
9
+ "lines": 80,
10
+ "statements": 80,
11
+ "reporter": [
12
+ "text",
13
+ "json-summary",
14
+ "lcov"
15
+ ]
16
+ }
package/.oxfmtrc.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
3
+ "singleQuote": true,
4
+ "tabWidth": 2,
5
+ "sortPackageJson": false,
6
+ "ignorePatterns": [
7
+ "**/node_modules/**",
8
+ "**/coverage/**",
9
+ "pnpm-lock.yaml",
10
+ "**/*.json",
11
+ "**/*.jsonc",
12
+ "**/*.yml",
13
+ "**/*.yaml",
14
+ "**/*.md",
15
+ "**/*.hbs",
16
+ "**/*.html",
17
+ "**/*.css"
18
+ ]
19
+ }
package/.oxlintrc.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "categories": {
4
+ "correctness": "error",
5
+ "suspicious": "warn"
6
+ },
7
+ "env": {
8
+ "node": true,
9
+ "es2024": true
10
+ },
11
+ "rules": {
12
+ "no-console": "error"
13
+ },
14
+ "overrides": [
15
+ {
16
+ "files": ["test/**/*.js"],
17
+ "env": {
18
+ "mocha": true
19
+ },
20
+ "rules": {
21
+ "no-console": "off",
22
+ "no-unused-vars": "off"
23
+ }
24
+ },
25
+ {
26
+ "files": ["lib/hbs.js", "example/**/*.js"],
27
+ "rules": {
28
+ "no-console": "off"
29
+ }
30
+ }
31
+ ]
32
+ }
package/AGENTS.md ADDED
@@ -0,0 +1,40 @@
1
+ # express-hbs agent notes
2
+
3
+ `express-hbs` is a CommonJS Express view-engine library. Read `README.md` for
4
+ the public API and usage examples before changing behavior.
5
+
6
+ ## Commands
7
+
8
+ Use pnpm 10 on Node.js 20 or later.
9
+
10
+ ```sh
11
+ pnpm install --frozen-lockfile
12
+ pnpm lint
13
+ pnpm test
14
+ pnpm coverage
15
+ ```
16
+
17
+ `pnpm test` runs `mocha` and then `pnpm lint` through the `posttest` script.
18
+ `pnpm coverage` runs the same test suite under `nyc` and enforces the coverage
19
+ thresholds in `package.json`.
20
+
21
+ ## Boundaries
22
+
23
+ - Do not edit `pnpm-lock.yaml` by hand. Regenerate it with `pnpm install`.
24
+ - Keep `lib/hbs.js` and `lib/resolver.js` CommonJS; this package does not use
25
+ ESM.
26
+ - Treat files under `test/views/**`, `test/issues/**`, and `example/views/**`
27
+ as rendering fixtures. Seemingly odd whitespace, malformed markup, or partial
28
+ names may be intentional test coverage.
29
+ - Keep the security warning around user-controlled `layout` values and
30
+ `restrictLayoutsTo` in sync with behavior.
31
+
32
+ ## Style notes
33
+
34
+ - Formatting is enforced by `oxfmt`; run `pnpm lint:fix` rather than doing large
35
+ manual formatting sweeps.
36
+ - The formatter intentionally ignores Handlebars, HTML, CSS, lockfile, and
37
+ fixture data files. Preserve their existing shape unless the test expectation
38
+ itself is being changed.
39
+ - Add or update tests for layout resolution, partial loading, async helpers,
40
+ template options, and caching changes.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2012-2023 Barc, Inc., Ghost Foundation
3
+ Copyright (c) 2012-2025 Barc, Inc., Ghost Foundation
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
package/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # express-hbs
2
2
 
3
- Express handlebars template engine with multiple layouts, blocks and cached partials.
3
+ Express Handlebars template engine for Express apps, with nested layouts,
4
+ named content blocks, cached partials, i18n helpers, and asynchronous helpers.
5
+
6
+ `express-hbs` exposes Express-compatible view engine functions and a shared
7
+ Handlebars instance. It is used by apps that want Handlebars templates with
8
+ layout inheritance and block-style content regions while keeping the familiar
9
+ `app.engine()` integration.
10
+
11
+ ## Requirements
12
+
13
+ - Node.js 20 or later
14
+ - pnpm 10 when working on this repository
4
15
 
5
16
  ## v2.0.0
6
17
 
@@ -17,7 +28,8 @@ If you're upgrading from v0.8.4 to v1.0.0 there are some potentially breaking ch
17
28
 
18
29
  ## Usage
19
30
 
20
- To use with express 4.
31
+ Register the engine with Express using `hbs.express4()`.
32
+
21
33
  ```js
22
34
  var hbs = require('express-hbs');
23
35
 
@@ -28,7 +40,9 @@ app.engine('hbs', hbs.express4({
28
40
  app.set('view engine', 'hbs');
29
41
  app.set('views', __dirname + '/views');
30
42
  ```
31
- To use with express 3 is the same as above, except use hbs.express3
43
+
44
+ `hbs.express3()` is still available for legacy apps and accepts the same
45
+ options:
32
46
 
33
47
  ```js
34
48
  app.engine('hbs', hbs.express3({
@@ -36,7 +50,7 @@ app.engine('hbs', hbs.express3({
36
50
  }));
37
51
  ```
38
52
 
39
- Options for `#express3` and `#express4`
53
+ Options for `express3()` and `express4()`:
40
54
 
41
55
  ```js
42
56
  hbs.express4({
@@ -67,15 +81,15 @@ hbs.express4({
67
81
 
68
82
  ## Syntax
69
83
 
70
- To mark where layout should insert page
84
+ To mark where a layout should insert page content:
71
85
 
72
86
  {{{body}}}
73
87
 
74
- To declare a block placeholder in layout
88
+ To declare a block placeholder in a layout:
75
89
 
76
90
  {{{block "pageScripts"}}}
77
91
 
78
- To define block content in a page
92
+ To define block content in a page:
79
93
 
80
94
  {{#contentFor "pageScripts"}}
81
95
  CONTENT HERE
@@ -83,7 +97,7 @@ To define block content in a page
83
97
 
84
98
  ## Layouts
85
99
 
86
- There are three ways to use a layout, listed in precedence order
100
+ There are three ways to use a layout, listed in precedence order:
87
101
 
88
102
  1. Declarative within a page. Use handlebars comment
89
103
 
@@ -100,9 +114,10 @@ There are three ways to use a layout, listed in precedence order
100
114
 
101
115
  2. As an option to render
102
116
 
103
- ## ⚠️ This creates a potential security vulnerability if used without a `restrictLayoutsTo`:
104
-
105
- The `restrictLayoutsTo` option will restrict reading layouts to a particular directory, if you do not pass this option then do not use the `layout` option in conjunction with passing user submitted data to res.render e.g. `res.render('index', req.query)`. This allows users to read arbitrary files from your filesystem!
117
+ ⚠️ Passing user-controlled data into the `layout` option can read arbitrary
118
+ files unless `restrictLayoutsTo` confines layout resolution to a safe
119
+ directory. Do not call `res.render('index', req.query)` or similar without
120
+ setting `restrictLayoutsTo`.
106
121
 
107
122
  ```js
108
123
  res.render('veggies', {
@@ -112,7 +127,8 @@ There are three ways to use a layout, listed in precedence order
112
127
  });
113
128
  ```
114
129
 
115
- This option also allows for layout suppression (both the default layout and when specified declaratively in a page) by passing in a falsey Javascript value as the value of the `layout` property:
130
+ This option also allows layout suppression, including the default layout and
131
+ template-declared layouts, by passing a falsey JavaScript value:
116
132
 
117
133
  ```js
118
134
  res.render('veggies', {
@@ -215,9 +231,12 @@ var instance2 = hbs.create();
215
231
 
216
232
  ## Template options
217
233
 
218
- The main use case for template options is setting the handlebars "data" object - this creates global template variables accessible with an `@` prefix.
234
+ The main use case for template options is setting the Handlebars `data` object,
235
+ which creates global template variables accessible with an `@` prefix.
219
236
 
220
- Template options can be set in 3 ways. When setting global template options they can be [passed as config on creation of an instance](https://github.com/barc/express-hbs#usage), and they can also be updated used the `updateTemplateOptions(templateOptions)` method of an instance. To set template options for an individual request they can be set on `res.locals` using the helper method `updateLocalTemplateOptions(locals, templateOptions)`.
237
+ Template options can be passed when creating an engine instance, updated with
238
+ `updateTemplateOptions(templateOptions)`, or set for one request with
239
+ `updateLocalTemplateOptions(locals, templateOptions)` on `res.locals`.
221
240
 
222
241
  Both of these methods have a companion method `getTemplateOptions()` and `getLocalTemplateOptions(locals)`, which should be used when extending or merging the current options.
223
242
 
@@ -275,20 +294,29 @@ File `views/index.hbs`
275
294
 
276
295
  To run example project
277
296
 
278
- npm install -d
279
- node example/app.js
297
+ ```sh
298
+ pnpm install
299
+ node example/app.js
300
+ ```
280
301
 
302
+ The example server listens on <http://localhost:3000>.
281
303
 
282
304
  ## Testing
283
305
 
284
- The test suite requires the `grunt-cli` package:
306
+ Install dependencies and run the test suite:
285
307
 
286
- npm install -g grunt-cli
287
- npm install -d
308
+ ```sh
309
+ pnpm install
310
+ pnpm test
311
+ ```
288
312
 
289
- Once everything's installed, just run:
313
+ Useful maintainer commands:
290
314
 
291
- npm test
315
+ ```sh
316
+ pnpm lint # oxlint plus oxfmt --check
317
+ pnpm lint:fix # apply safe lint fixes and formatting
318
+ pnpm coverage # mocha under nyc with enforced coverage thresholds
319
+ ```
292
320
 
293
321
 
294
322
  ## Credits
@@ -302,4 +330,4 @@ Big thanks to all [CONTRIBUTORS](https://github.com/TryGhost/express-hbs/contrib
302
330
 
303
331
  The MIT License (MIT)
304
332
 
305
- Copyright (c) 2012-2023 Barc, Inc., Ghost Foundation - Released under the [MIT license](LICENSE).
333
+ Copyright (c) 2012-2026 Barc, Inc., Ghost Foundation - Released under the [MIT license](LICENSE).
@@ -1,4 +1,4 @@
1
- // npm install express express-hbs
1
+ // pnpm add express express-hbs
2
2
 
3
3
  'use strict';
4
4
 
@@ -16,18 +16,20 @@ var viewsDir = relative('views');
16
16
  app.use(express.static(relative('public')));
17
17
 
18
18
  // Hook in express-hbs and tell it where known directories reside
19
- app.engine('hbs', hbs.express4({
20
- partialsDir: [relative('views/partials'), relative('views/partials-other')],
21
- layoutsDir: relative('views/layout'),
22
- defaultLayout: relative('views/layout/default.hbs'),
23
- restrictLayoutsTo: relative('views/layout')
24
- }));
19
+ app.engine(
20
+ 'hbs',
21
+ hbs.express4({
22
+ partialsDir: [relative('views/partials'), relative('views/partials-other')],
23
+ layoutsDir: relative('views/layout'),
24
+ defaultLayout: relative('views/layout/default.hbs'),
25
+ restrictLayoutsTo: relative('views/layout'),
26
+ }),
27
+ );
25
28
  app.set('view engine', 'hbs');
26
29
  app.set('views', relative('views'));
27
30
 
28
-
29
31
  // Register sync helper
30
- hbs.registerHelper('link', function(text, options) {
32
+ hbs.registerHelper('link', function (text, options) {
31
33
  var attrs = [];
32
34
  for (var prop in options.hash) {
33
35
  attrs.push(prop + '="' + options.hash[prop] + '"');
@@ -36,66 +38,57 @@ hbs.registerHelper('link', function(text, options) {
36
38
  });
37
39
 
38
40
  // Register Async helpers
39
- hbs.registerAsyncHelper('readFile', function(filename, cb) {
40
- fs.readFile(fp.join(viewsDir, filename), 'utf8', function(err, content) {
41
+ hbs.registerAsyncHelper('readFile', function (filename, cb) {
42
+ fs.readFile(fp.join(viewsDir, filename), 'utf8', function (err, content) {
41
43
  if (err) console.error(err);
42
44
  cb(new hbs.SafeString(content));
43
45
  });
44
46
  });
45
47
 
46
- var fruits = [
47
- {name: 'apple'},
48
- {name: 'orange'},
49
- {name: 'pear'}
50
- ];
51
-
48
+ var fruits = [{ name: 'apple' }, { name: 'orange' }, { name: 'pear' }];
52
49
 
53
- var veggies = [
54
- {name: 'asparagus'},
55
- {name: 'carrot'},
56
- {name: 'spinach'}
57
- ];
50
+ var veggies = [{ name: 'asparagus' }, { name: 'carrot' }, { name: 'spinach' }];
58
51
 
59
- app.get('/', function(req, res) {
52
+ app.get('/', function (req, res) {
60
53
  res.render('index', {
61
- title: 'express-hbs example'
54
+ title: 'express-hbs example',
62
55
  });
63
56
  });
64
57
 
65
- app.get('/fruits', function(req, res) {
58
+ app.get('/fruits', function (req, res) {
66
59
  res.render('fruits/index-layoutsDir', {
67
60
  title: 'My favorite fruits',
68
- fruits: fruits
61
+ fruits: fruits,
69
62
  });
70
63
  });
71
64
 
72
- app.get('/fruits/:name', function(req, res) {
73
- res.render('fruits/details-layoutsDir', {
74
- fruit: req.params.name
75
- });
65
+ app.get('/fruits/:name', function (req, res) {
66
+ res.render('fruits/details-layoutsDir', {
67
+ fruit: req.params.name,
68
+ });
76
69
  });
77
70
 
78
- app.get('/veggies', function(req, res) {
71
+ app.get('/veggies', function (req, res) {
79
72
  res.render('veggies', {
80
73
  title: 'My favorite veggies',
81
74
  veggies: veggies,
82
- layout: 'veggie'
75
+ layout: 'veggie',
83
76
  });
84
77
  });
85
78
 
86
- app.get('/veggies/explicit-dir', function(req, res) {
79
+ app.get('/veggies/explicit-dir', function (req, res) {
87
80
  res.render('veggies', {
88
81
  title: 'My favorite veggies',
89
82
  veggies: veggies,
90
- layout: relative('views/layout/veggie')
83
+ layout: relative('views/layout/veggie'),
91
84
  });
92
85
  });
93
86
 
94
- app.get('/veggies/:name', function(req, res) {
95
- res.render('veggies/details', {
96
- veggie: req.params.name,
97
- layout: 'veggie-details'
98
- });
87
+ app.get('/veggies/:name', function (req, res) {
88
+ res.render('veggies/details', {
89
+ veggie: req.params.name,
90
+ layout: 'veggie-details',
91
+ });
99
92
  });
100
93
 
101
94
  if (require.main === module) {
package/example/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- // npm install express express-hbs
2
+ // pnpm add express express-hbs
3
3
 
4
4
  function create(hbs, env) {
5
5
  if (env) process.env.NODE_ENV = env;
@@ -18,82 +18,75 @@ function create(hbs, env) {
18
18
  app.use(express.static(relative('public')));
19
19
 
20
20
  // Hook in express-hbs and tell it where known directories reside
21
- app.engine('hbs', hbs.express4({
22
- partialsDir: [relative('views/partials'), relative('views/partials-other')],
23
- defaultLayout: relative('views/layout/default.hbs'),
24
- restrictLayoutsTo: viewsDir
25
- }));
21
+ app.engine(
22
+ 'hbs',
23
+ hbs.express4({
24
+ partialsDir: [relative('views/partials'), relative('views/partials-other')],
25
+ defaultLayout: relative('views/layout/default.hbs'),
26
+ restrictLayoutsTo: viewsDir,
27
+ }),
28
+ );
26
29
  app.set('view engine', 'hbs');
27
30
  app.set('views', viewsDir);
28
31
 
29
32
  // Register sync helper
30
- hbs.registerHelper('link', function(text, options) {
33
+ hbs.registerHelper('link', function (text, options) {
31
34
  var attrs = [];
32
35
  for (var prop in options.hash) {
33
36
  attrs.push(prop + '="' + options.hash[prop] + '"');
34
37
  }
35
- return new hbs.SafeString(
36
- '<a ' + attrs.join(' ') + '>' + text + '</a>'
37
- );
38
+ return new hbs.SafeString('<a ' + attrs.join(' ') + '>' + text + '</a>');
38
39
  });
39
40
 
40
41
  // Register Async helpers
41
- hbs.registerAsyncHelper('readFile', function(filename, cb) {
42
- fs.readFile(fp.join(viewsDir, filename), 'utf8', function(err, content) {
42
+ hbs.registerAsyncHelper('readFile', function (filename, cb) {
43
+ fs.readFile(fp.join(viewsDir, filename), 'utf8', function (err, content) {
43
44
  if (err) console.error(err);
44
45
  cb(new hbs.SafeString(content));
45
46
  });
46
47
  });
47
48
 
48
- var fruits = [
49
- {name: 'apple'},
50
- {name: 'orange'},
51
- {name: 'pear'}
52
- ];
49
+ var fruits = [{ name: 'apple' }, { name: 'orange' }, { name: 'pear' }];
53
50
 
54
- var veggies = [
55
- {name: 'asparagus'},
56
- {name: 'carrot'},
57
- {name: 'spinach'}
58
- ];
51
+ var veggies = [{ name: 'asparagus' }, { name: 'carrot' }, { name: 'spinach' }];
59
52
 
60
- app.get('/', function(req, res) {
53
+ app.get('/', function (req, res) {
61
54
  res.render('index', {
62
- title: 'express-hbs example'
55
+ title: 'express-hbs example',
63
56
  });
64
57
  });
65
58
 
66
- app.get('/replace', function(req, res) {
59
+ app.get('/replace', function (req, res) {
67
60
  res.render('replace', {
68
- title: 'express-hbs example'
61
+ title: 'express-hbs example',
69
62
  });
70
63
  });
71
64
 
72
- app.get('/fruits', function(req, res) {
65
+ app.get('/fruits', function (req, res) {
73
66
  res.render('fruits/index', {
74
67
  title: 'My favorite fruits',
75
- fruits: fruits
68
+ fruits: fruits,
76
69
  });
77
70
  });
78
71
 
79
- app.get('/fruits/:name', function(req, res) {
72
+ app.get('/fruits/:name', function (req, res) {
80
73
  res.render('fruits/details', {
81
- fruit: req.params.name
74
+ fruit: req.params.name,
82
75
  });
83
76
  });
84
77
 
85
- app.get('/veggies', function(req, res) {
78
+ app.get('/veggies', function (req, res) {
86
79
  res.render('veggies', {
87
80
  title: 'My favorite veggies',
88
81
  veggies: veggies,
89
- layout: 'layout/veggie'
82
+ layout: 'layout/veggie',
90
83
  });
91
84
  });
92
85
 
93
- app.get('/veggies/:name', function(req, res) {
86
+ app.get('/veggies/:name', function (req, res) {
94
87
  res.render('veggies/details', {
95
88
  veggie: req.params.name,
96
- layout: 'layout/veggie-details'
89
+ layout: 'layout/veggie-details',
97
90
  });
98
91
  });
99
92