Prerequisites:
- Node.js environment
yarn
package manager
Install Nest CLI globally.
$ yarn global add @nestjs/cli
Scaffold your project. (In this repository the app name is nestjs-mvc
.)
$ nest new nestjs-mvc
$ cd nestjs-mvc
Optional step: upgrade package.json
dependencies to the latest.
$ yarn global add npm-check-updates
$ ncu -u
$ yarn
Optional step: update format
script in package.json
, so prettier will format all your project files.
"format": "prettier --write '**/*.{ts,tsx,js,jsx,json,md,html}'",
Additionally you have to create a ./.prettierignore
file in your project root folder with the following content:
coverage
dist
package-lock.json
.cache
.idea
.vscode
It is a good practice to have also an .editorconfig
file in your project's root folder with the following content. More about editorconfig: https://editorconfig.org/
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
Now you can test your scaffolded project. Check package.json
's scripts
section for available commands.
$ yarn build
$ yarn format
etc.
Before any git commit, you should run formatter, linting tool with fix, test and check coverage.
$ yarn format
$ yarn lint --fix
$ yarn test
$ yarn test:e2e
$ yarn test:cov
(Don't forget to create a git commit in this stage.)
Run your project in dev mode and open the app in your browser. (The default address is http://localhost:3000)
$ yarn start:dev
$ open http://localhost:3000
You should see the Hello World!
message.
Let's update our homepage to render a Handlebar template. We partially follow the instructions from this page, please read it for more details: https://docs.nestjs.com/techniques/mvc
$ yarn add hbs @types/hbs
Update ./src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import hbs = require('hbs');
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(__dirname, '..', 'public'));
app.setBaseViewsDir(join(__dirname, '..', 'views'));
app.setViewEngine('hbs');
hbs.registerPartials(join(__dirname, '..', 'views', 'partials'));
await app.listen(3000);
}
bootstrap();
Create two new folders in the project root:
$ mkdir public views
Create a custom stylesheets
folder in ./public
and add custom.css
. You can place here your custom styles.
$ mkdir ./public/stylesheets
$ touch ./public/stylesheets/custom.css
Create a few handlebar files in views
folder.
$ touch ./views/about.hbs
$ touch ./views/home.hbs
$ touch ./views/layout.hbs
Create ./views/partials
subfolder and add a navbar
partial.
$ mkdir ./views/partials
$ touch ./views/partials/navbar.hbs
Add content to your templates.
./views/partials/navbar.hbs
./views/about.hbs
and ./views/home.hbs
./views/layout.hbs
Update ./src/app.controller.ts
.
import { Controller, Get, Render } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@Render('home')
root() {
return { title: 'Home Page' };
}
@Get('/about')
@Render('about')
about() {
return { title: 'About Page' };
}
}
You can update nodemon
configuration files to watch handlebar files and the views
folder.
./nodemon.json
{
"watch": ["dist", "views"],
"ext": "js,hbs",
"exec": "node dist/main"
}
./nodemon-debug.json
{
"watch": ["src", "views"],
"ext": "ts,hbs",
"ignore": ["src/**/*.spec.ts"],
"exec": "node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts"
}
You can run your application with yarn start:dev
and refresh your page in the browser. (http://localhost:3000)
Update tests.
./src/app.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
it('renders root', () => {
expect(appController.root()).toStrictEqual({ title: 'Home Page' });
});
it('renders /about', () => {
expect(appController.about()).toStrictEqual({ title: 'About Page' });
});
});
./test/app.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
import { join } from 'path';
import hbs = require('hbs');
describe('AppController (e2e)', () => {
let app;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useStaticAssets(join(__dirname, '..', 'public'));
app.setBaseViewsDir(join(__dirname, '..', 'views'));
app.setViewEngine('hbs');
hbs.registerPartials(join(__dirname, '..', 'views', 'partials'));
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200);
});
it('/about (GET)', () => {
return request(app.getHttpServer())
.get('/about')
.expect(200);
});
});