Within the previous post about hapi and handlebars, we show you how to create a dynamic layout template. Actually, this was just the surface of the functionality hapi provides with its handlebars integration. The view configuration for handlebars with hapi allows multiple options. This article shows you how to use the partialsPath
option to add partial layouts into your app.
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
File Structure
We use the same code structure as we did within the previous post about the dynamic handlebars layout. This keeps us within the same context and allows references between the posts. The views
folder contains any view files ending on .html
even though the files are located within a subdirectory. Besides the views, we have a single file server.js
which describes the minimum functionality required to make this example work properly.
The following overview outlines the the file structure:
views/
helpers/
partials/
header.html
footer.html
layout/
default.html
index.html
server.js
The default
layout is located within the layout
folder. Our partial views have their own directory called partials
. The index.html
is a normal view showing the user some funky text :)
Hapi Server View Configuration
As with dynamic layouts, we use the same server code for this example about partial views with hapi. The interesting part is the view configuration for the server. This time, we define the partialsPath
property to let hapi know where to find partial views.
const Hapi = require('hapi')
// Create hapi server instance
const server = new Hapi.Server({
host: 'localhost',
port: 3000
})
async function liftOff() {
await server.register({
plugin: require('vision') // add template rendering support in hapi
})
server.views({
engines: {
html: require('handlebars')
},
path: 'views',
layoutPath: 'views/layout',
layout: 'default',
partialsPath: 'views/partials'
//helpersPath: 'views/helpers',
});
// create your routes
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
// Render the view with the custom greeting
const data = {
title: 'This is Index!',
message: 'Hello, World. You crazy handlebars layout'
}
return h.view('index', data);
}
})
try {
await server.start()
// Log to the console the host and port info
console.log('Server started at: ' + server.info.uri);
} catch (err) {
console.log(err)
process.exit(1)
}
}
liftOff()
The partialsPath
property expects the root path to your partial layout files. Keep an eye on your path definition. Within this example, we use relative paths. It’s better to use the absolute path. Node provides different options like __dirname
or the path
module. Just make sure the server is able to find the directories, even though it’s not started from within the directory.
View Templates
To provide a comprehensive overview, we show the code for every layout template from the file structure. Additionally, we combine the default and partial layout files to “import” the contents from partial views into the defaut layout.
Default Layout Template
We defined this default layout template as the way to go for every view when rendering a web view with hapi. Hapi automatically uses this layout and includes the defined subview into it. The most important parts for this guide are the {{> header}}
and {{> footer}}
blocks. Both blocks are used to include the contents from each partial view into the default layout.
views/layout/default.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Your Title</title>
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- import CSS or other 3rd pary libs -->
</head>
<body>
<div class="container">
{{! Import the contents of header.html from partials}}
{{> header}}
{{! Every view gets inserted here }}
{{{content}}}
</div>
{{! Import the contents of footer.html from partials}}
{{> footer}}
</body>
</html>
We’ll provide more information about the partial view import within the Partial Views section.
Index View Template
The index.html
template is just a simple view and comprises placeholders for {{title}}
and {{message}}
. Our previous post explains the index view in more detail.
<h1>{{title}}</h1>
<p>{{message}}</p>
Partial Views
Handlebars allows the definition of reusable views as shared templates. You can import partials into your view with handlebars {{> partialPath}}
arrow bracket operator. The arrow bracket defines an import of a partial view and expects the path to the view file as parameter.
The default layout above imports two partial views: header
and footer
. You don’t need to specify the file ending when importing views into your layout. The following snippets show exemplary code for each template which gets imported while rendering the default layout.
views/partials/header.html
<header class="main-header">
<nav class="navbar navbar-default">
<div class="container">
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/home">Home</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/about">About</a></li>
</ul>
</div>
</div>
</nav>
</header>
The header layout is just for illustration purposes. We took a basic Bootstrap navigation to include into the default view.
views/partials/footer.html
{{! The main JavaScript files }}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
{{! Whatever you want to put into the footer :)}}
The footer template is the single jquery import. Expand the template as you wish and use more complex separation of layout and additional importS of javascript files.
Result
The resulting HTML template of the index page will look like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Your Title</title>
<meta name="HandheldFriendly" content="True" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div class="container">
<header class="main-header">
<nav class="navbar navbar-default">
<div class="container">
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a href="/home">Home</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/about">About</a></li>
</ul>
</div>
</div>
</nav>
</header>
<h1>This is Index!</h1>
<p>Hello, World. You crazy handlebars layout</p>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
</body>
</html>
The contents of header.html
and footer.html
partial views are imported into the default layout file. Additionally, the placeholders within the index.html
are replaced with passed data from hapi route definition. Further, the rendered layout from index.html
file replaces the {{{content}}}
placeholder within the default layout template.
Things don’t work out? Please don’t hesitate to leave a comment below or shoot us a tweet @futurestud_io.