Skip to content

Chapter 2 - Render full HTML pages with Handlebars

In this chapter, you will install Handlebars to display a message with a basic HTML template.

Steps

Install Handlebars

Install Handlebars in your project.

In a terminal, execute the following command(s).
1
2
3
npm install --save hbs

npm install --save-dev @types/hbs

Install Handlebars extension in the Dev Container

Find the Handlebars andrejunges.Handlebars extension in Visual Studio Code extensions.

Install the extension. Once installed, click on the little Manage gear (⚙) and select the Add to devcontainer.json. Your devcontainer.json should looks like that.

.devcontainer/devcontainer.json
// See https://containers.dev/implementors/json_reference/ for configuration reference
{
	"name": "Create a media player application project",
	"build": {
		"dockerfile": "Dockerfile"
	},
	"remoteUser": "node",
	"postCreateCommand": "./.devcontainer/npm-global-without-sudo.sh",
	"customizations": {
		"vscode": {
			"extensions": ["andrejunges.Handlebars"]
		}
	}
}

Open the Visual Studio Code command Palette with View > Command palette... and type the Dev Containers: Rebuild and Reopen in Container command and select it. This will rebuild your devcontainer and install the new extension.

Make usage of Handlebars

Create the views directory at the root level of your working directory that will be used by Handlebars.

In a terminal, execute the following command(s).
mkdir views

Update the main NestJS file to use Handlebars.

src/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
	const app = await NestFactory.create<NestExpressApplication>(AppModule);

	app.setBaseViewsDir(join(__dirname, '..', 'views')); // (1)!
	app.setViewEngine('hbs'); // (2)!

	await app.listen(3000);
}

bootstrap();
  1. Handlebars will search for views in this directory.
  2. NestJS now knows that it has to use Handlebars to render the views.

Create the main page view

Create the main page view to display a message.

views/index.hbs
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>Media Player</title>
	</head>
	<body>
		This is the message stored in `hello`: {{ hello }} <!-- (1)! -->
	</body>
</html>
  1. This is an expression that Handlebars will use to replace the variable hello with its value on rendering.

Update the controller

Update the AppController controller so it uses Handlebars views.

src/app.controller.ts
import { Get, Controller, Render } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
	constructor(private readonly appService: AppService) {}

	@Get()
	@Render('index') // (1)!
	root() {
		const hello = this.appService.getHello();

		return { hello: hello }; // (2)!
	}
}
  1. Handlebars will render the template with the one you created earlier (views/index.hbs).
  2. Define the content of the variable hello that will be rendered with Handlebars. Note that you can write return { hello };. It will use hello as the key and the value at the same time.

Check the results

Your working directory should look like this.

.
├── .devcontainer
│   └── ...
├── node_modules
│   └── ...
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── views
│   └── index.hbs
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json

Start the application and access http://localhost:3000. You should see a page with the following content.

This is the message stored in `hello`: Hello World!

Have you noticed? The title of the page is Media Player! This is what differentiates the previous step. Your application renders an HTML page that is sent back to you.

To stop your NestJS application, press Ctrl+C in your terminal.

Summary

Congrats! You have a NestJS application that renders full HTML pages! At the moment, the page feels quite empty but you can now make pages that some parts can be rendered with variables thanks to the help of Handlebars.

Go further

Are you able to add a goodbye variable to the service that is displayed in the index view so that it displays the following content? Expand the next component to see the answer!

This is the message stored in `hello`: Hello World!
This is the message stored in `goodbye`: Goodbye World!
Show me the answer!

Update the AppService service to include a getGoodbye() method.

src/app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
	getHello(): string {
		return 'Hello World!';
	}

	getGoodbye(): string {
		return 'Goodbye World!';
	}
}

Update the AppController controller to get and return the goodbye variable to pass to the Handlebars template.

src/app.controller.ts
import { Get, Controller, Render } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
	constructor(private readonly appService: AppService) {}

	@Get()
	@Render('index')
	root() {
		const hello = this.appService.getHello();
		const goodbye = this.appService.getGoodbye();

		return { // (1)!
			hello: hello,
			goodbye: goodbye,
		};
	}
}
  1. As stated earlier, the highlighted lines can be written as return { hello, goodbye };.

Update the index view to display the goodbye variable.

views/index.hbs
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>Media Player</title>
	</head>
	<body>
		This is the message stored in `hello`: {{ hello }}<br> <!-- (1)! -->
		This is the message stored in `goodbye`: {{ goodbye }}
	</body>
</html>
  1. The <br> HTML element can be used to break lines so each sentence is on its own line.

Save your changes. If you didn't stop your NestJS application, it should automatically restart the application. Access http://localhost:3000, refresh the page and you should see the expected result.