This tutorial is part of an extensive hapi series that guides you through the framework’s fundamentals. Within the previous guides, you’ve already learned how to get your server up and running and further how to install and create plugins leveraging hapi’s powerful plugin system.
This guide walks you through the essentials of routing and how to add routes for all kinds of HTTP methods (GET
, POST
, PUT
, etc.).
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
- Route Handling and Drive Traffic to Your Server
- How to Serve Static Files (Images, JS, CSS, etc.)
- How to Use Query Parameters
- Optional Path Parameters
- Multi-Segment/Wildcard Path Parameters
- Ignore Trailing Slashes on Route Paths
- How to Fix “Unsupported Media Type”
- 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
Routing Basics
Adding routes to your hapi server requires three elements: HTTP method
, path
, and a route handler
. You’re going to register new routes using hapi’s server
instance and the procedure can be as straight as this:
hapi v17
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello Future Studio'
}
})
hapi v16
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
reply('Hello Future Studio')
}
})
This route will respond to a GET
request at the root path /
by sending the text Hello Future Studio
. The following section will show you how to register routes for other HTTP methods than GET
.
Route all the Methods: GET
, POST
, PUT
, etc.
Within the example above you’ve seen the registration of a GET
route. You can use any valid HTTP method name or even an array of HTTP method names as a value for the method
property. To make things approachable, let’s have a look at the following example:
hapi v17
server.route({
method: [ 'GET', 'POST', 'PUT' ],
path: '/',
handler: (request, h) => {
return 'Hello Future Studio'
}
})
hapi v16
server.route({
method: [ 'GET', 'POST', 'PUT' ],
path: '/',
handler: function (request, reply) {
reply('Hello Future Studio')
}
})
The route above will respond with Hello Future Studio
for all GET
, POST
and PUT
requests at path /
.
Actually, executing the same code for different request types isn’t always intended behavior. You might want to render a view for a GET
and let users send data to your server for POST
or PUT
requests.
hapi v17
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello Future Studio'
}
})
server.route({
method: [ 'POST', 'PUT' ],
path: '/',
handler: (request, h) => {
// process the request’s payload …
return 'Created a new Future Studio instance'
}
})
hapi v16
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
reply('Hello Future Studio')
}
})
server.route({
method: [ 'POST', 'PUT' ],
path: '/',
handler: function (request, reply) {
// process the request’s payload …
reply('Created a new Future Studio instance')
}
})
Of course, you can do complex processing and execute load-intensive tasks within your route handlers. Ultimately, you need to respond the request. The response can be either empty or another kind of data like a view or JSON string or just text, etc.
Register Multiple Routes at Once
Hapi’s server.route
function allows you to pass either single route objects or an array of routes. The examples above registered each route separately.
In the following, we’ll simplify that code snippet to register both routes at once using an array.
hapi v17
server.route([
{
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello Future Studio'
}
},
{
method: 'POST',
path: '/',
handler: (request, h) => {
// process the request’s payload …
return 'Created a new Future Studio instance'
}
}
])
hapi v16
server.route([
{
method: 'GET',
path: '/',
handler: function (request, reply) {
reply('Hello Future Studio')
}
},
{
method: 'POST',
path: '/',
handler: function (request, reply) {
// process the request’s payload …
reply('Created a new Future Studio instance')
}
}
])
This little helper is kind of handy when defining a group of routes for a given context.
Path & Path Parameters
You’ve already noticed that the path
property of server.route
is a string value that specifies the individual route endpoint.
A route’s path is tightly coupled with parameters and there are multiple use cases that come to your mind when thinking about path parameters. To add a parameter, just wrap its name into curly brackets, like {parameter}
.
hapi v17
server.route({
method: 'GET',
path: '/page/{page}',
handler: (request, h) => {
return `Greetings from page ${encodeURIComponent(request.params.page)}`
}
})
hapi v16
server.route({
method: 'GET',
path: '/page/{page}',
handler: function (request, reply) {
reply('Greetings from page ' + encodeURIComponent(request.params.page))
}
})
This snippet will respond with Greetings from page <page>
if you provide a value for {page}
within your request. The encodeURIComponent(str)
function will encode your page
parameter to avoid users sending a text that might affect your server’s security.
There are situations where path parameters should be optional. Imagine that you receive an initial data set as an overview when requesting the content at path /
and proceed to fetch detailed data at path /<content-identifier>
. To declare a path parameter optional, add a question mark at the end of your parameter name within the path
: {parametername?}
hapi v17
server.route({
method: 'GET',
path: '/{identifier?}',
handler: (request, h) => {
return `You requested data for ${encodeURIComponent(request.params.identifier)}`
}
})
hapi v16
server.route({
method: 'GET',
path: '/{identifier?}',
handler: function (request, reply) {
reply('You requested data for ' + encodeURIComponent(request.params.identifier))
}
})
Requesting the GET /
endpoint without a parameter will result in the following message: You requested data for undefined
. If you’re passing a parameter with your request, you’ll see a response message like You requested data for <your parameter>
.
Please be aware that you can only define the last named parameter as optional. Precisely, /tag/{tagname?}/{page}
is an invalid path, because there’s another parameter after the optional tagname
.
Further, each path segment can have only a single parameter. That means, you’re allowed to define a route like /{file}.zip
but not /{file}.{ext}
. Path segments are divided by a forward slash /
.
Route Handlers
The third property for server.route
is the handler
function. This handler function accepts two parameters: request
and reply
. The request
object contains details about the actual client’s request, e.g. payload and headers, authentication information, parameters for query and path, etc. You can find details about hapi’s request object within the documentation.
The second parameter is reply
and used to respond a request appropriately. Responding a request with payload is as simple as just passing your desired value as a parameter.
The payload can be (reply(payload)
):
- string
- buffer
- stream
- JSON serializable object
The result of reply
is a response object that allows chaining to further refine the final response, like status code, add response headers, define the response charset or encoding, and many more. Please find a detailed overview of allowed options for the response in hapi’s documentation.
In later tutorials, we’ll go into detail about requests and responses. For now, it’s important and sufficient to know that hapi provides the capabilities to refine your responses as you wish.
Route Config & Options
Besides method
, path
, and handler
, you can define an additional config
property for each route. That’s the exact place to specify things like payload validation, route prerequisites, authentication, caching, etc. We’ll have a detailed look at these capabilities in later tutorials.
Furthermore, you can define documentation related options (description
, notes
, tags
) that doesn’t affect the route’s functionality, but can be helpful and valuable when generating the app’s documentation or bringing new developers on board.
As of now, find more details about route options in hapi’s documentation. As already mentioned, we’ll get back to these functionalities in later tutorials.
Alright, so how does it look like?
hapi v17
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello Future Studio'
},
config: {
description: 'Sends a friendly greeting!',
notes: 'No route parameters available',
tags: ['greeting']
}
})
hapi v16
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
reply('Hello Future Studio')
},
config: {
description: 'Sends a friendly greeting!',
notes: 'No route parameters available',
tags: ['greeting']
}
})
Hapi allows you to move the handler
function into the config
object. That might be beneficial if you want to keep your route configuration and handler close to being aware of authentication or caching mechanisms that might affect the actual functionality inside the handler.
Outlook
This guide provides you an outline of route handling in hapi. Now, you’re able to register custom routes to your server instance and add (optional) path parameters. Also, you know that route configurations are available and can be specified individually.
Next up is hapi’s plugin system. Learn how to install plugins to extend hapi’s built-in feature set.
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!