{"id":13524110,"url":"https://github.com/fastify/point-of-view","last_synced_at":"2025-05-14T07:10:36.963Z","repository":{"id":19342683,"uuid":"86670089","full_name":"fastify/point-of-view","owner":"fastify","description":"Template rendering plugin for Fastify","archived":false,"fork":false,"pushed_at":"2025-05-05T17:59:24.000Z","size":598,"stargazers_count":360,"open_issues_count":10,"forks_count":92,"subscribers_count":20,"default_branch":"main","last_synced_at":"2025-05-13T06:03:14.138Z","etag":null,"topics":["fastify","fastify-plugin","speed","templates"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/@fastify/view","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fastify.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"fastify","open_collective":"fastify"}},"created_at":"2017-03-30T07:02:23.000Z","updated_at":"2025-05-05T17:57:58.000Z","dependencies_parsed_at":"2023-02-14T17:01:51.076Z","dependency_job_id":"b455c286-f846-4d9e-9362-a1413c28650a","html_url":"https://github.com/fastify/point-of-view","commit_stats":{"total_commits":379,"total_committers":92,"mean_commits":4.119565217391305,"dds":0.8469656992084433,"last_synced_commit":"5f30f93ce8dad7c029f9607b771c4c928fa23f68"},"previous_names":[],"tags_count":83,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Fpoint-of-view","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Fpoint-of-view/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Fpoint-of-view/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fastify%2Fpoint-of-view/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fastify","download_url":"https://codeload.github.com/fastify/point-of-view/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254092797,"owners_count":22013291,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastify","fastify-plugin","speed","templates"],"created_at":"2024-08-01T06:01:07.034Z","updated_at":"2025-05-14T07:10:31.949Z","avatar_url":"https://github.com/fastify.png","language":"JavaScript","funding_links":["https://github.com/sponsors/fastify","https://opencollective.com/fastify"],"categories":["JavaScript","\u003ch2 align=\"center\"\u003eAwesome Fastify\u003c/h2\u003e"],"sub_categories":["\u003ch2 align=\"center\"\u003eEcosystem\u003c/h2\u003e"],"readme":"# @fastify/view\n\n[![CI](https://github.com/fastify/point-of-view/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/point-of-view/actions/workflows/ci.yml)\n[![NPM version](https://img.shields.io/npm/v/@fastify/view.svg?style=flat)](https://www.npmjs.com/package/@fastify/view)\n[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)\n\nTemplates rendering plugin support for Fastify.\n\n`@fastify/view` decorates the reply interface with the `view` and `viewAsync` methods for managing view engines, which can be used to render templated responses.\n\nCurrently supports the following templates engines:\n\n- [`ejs`](https://ejs.co/)\n- [`nunjucks`](https://mozilla.github.io/nunjucks/)\n- [`pug`](https://pugjs.org/api/getting-started.html)\n- [`handlebars`](https://handlebarsjs.com/)\n- [`mustache`](https://mustache.github.io/)\n- [`twig`](https://twig.symfony.com/)\n- [`liquid`](https://github.com/harttle/liquidjs)\n- [`doT`](https://github.com/olado/doT)\n- [`eta`](https://eta.js.org)\n\nIn `production` mode, `@fastify/view` will heavily cache the templates file and functions, while in `development` will reload every time the template file and function.\n\n_Note: For **Fastify v3 support**, please use point-of-view `5.x` (npm i point-of-view@5)._\n\n_Note that at least Fastify `v2.0.0` is needed._\n\n## Recent Changes\n\n_Note: `reply.viewAsync` added as a replacement for `reply.view` and `fastify.view`. See [Migrating from view to viewAsync](#migrating-from-view-to-viewAsync)._\n\n_Note: [`ejs-mate`](https://github.com/JacksonTian/ejs-mate) support [has been dropped](https://github.com/fastify/point-of-view/pull/157)._\n\n_Note: [`marko`](https://markojs.com/) support has been dropped. Please use [`@marko/fastify`](https://github.com/marko-js/fastify) instead._\n\n#### Benchmarks\n\nThe benchmarks were run with the files in the `benchmark` folder with the `ejs` engine.\nThe data has been taken with: `autocannon -c 100 -d 5 -p 10 localhost:3000`\n\n- Express: 8.8k req/sec\n- **Fastify**: 15.6k req/sec\n\n## Install\n\n```\nnpm i @fastify/view\n```\n\n\u003ca name=\"quickstart\"\u003e\u003c/a\u003e\n\n## Quick start\n\n`fastify.register` is used to register @fastify/view. By default, It will decorate the `reply` object with a `view` method that takes at least two arguments:\n\n- the template to be rendered\n- the data that should be available to the template during rendering\n\nThis example will render the template using the EJS engine and provide a variable `name` to be used inside the template:\n\n```html\n\u003c!-- index.ejs ---\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\u003c/head\u003e\n  \u003cbody\u003e\n    \u003cp\u003eHello, \u003c%= name %\u003e!\u003c/p\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n```js\n// index.js:\nconst fastify = require(\"fastify\")()\nconst fastifyView = require(\"@fastify/view\")\n\nfastify.register(fastifyView, {\n  engine: {\n    ejs: require(\"ejs\")\n  }\n})\n\n// synchronous handler:\nfastify.get(\"/\", (req, reply) =\u003e {\n  reply.view(\"index.ejs\", { name: \"User\" });\n})\n\n// asynchronous handler:\nfastify.get(\"/\", async (req, reply) =\u003e {\n  return reply.viewAsync(\"index.ejs\", { name: \"User\" });\n})\n\nfastify.listen({ port: 3000 }, (err) =\u003e {\n  if (err) throw err;\n  console.log(`server listening on ${fastify.server.address().port}`);\n})\n```\n\n## Configuration\n\n### Options\n\n| Option                 | Description | Default |\n| ---------------------- | ----------- | ------- |\n| `engine`               | **Required**. The template engine object - pass in the return value of `require('\u003cengine\u003e')`    | |\n| `production`           | Enables caching of template files and render functions | `NODE_ENV === \"production\"` |\n| `maxCache`             | In `production` mode, maximum number of cached template files and render functions | `100` |\n| `defaultContext`       | Template variables available to all views. Variables provided on render have precedence and will **override** this if they have the same name. \u003cbr\u003e\u003cbr\u003eExample: `{ siteName: \"MyAwesomeSite\" }` | `{}` |\n| `propertyName`         | The property that should be used to decorate `reply` and `fastify` \u003cbr\u003e\u003cbr\u003eE.g. `reply.view()` and `fastify.view()` where `\"view\"` is the property name | `\"view\"` |\n| `asyncPropertyName`    | The property that should be used to decorate `reply` for async handler \u003cbr\u003e\u003cbr\u003eDefaults to `${propertyName}Async` if `propertyName` is defined | `\"viewAsync\"` |\n| `root`                 | The root path of your templates folder. The template name or path passed to the render function will be resolved relative to this path | `\"./\"` |\n| `charset`              | Default charset used when setting `Content-Type` header | `\"utf-8\"` |\n| `includeViewExtension` | Automatically append the default extension for the used template engine **if omitted from the template name**. So instead of `template.hbs`, just `template` can be used | `false` |\n| `viewExt`              | Override the default extension for a given template engine. This has precedence over `includeViewExtension` and will lead to the same behavior, just with a custom extension. \u003cbr\u003e\u003cbr\u003eExample: `\"handlebars\"` | `\"\"` |\n| `layout`               | See [Layouts](#layouts) \u003cbr\u003e\u003cbr\u003eThis option lets you specify a global layout file to be used when rendering your templates. Settings like `root` or `viewExt` apply as for any other template file. \u003cbr\u003e\u003cbr\u003eExample: `./templates/layouts/main.hbs`  | |\n| `options`              | See [Engine-specific settings](#engine-specific-settings) | `{}` |\n\n### Example\n\n```js\nfastify.register(require(\"@fastify/view\"), {\n  engine: {\n    handlebars: require(\"handlebars\"),\n  },\n  root: path.join(__dirname, \"views\"), // Points to `./views` relative to the current file\n  layout: \"./templates/template\", // Sets the layout to use to `./views/templates/layout.handlebars` relative to the current file.\n  viewExt: \"handlebars\", // Sets the default extension to `.handlebars`\n  propertyName: \"render\", // The template can now be rendered via `reply.render()` and `fastify.render()`\n  defaultContext: {\n    dev: process.env.NODE_ENV === \"development\", // Inside your templates, `dev` will be `true` if the expression evaluates to true\n  },\n  options: {}, // No options passed to handlebars\n});\n```\n\n## Layouts\n\n@fastify/view supports layouts for **EJS**, **Handlebars**, **Eta** and **doT**. When a layout is specified, the request template is first rendered, then the layout template is rendered with the request-rendered html set on `body`.\n\n### Example\n\n```html\n\u003c!-- layout.ejs: --\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\u003c/head\u003e\n  \u003cbody\u003e\n    \u003c!--\n      Ensure body is not escaped:\n\n      EJS: \u003c%- body %\u003e\n      Handlebars: {{{ body }}}\n      ETA/doT: \u003c%~ it.body %\u003e\n    --\u003e\n    \u003c%- body %\u003e\n    \u003cbr/\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n```html\n\u003c!-- template.ejs: --\u003e\n\u003cp\u003e\u003c%= text %\u003e\u003c/p\u003e\n```\n\n```js\n// index.js:\nfastify.register(fastifyView, {\n  engine: { ejs },\n  layout: \"layout.ejs\"\n})\n\nfastify.get('/', (req, reply) =\u003e {\n  const data = { text: \"Hello!\"}\n  reply.view('template.ejs', data)\n})\n```\n\n### Providing a layout on render\n**Please note:** Global layouts and providing layouts on render are mutually exclusive. They can not be mixed.\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  const data = { text: \"Hello!\"}\n  reply.view('template.ejs', data, { layout: 'layout.ejs' })\n})\n```\n\n## Setting request-global variables\nSometimes, several templates should have access to the same request-specific variables. E.g. when setting the current username.\n\nIf you want to provide data, which will be depended on by a request and available in all views, you have to add property `locals` to `reply` object, like in the example below:\n\n```js\nfastify.addHook(\"preHandler\", function (request, reply, done) {\n  reply.locals = {\n    text: getTextFromRequest(request), // it will be available in all views\n  };\n\n  done();\n});\n```\n\nProperties from `reply.locals` will override those from `defaultContext`, but not from `data` parameter provided to `reply.view(template, data)` function.\n\n## Rendering the template into a variable\nThe `fastify` object is decorated the same way as `reply` and allows you to just render a view into a variable (without request-global variables) instead of sending the result back to the browser:\n\n```js\n// Promise based, using async/await\nconst html = await fastify.view(\"/templates/index.ejs\", { text: \"text\" });\n\n// Callback based\nfastify.view(\"/templates/index.ejs\", { text: \"text\" }, (err, html) =\u003e {\n  // Handle error\n  // Do something with `html`\n});\n```\n\nIf called within a request hook and you need request-global variables, see [Migrating from view to viewAsync](#migrating-from-view-to-viewAsync).\n\n\n## Registering multiple engines\n\nRegistering multiple engines with different configurations is supported. They are distinguished via their `propertyName`:\n\n```js\nfastify.register(require(\"@fastify/view\"), {\n  engine: { ejs: ejs },\n  layout: \"./templates/layout-mobile.ejs\",\n  propertyName: \"mobile\",\n});\n\nfastify.register(require(\"@fastify/view\"), {\n  engine: { ejs: ejs },\n  layout: \"./templates/layout-desktop.ejs\",\n  propertyName: \"desktop\",\n});\n\nfastify.get(\"/mobile\", (req, reply) =\u003e {\n  // Render using the `mobile` render function\n  return reply.mobile(\"/templates/index.ejs\", { text: \"text\" });\n});\n\nfastify.get(\"/desktop\", (req, reply) =\u003e {\n  // Render using the `desktop` render function\n  return reply.desktop(\"/templates/index.ejs\", { text: \"text\" });\n});\n```\n\n## Minifying HTML on render\n\nTo utilize [`html-minifier-terser`](https://www.npmjs.com/package/html-minifier-terser) in the rendering process, you can add the option `useHtmlMinifier` with a reference to `html-minifier-terser`,\nand the optional `htmlMinifierOptions` option is used to specify the `html-minifier-terser` options:\n\n```js\n// get a reference to html-minifier-terser\nconst minifier = require('html-minifier-terser')\n// optionally defined the html-minifier-terser options\nconst minifierOpts = {\n  removeComments: true,\n  removeCommentsFromCDATA: true,\n  collapseWhitespace: true,\n  collapseBooleanAttributes: true,\n  removeAttributeQuotes: true,\n  removeEmptyAttributes: true\n}\n// in template engine options configure the use of html-minifier\n  options: {\n    useHtmlMinifier: minifier,\n    htmlMinifierOptions: minifierOpts\n  }\n```\n\nTo exclude paths from minification, you can add the option `pathsToExcludeHtmlMinifier` with a list of paths:\n```js\n// get a reference to html-minifier-terser\nconst minifier = require('html-minifier-terser')\n// in options configure the use of html-minifier-terser and set paths to exclude from minification\nconst options = {\n  useHtmlMinifier: minifier,\n  pathsToExcludeHtmlMinifier: ['/test']\n}\n\nfastify.register(require(\"@fastify/view\"), {\n  engine: {\n    ejs: require('ejs')\n  },\n  options\n});\n\n// This path is excluded from minification\nfastify.get(\"/test\", (req, reply) =\u003e {\n  reply.view(\"./template/index.ejs\", { text: \"text\" });\n});\n\n```\n\n\n\n## Engine-specific settings\n\n\u003c!---\n// I don't think this is needed - see https://github.com/fastify/point-of-view/issues/280\n## EJS\n\nTo use include files please extend your template options as follows:\n\n```js\n// get a reference to resolve\nconst resolve = require('node:path').resolve;\n// other code ...\n// in template engine options configure how to resolve templates folder\noptions: {\n  filename: resolve(\"templates\");\n}\n```\n\nand in ejs template files (for example templates/index.ejs) use something like:\n\n```html\n\u003c%- include('header.ejs') %\u003e\n```\n\nwith a path relative to the current page, or an absolute path. Please check this example [here](./templates/layout-with-includes.ejs)\n---\u003e\n\n### Mustache\n\nTo use partials in mustache you will need to pass the names and paths in the options parameter:\n\n```js\n  options: {\n    partials: {\n      header: 'header.mustache',\n      footer: 'footer.mustache'\n    }\n  }\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  reply.view('./templates/index.mustache', data)\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.mustache', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      const render = mustache.render.bind(mustache, file)\n      reply.view(render, data)\n    }\n  })\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.mustache', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view({ raw: file }, data)\n    }\n  })\n})\n```\n\n### Handlebars\n\nTo use partials in handlebars you will need to pass the names and paths in the options parameter:\n\n```js\n  options: {\n    partials: {\n      header: 'header.hbs',\n      footer: 'footer.hbs'\n    }\n  }\n```\n\nYou can specify [compile options](https://handlebarsjs.com/api-reference/compilation.html#handlebars-compile-template-options) as well:\n\n```js\n  options: {\n    compileOptions: {\n      preventIndent: true\n    }\n  }\n```\n\nTo access `defaultContext` and `reply.locals` as [`@data` variables](https://handlebarsjs.com/api-reference/data-variables.html):\n\n```js\n  options: {\n    useDataVariables: true\n  }\n```\n\nTo use layouts in handlebars you will need to pass the `layout` parameter:\n\n```js\nfastify.register(require(\"@fastify/view\"), {\n  engine: {\n    handlebars: require(\"handlebars\"),\n  },\n  layout: \"./templates/layout.hbs\",\n});\n\nfastify.get(\"/\", (req, reply) =\u003e {\n  reply.view(\"./templates/index.hbs\", { text: \"text\" });\n});\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.hbs', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      const render = handlebars.compile(file)\n      reply.view(render, data)\n    }\n  })\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.hbs', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view({ raw: file }, data)\n    }\n  })\n})\n```\n\n### Nunjucks\n\nYou can load templates from multiple paths when using the nunjucks engine:\n\n```js\nfastify.register(require(\"@fastify/view\"), {\n  engine: {\n    nunjucks: require(\"nunjucks\"),\n  },\n  templates: [\n    \"node_modules/shared-components\",\n    \"views\",\n  ],\n});\n```\n\nTo configure nunjucks environment after initialization, you can pass callback function to options:\n\n```js\noptions: {\n  onConfigure: (env) =\u003e {\n    // do whatever you want on nunjucks env\n  };\n}\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  reply.view('./templates/index.njk', data)\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.njk', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      const render = nunjucks.compile(file)\n      reply.view(render, data)\n    }\n  })\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.njk', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view({ raw: file }, data)\n    }\n  })\n})\n```\n\n### Liquid\n\nTo configure liquid you need to pass the engine instance as engine option:\n\n```js\nconst { Liquid } = require(\"liquidjs\");\nconst path = require('node:path');\n\nconst engine = new Liquid({\n  root: path.join(__dirname, \"templates\"),\n  extname: \".liquid\",\n});\n\nfastify.register(require(\"@fastify/view\"), {\n  engine: {\n    liquid: engine,\n  },\n});\n\nfastify.get(\"/\", (req, reply) =\u003e {\n  reply.view(\"./templates/index.liquid\", { text: \"text\" });\n});\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.liquid', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      const render = engine.renderFile.bind(engine, './templates/index.liquid')\n      reply.view(render, data)\n    }\n  })\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.liquid', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view({ raw: file }, data)\n    }\n  })\n})\n```\n\n### doT\n\nWhen using [doT](https://github.com/olado/doT) the plugin compiles all templates when the application starts, this way all `.def` files are loaded and\nboth `.jst` and `.dot` files are loaded as in-memory functions.\nThis behavior is recommended by the doT team [here](https://github.com/olado/doT#security-considerations).\nTo make it possible it is necessary to provide a `root` or `templates` option with the path to the template directory.\n\n```js\nfastify.register(require(\"@fastify/view\"), {\n  engine: {\n    dot: require(\"dot\"),\n  },\n  root: \"templates\",\n  options: {\n    destination: \"dot-compiled\", // path where compiled .jst files are placed (default = 'out')\n  },\n});\n\nfastify.get(\"/\", (req, reply) =\u003e {\n  // this works both for .jst and .dot files\n  reply.view(\"index\", { text: \"text\" });\n});\n```\n\n```js\nconst d = dot.process({ path: 'templates', destination: 'out' })\nfastify.get('/', (req, reply) =\u003e {\n  reply.view(d.index, data)\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  reply.view({ raw: readFileSync('./templates/index.dot'), imports: { def: readFileSync('./templates/index.def') } }, data)\n})\n```\n\n### eta\n\n```js\nconst { Eta } = require('eta')\nlet eta = new Eta()\nfastify.register(pointOfView, {\n  engine: {\n    eta\n  },\n  templates: 'templates'\n})\n\nfastify.get(\"/\", (req, reply) =\u003e {\n  reply.view(\"index.eta\", { text: \"text\" });\n});\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.eta', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view(eta.compile(file), data)\n    }\n  })\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.eta', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view({ raw: file }, data)\n    }\n  })\n})\n```\n\n### ejs\n\n```js\nconst ejs = require('ejs')\nfastify.register(pointOfView, {\n  engine: {\n    ejs\n  },\n  templates: 'templates'\n})\n\nfastify.get(\"/\", (req, reply) =\u003e {\n  reply.view(\"index.ejs\", { text: \"text\" });\n});\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.ejs', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view(ejs.compile(file), data)\n    }\n  })\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.ejs', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view({ raw: file }, data)\n    }\n  })\n})\n```\n\n### pug\n\n```js\nconst pug = require('pug')\nfastify.register(pointOfView, {\n  engine: {\n    pug\n  }\n})\n\n\nfastify.get(\"/\", (req, reply) =\u003e {\n  reply.view(\"index.pug\", { text: \"text\" });\n});\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.pug', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view(pug.compile(file), data)\n    }\n  })\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.pug', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view({ raw: file }, data)\n    }\n  })\n})\n```\n\n### twig\n\n```js\nconst twig = require('twig')\nfastify.register(pointOfView, {\n  engine: {\n    twig\n  }\n})\n\n\nfastify.get(\"/\", (req, reply) =\u003e {\n  reply.view(\"index.twig\", { text: \"text\" });\n});\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.twig', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view(twig.twig({ data: file }), data)\n    }\n  })\n})\n```\n\n```js\nfastify.get('/', (req, reply) =\u003e {\n  fs.readFile('./templates/index.twig', 'utf8', (err, file) =\u003e {\n    if (err) {\n      reply.send(err)\n    } else {\n      reply.view({ raw: file }, data)\n    }\n  })\n})\n```\n\n\u003c!---\n// This seems a bit random given that there was no mention of typescript before.\n### Typing\n\nTyping parameters from `reply.locals` in `typescript`:\n\n```typescript\ninterface Locals {\n  appVersion: string;\n  isAuthorized: boolean;\n  user?: {\n    id: number;\n    login: string;\n  };\n}\n\ndeclare module \"fastify\" {\n  interface FastifyReply {\n    locals: Partial\u003cLocals\u003e | undefined;\n  }\n}\n\napp.addHook(\"onRequest\", (request, reply, done) =\u003e {\n  if (!reply.locals) {\n    reply.locals = {};\n  }\n\n  reply.locals.isAuthorized = true;\n  reply.locals.user = {\n    id: 1,\n    login: \"Admin\",\n  };\n});\n\napp.get(\"/data\", (request, reply) =\u003e {\n  if (!reply.locals) {\n    reply.locals = {};\n  }\n\n  // reply.locals.appVersion = 1 // not a valid type\n  reply.locals.appVersion = \"4.14.0\";\n  reply.view\u003c{ text: string }\u003e(\"/index\", { text: \"Sample data\" });\n});\n```\n--\u003e\n\n## Miscellaneous\n\n### Using @fastify/view as a dependency in a fastify-plugin\n\nTo require `@fastify/view` as a dependency to a [fastify-plugin](https://github.com/fastify/fastify-plugin), add the name `@fastify/view` to the dependencies array in the [plugin's opts](https://github.com/fastify/fastify-plugin#dependencies).\n\n```js\nfastify.register(myViewRendererPlugin, {\n  dependencies: [\"@fastify/view\"],\n});\n```\n\n### Forcing a cache-flush\n\nTo forcefully clear the cache when in production mode, call the `view.clearCache()` function.\n\n```js\nfastify.view.clearCache();\n```\n\n\u003ca name=\"note\"\u003e\u003c/a\u003e\n\n### Migrating from `view` to `viewAsync`\n\nThe behavior of `reply.view` is to immediately send the HTML response as soon as rendering is completed, or immediately send a 500 response with error if encountered, short-circuiting fastify's error handling hooks, whereas `reply.viewAsync` returns a promise that either resolves to the rendered HTML, or rejects on any errors. `fastify.view` has no mechanism for providing request-global variables, if needed. `reply.viewAsync` can be used in both sync and async handlers.\n\n#### Sync handler\nPreviously:\n```js\nfastify.get('/', (req, reply) =\u003e {\n  reply.view('index.ejs', { text: 'text' })\n})\n```\nNow:\n```js\nfastify.get('/', (req, reply) =\u003e {\n  return reply.viewAsync('index.ejs', { text: 'text' })\n})\n```\n#### Async handler\nPreviously:\n```js\n// This is an async function\nfastify.get(\"/\", async (req, reply) =\u003e {\n  const data = await something();\n  reply.view(\"/templates/index.ejs\", { data });\n  return\n})\n```\n\nNow:\n```js\n// This is an async function\nfastify.get(\"/\", async (req, reply) =\u003e {\n  const data = await something();\n  return reply.viewAsync(\"/templates/index.ejs\", { data });\n})\n```\n#### fastify.view (when called inside a route hook)\nPreviously:\n```js\n// Promise based, using async/await\nfastify.get(\"/\", async (req, reply) =\u003e {\n  const html = await fastify.view(\"/templates/index.ejs\", { text: \"text\" });\n  return html\n})\n```\n```js\n// Callback based\nfastify.get(\"/\", (req, reply) =\u003e {\n  fastify.view(\"/templates/index.ejs\", { text: \"text\" }, (err, html) =\u003e {\n    if(err) {\n      reply.send(err)\n    }\n    else {\n      reply.type(\"application/html\").send(html)\n    }\n  });\n})\n```\nNow:\n```js\n// Promise based, using async/await\nfastify.get(\"/\", (req, reply) =\u003e {\n  const html = await fastify.viewAsync(\"/templates/index.ejs\", { text: \"text\" });\n  return html\n})\n```\n```js\nfastify.get(\"/\", (req, reply) =\u003e {\n  fastify.viewAsync(\"/templates/index.ejs\", { text: \"text\" })\n    .then((html) =\u003e reply.type(\"application/html\").send(html))\n    .catch((err) =\u003e reply.send(err))\n  });\n})\n```\n\n## Note\n\nBy default, views are served with the mime type `text/html`, with the charset specified in options. You can specify a different `Content-Type` header using `reply.type`.\n\n## Acknowledgments\n\nThis project is kindly sponsored by:\n\n- [nearForm](https://nearform.com)\n- [LetzDoIt](https://www.letzdoitapp.com/)\n\n## License\n\nLicensed under [MIT](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffastify%2Fpoint-of-view","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffastify%2Fpoint-of-view","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffastify%2Fpoint-of-view/lists"}