Model-View-Controller

Nest, by default, makes use of the Express library under the hood. Hence, every technique for using the MVC (Model-View-Controller) pattern in Express applies to Nest as well.

First, let’s scaffold a simple Nest application using the CLI tool:

  1. $ npm i -g @nestjs/cli
  2. $ nest new project

In order to create an MVC app, we also need a template engine to render our HTML views:

  1. $ npm install --save hbs

We’ve used the hbs (Handlebars) engine, though you can use whatever fits your requirements. Once the installation process is complete, we need to configure the express instance using the following code:

  1. @@filename(main)
  2. import { NestFactory } from '@nestjs/core';
  3. import { NestExpressApplication } from '@nestjs/platform-express';
  4. import { join } from 'path';
  5. import { AppModule } from './app.module';
  6. async function bootstrap() {
  7. const app = await NestFactory.create<NestExpressApplication>(
  8. AppModule,
  9. );
  10. app.useStaticAssets(join(__dirname, '..', 'public'));
  11. app.setBaseViewsDir(join(__dirname, '..', 'views'));
  12. app.setViewEngine('hbs');
  13. await app.listen(3000);
  14. }
  15. bootstrap();
  16. @@switch
  17. import { NestFactory } from '@nestjs/core';
  18. import { join } from 'path';
  19. import { AppModule } from './app.module';
  20. async function bootstrap() {
  21. const app = await NestFactory.create(
  22. AppModule,
  23. );
  24. app.useStaticAssets(join(__dirname, '..', 'public'));
  25. app.setBaseViewsDir(join(__dirname, '..', 'views'));
  26. app.setViewEngine('hbs');
  27. await app.listen(3000);
  28. }
  29. bootstrap();

We told Express that the public directory will be used for storing static assets, views will contain templates, and the hbs template engine should be used to render HTML output.

Template rendering

Now, let’s create a views directory and index.hbs template inside it. In the template, we’ll print a message passed from the controller:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8" />
  5. <title>App</title>
  6. </head>
  7. <body>
  8. {{ "{{ message }\}" }}
  9. </body>
  10. </html>

Next, open the app.controller file and replace the root() method with the following code:

  1. @@filename(app.controller)
  2. import { Get, Controller, Render } from '@nestjs/common';
  3. @Controller()
  4. export class AppController {
  5. @Get()
  6. @Render('index')
  7. root() {
  8. return { message: 'Hello world!' };
  9. }
  10. }

In this code, we are specifying the template to use in the @Render() decorator, and the return value of the route handler method is passed to the template for rendering. Notice that the return value is an object with a property message, matching the message placeholder we created in the template.

While the application is running, open your browser and navigate to http://localhost:3000. You should see the Hello world! message.

Dynamic template rendering

If the application logic must dynamically decide which template to render, then we should use the @Res() decorator, and supply the view name in our route handler, rather than in the @Render() decorator:

info Hint When Nest detects the @Res() decorator, it injects the library-specific response object. We can use this object to dynamically render the template. Learn more about the response object API here.

  1. @@filename(app.controller)
  2. import { Get, Controller, Render } from '@nestjs/common';
  3. import { Response } from 'express';
  4. import { AppService } from './app.service';
  5. @Controller()
  6. export class AppController {
  7. constructor(private readonly appService: AppService) {}
  8. @Get()
  9. root(@Res() res: Response) {
  10. return res.render(
  11. this.appService.getViewName(),
  12. { message: 'Hello world!' },
  13. );
  14. }
  15. }

Example

A working example is available here.

Fastify

As mentioned in this chapter, we are able to use any compatible HTTP provider together with Nest. One such library is Fastify. In order to create an MVC application with Fastify, we have to install the following packages:

  1. $ npm i --save fastify point-of-view handlebars

The next steps cover almost the same process used with Express, with minor differences specific to the platform. Once the installation process is complete, open the main.ts file and update its contents:

  1. @@filename(main)
  2. import { NestFactory } from '@nestjs/core';
  3. import { NestFastifyApplication, FastifyAdapter } from '@nestjs/platform-fastify';
  4. import { AppModule } from './app.module';
  5. import { join } from 'path';
  6. async function bootstrap() {
  7. const app = await NestFactory.create<NestFastifyApplication>(
  8. AppModule,
  9. new FastifyAdapter(),
  10. );
  11. app.useStaticAssets({
  12. root: join(__dirname, '..', 'public'),
  13. prefix: '/public/',
  14. });
  15. app.setViewEngine({
  16. engine: {
  17. handlebars: require('handlebars'),
  18. },
  19. templates: join(__dirname, '..', 'views'),
  20. });
  21. await app.listen(3000);
  22. }
  23. bootstrap();
  24. @@switch
  25. import { NestFactory } from '@nestjs/core';
  26. import { FastifyAdapter } from '@nestjs/platform-fastify';
  27. import { AppModule } from './app.module';
  28. import { join } from 'path';
  29. async function bootstrap() {
  30. const app = await NestFactory.create(AppModule, new FastifyAdapter());
  31. app.useStaticAssets({
  32. root: join(__dirname, '..', 'public'),
  33. prefix: '/public/',
  34. });
  35. app.setViewEngine({
  36. engine: {
  37. handlebars: require('handlebars'),
  38. },
  39. templates: join(__dirname, '..', 'views'),
  40. });
  41. await app.listen(3000);
  42. }
  43. bootstrap();

The Fastify API is slightly different but the end result of those methods calls remains the same. One difference to notice with Fastify is that the template name passed into the @Render() decorator must include a file extension.

  1. @@filename(app.controller)
  2. import { Get, Controller, Render } from '@nestjs/common';
  3. @Controller()
  4. export class AppController {
  5. @Get()
  6. @Render('index.hbs')
  7. root() {
  8. return { message: 'Hello world!' };
  9. }
  10. }

While the application is running, open your browser and navigate to http://localhost:3000. You should see the Hello world! message.

Example

A working example is available here.