Within the previous tutorial, you’ve seen how to serve static files using hapi. HTML files also apply to the static file concept and you could use hapi to reply requests with rendered HTML content. Nonetheless, if you’re on the search for a more dynamic approach related to views, hapi got your back!
This guide walks you through the setup of hapi to render views on server side and reply them for a given request.
Before diving into the details, have a look at the series outline and find posts that match your interests and needs.
hapi Series Overview
- What You’ll Build
- Prepare Your Project: Stack & Structure
- Environment Variables and Storing Secrets
- Set Up MongoDB and Connect With Mongoose
- Sending Emails in Node.js
- Load the User’s Profile Picture From Gravatar Using Virtuals in Mongoose
- Implement a User Profile Editing Screen
- Generate a Username in Mongoose Middleware
- Displaying Seasons and Episodes for TV Shows with Mongoose Relationship Population
- Implementing Pagination for Movies
- Implement a Watchlist
- Create a Full Text Search with MongoDB
- Create a REST API with JSON Endpoints
- Update Mongoose Models for JSON Responses
- API Pagination for TV Shows
- Customize API Endpoints with Query Parameters
- Always Throw and Handle API Validation Errors
- Advanced API Validation With Custom Errors
- Create an API Documentation with Swagger
- Customize Your Swagger API Documentation URL
- Describe Endpoint Details in Your Swagger API Documentation
- 10 Tips on API Testing With Postman
- JWT Authentication in Swagger API Documentation
- API Versioning with Request Headers
- API Login With Username and Password to Generate a JWT
- JWT Authentication and Private API Endpoints
- Refresh Tokens With JWT Authentication
- Create a JWT Utility
- JWT Refresh Token for Multiple Devices
- Check Refresh Token in Authentication Strategy
- Rate Limit Your Refresh Token API Endpoint
- How to Revoke a JWT
- Invalidate JWTs With Blacklists
- JWT Logout (Part 1/2)
- JWT “Immediate” Logout (Part 2/2)
- A Better Place to Invalidate Tokens
- How to Switch the JWT Signing Algorithm
- Roll Your Own Refresh Token Authentication Scheme
- JWT Claims 101
- Use JWT With Asymmetric Signatures (RS256 & Co.)
- Encrypt the JWT Payload (The Simple Way)
- Increase JWT Security Beyond the Signature
- Unsigned JSON Web Tokens (Unsecured JWS)
- JWK and JWKS Overview
- Provide a JWKS API Endpoint
- Create a JWK from a Shared Secret
- JWT Verification via JWKS API Endpoint
- What is JOSE in JWT
- Encrypt a JWT (the JWE Way)
- Authenticate Encrypted JWTs (JWE)
- Encrypted and Signed JWT (Nested JWT)
- Bringing Back JWT Decoding and Authentication
- Bringing Back JWT Claims in the JWT Payload
- Basic Authentication With Username and Password
- Authentication and Remember Me Using Cookies
- How to Set a Default Authentication Strategy
- Define Multiple Authentication Strategies for a Route
- Restrict User Access With Scopes
- Show „Insufficient Scope“ View for Routes With Restricted Access
- Access Restriction With Dynamic and Advanced Scopes
- hapi - How to Fix „unknown authentication strategy“
- Authenticate with GitHub And Remember the Login
- Authenticate with GitLab And Remember the User
- How to Combine Bell With Another Authentication Strategy
- Custom OAuth Bell Strategy to Connect With any Server
- Redirect to Previous Page After Login
- How to Implement a Complete Sign Up Flow With Email and Password
- How to Implement a Complete Login Flow
- Implement a Password-Reset Flow
- Views in hapi 9 (and above)
- How to Render and Reply Views
- How to Reply and Render Pug Views (Using Pug 2.0)
- How to Create a Dynamic Handlebars Layout Template
- Create and Use Handlebars Partial Views
- Create and Use Custom Handlebars Helpers
- Specify a Different Handlebars Layout for a Specific View
- How to Create Jade-Like Layout Blocks in Handlebars
- Use Vue.js Mustache Tags in Handlebars Templates
- How to Use Multiple Handlebars Layouts
- How to Access and Handle Request Payload
- Access Request Headers
- How to Manage Cookies and HTTP States Across Requests
- Detect and Get the Client IP Address
- How to Upload Files
- Quick Access to Logged In User in Route Handlers
- How to Fix “handler method did not return a value, a promise, or throw an error”
- How to Fix “X must return an error, a takeover response, or a continue signal”
- Query Parameter Validation With Joi
- Path Parameter Validation With Joi
- Request Payload Validation With Joi
- Validate Query and Path Parameters, Payload and Headers All at Once on Your Routes
- Validate Request Headers With Joi
- Reply Custom View for Failed Validations
- Handle Failed Validations and Show Errors Details at Inputs
- How to Fix AssertionError, Cannot validate HEAD or GET requests
Plugin Required: Vision
In case you followed this series, you already know about hapi’s powerful plugin system and that the framework’s core is sort of stripped down to the minimum required.
To add view support for your hapi server, the vision plugin needs to be registered to your hapi server instance. Vision adds template rendering support for multiple template engines like Handlebars, Pug (previously Jade), EJS, Mustache, and many more. We’re currently not aware of any other plugin that would add template rendering support to hapi. If you know one, please share a linke within the comments :)
Alright, add vision as a dependency to your project:
npm install -S vision
# -S is equal to --save
As soon as the dependency installation has finished, you can use the package within your application. Prepare your server for view support by registering the vision plugin.
hapi v17
const Hapi = require('hapi')
// create new server instance
const server = new Hapi.Server()
async function liftOff() {
await server.register({
plugin: require('vision') // add template rendering support in hapi
})
}
liftOff()
hapi v16
const Hapi = require('hapi')
const Vision = require('vision')
// create new server instance
const server = new Hapi.Server()
// register vision to your server instance
server.register(Vision, function (err) {
if (err) {
console.log('Cannot register vision')
}
})
Vision decorates your server
, request
and reply
interfaces with additional methods to handle view engines and ultimately respond a request with a rendered template.
Once the plugin is registered successfully, you’re able to configure the view settings.
Prepare Your Server
Vision decorates your server
instance and adds the views()
method that allows you to configure template support by defining your desired engine, the path to view files and further options.
Within the following, we’ll use handlebars as the template engine of choice. As already mentioned, you’re free to use your favorite engine and certainly hapi supports it. Using handlebars requires us to add the package as a project dependency: npm install -S handlebars
. Further, we’ll use the following structure where view files are located within a views
folder besides the server’s file.
my-project-folder
|- server.js
|- views
|- layout.html
|- index.html
The code block above depicts the mentioned folder structure. The server.js
file contains all code to spin up your hapi instance. The views
folder contains the default layout (default.html
) and an additional view (index.html
). At this point, the contents of the individual view files don’t matter. Going in detail on the views itself would exceed the scope of this tutorial. There’s another guide waiting for you that is geared towards the views itself and we’ll tell you where to find it later in this section.
The following code snippet illustrates how to configure handlebars as the template rendering engine and also the path to view files including a default layout that will be used for every view.
hapi v17
const Hapi = require('hapi')
const Handlebars = require('handlebars')
// create new server instance
const server = new Hapi.Server()
async function liftOff() {
await server.register({
plugin: require('vision') // add template rendering support in hapi
})
// configure template support
server.views({
engines: {
html: Handlebars
},
path: __dirname + '/views',
layout: 'layout'
})
}
liftOff()
hapi v16
const Hapi = require('hapi')
const Vision = require('vision')
const Handlebars = require('handlebars')
// create new server instance
const server = new Hapi.Server()
// register vision to your server instance
server.register(Vision, function (err) {
if (err) {
console.log('Cannot register vision')
}
// configure template support
server.views({
engines: {
html: Handlebars
},
path: __dirname + '/views',
layout: 'layout'
})
})
Successfully registering vision to your server instance allows you to make use of the new server.views()
method to configure template rendering support. Please remember the depicted project structure where view files are located in a views
folder aside the server.js
file.
The path
option takes the path to your root folder where any view file is located (as .html
files). Also, you’re allowed to define a default layout that contains recurring blocks like header, content wrapper, footer, etc. There’s another tutorial going in detail on how to create dynamic handlebars layouts using hapi that might be interesting for you.
Default Layout Name
The layout
option takes either a boolean (true/false
) or the name of your default layout as a string parameter. If you’re using the boolean, please name your default layout as default.html
. Using the layout’s name, you can just provide the name like this: layout: 'my-default-layout'
.
Render and Reply Views
Enough theory and configuration, we know you’re interested in how to reply views. You can either use reply.view()
or a view handler for your route configuration. Vision decorated the reply interface and added the view()
method and also provides the handler
functionality to reply views directly.
Reply Rendered Views Using reply.view()
This section assumes that you’re familiar with routing in hapi. If you want to recap your knowledge, just follow the linked tutorial.
The reply.view()
method takes two arguments: first, the view’s file name and second, additional context data. The view name is required so that vision can pick the correct file from your defined view folder path. Context data is beneficial if you want to render placeholders in your views dynamically with user or context-specific data.
hapi v17
server.route({
method: 'GET',
path: '/',
handler: (request, reply) => {
var data = { message: 'Hello from Future Studio' }
return h.view('index', data)
}
})
hapi v16
server.route({
method: 'GET',
path: '/',
handler: (request, reply) => {
var data = { message: 'Hello from Future Studio' }
reply.view('index', data)
}
})
The route configuration above will render the index
view file and pass an additional data object to the view. Passing data to views is an extensive topic that is tightly coupled with your rendering engine. You’ll find more information on how to replace placeholders with actual data within the documentation of your chosen template engine.
View Handler
If you just want to render a view without passing and replacing dynamic attributes, just go ahead and define your handler as an object and pass the view
property with the related view name. Hapi will search for the given view, render the template and reply the HTML content.
server.route({
method: 'GET',
path: '/',
handler: {
view: 'index'
}
})
You can also pass static context data using a view handler. Use the view
property and pass an object containing fields for template
and context
. template
holds the view name and context
is the data object that can be used within your views to exchange placeholders with your desired data.
server.route({
method: 'GET',
path: '/',
handler: {
view: {
template: 'mytemplate',
context: {
title: 'My Template Title',
message: 'This is a message for my view!'
}
}
}
})
Supported View Engines
Vision ships with support for various template rendering engines. The list below outlines the most common engines that are supported out of the box.
Within Vision’s GitHub repository you’ll find example configuration for all of the listed engines.
Layouts
Throughout this guide, we’ve linked to another tutorial that leads you through the details on how to create a dynamic view layout with header, content and footer support. We’ve further guides that explain how to create and use partial views and helper functions within views that get rendered on server side using hapi.
Outlook
This step-by-step guide walked you through the setup of template rendering support within a hapi server. You’ve learned about the vision plugin that decorates the server
, request
and reply
interfaces and with that adds view support for various template formats. There’s a bunch of supported engines that come at no cost. You’re free to create your own package to bring vision and another template engine together.
In case you want to go in detail on views and create dynamic layouts, follow the linked guides on partial views and helper functions. There’s a lot to discover!
Besides nice looking websites, you want to serve static files like custom CSS and JS, and of course images! Head right to the next tutorial showing you how to serve files in hapi.
We appreciate feedback and love to help you if there’s a question on your mind. Let us know in the comments or on twitter @futurestud_io.
Make it rock & enjoy coding!
Additional Resources
- vision plugin to add template rendering support for hapi
- How to Create and Use Partial Views
- How to Create and Use Custom View Helpers