{"id":16721672,"url":"https://github.com/zoltan-nz/nestjs-mvc","last_synced_at":"2025-03-21T21:30:42.538Z","repository":{"id":36387477,"uuid":"188986449","full_name":"zoltan-nz/nestjs-mvc","owner":"zoltan-nz","description":"Tutorial: build a full stack Node.js application with NestJS and TypeScript","archived":false,"fork":false,"pushed_at":"2023-01-07T05:51:24.000Z","size":1419,"stargazers_count":26,"open_issues_count":17,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-01T10:02:20.776Z","etag":null,"topics":["nestjs","nodejs","tutorial","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zoltan-nz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-05-28T08:24:25.000Z","updated_at":"2025-02-07T19:50:37.000Z","dependencies_parsed_at":"2023-01-17T01:30:50.026Z","dependency_job_id":null,"html_url":"https://github.com/zoltan-nz/nestjs-mvc","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoltan-nz%2Fnestjs-mvc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoltan-nz%2Fnestjs-mvc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoltan-nz%2Fnestjs-mvc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoltan-nz%2Fnestjs-mvc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zoltan-nz","download_url":"https://codeload.github.com/zoltan-nz/nestjs-mvc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244160019,"owners_count":20408019,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nestjs","nodejs","tutorial","typescript"],"created_at":"2024-10-12T22:31:49.059Z","updated_at":"2025-03-21T21:30:41.952Z","avatar_url":"https://github.com/zoltan-nz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nest.js MVC\n\n## Implementation Log\n\nPrerequisites:\n\n- Node.js environment\n- `yarn` package manager\n\nInstall Nest CLI globally.\n\n```bash\n$ yarn global add @nestjs/cli\n```\n\nScaffold your project. (In this repository the app name is `nestjs-mvc`.)\n\n```bash\n$ nest new nestjs-mvc\n$ cd nestjs-mvc\n```\n\nOptional step: upgrade `package.json` dependencies to the latest.\n\n```bash\n$ yarn global add npm-check-updates\n$ ncu -u\n$ yarn\n```\n\nOptional step: update `format` script in `package.json`, so prettier will format all your project files.\n\n```\n    \"format\": \"prettier --write '**/*.{ts,tsx,js,jsx,json,md,html}'\",\n```\n\nAdditionally you have to create a `./.prettierignore` file in your project root folder with the following content:\n\n```\ncoverage\ndist\npackage-lock.json\n.cache\n.idea\n.vscode\n```\n\nIt 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/\n\n```\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n```\n\nNow you can test your scaffolded project. Check `package.json`'s `scripts` section for available commands.\n\n```bash\n$ yarn build\n$ yarn format\netc.\n```\n\nBefore any git commit, you should run formatter, linting tool with fix, test and check coverage.\n\n```bash\n$ yarn format\n$ yarn lint --fix\n$ yarn test\n$ yarn test:e2e\n$ yarn test:cov\n```\n\n(Don't forget to create a git commit in this stage.)\n\nRun your project in dev mode and open the app in your browser. (The default address is http://localhost:3000)\n\n```bash\n$ yarn start:dev\n$ open http://localhost:3000\n```\n\nYou should see the `Hello World!` message.\n\nLet'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\n\n```bash\n$ yarn add hbs @types/hbs\n```\n\nUpdate `./src/main.ts`\n\n```typescript\nimport { NestFactory } from '@nestjs/core';\nimport { AppModule } from './app.module';\nimport { NestExpressApplication } from '@nestjs/platform-express';\nimport { join } from 'path';\nimport hbs = require('hbs');\n\nasync function bootstrap() {\n  const app = await NestFactory.create\u003cNestExpressApplication\u003e(AppModule);\n\n  app.useStaticAssets(join(__dirname, '..', 'public'));\n  app.setBaseViewsDir(join(__dirname, '..', 'views'));\n  app.setViewEngine('hbs');\n\n  hbs.registerPartials(join(__dirname, '..', 'views', 'partials'));\n\n  await app.listen(3000);\n}\n\nbootstrap();\n```\n\nCreate two new folders in the project root:\n\n```bash\n$ mkdir public views\n```\n\nCreate a custom `stylesheets` folder in `./public` and add `custom.css`. You can place here your custom styles.\n\n```bash\n$ mkdir ./public/stylesheets\n$ touch ./public/stylesheets/custom.css\n```\n\nCreate a few handlebar files in `views` folder.\n\n```\n$ touch ./views/about.hbs\n$ touch ./views/home.hbs\n$ touch ./views/layout.hbs\n```\n\nCreate `./views/partials` subfolder and add a `navbar` partial.\n\n```bash\n$ mkdir ./views/partials\n$ touch ./views/partials/navbar.hbs\n```\n\nAdd content to your templates.\n\n`./views/partials/navbar.hbs`\n\n```handlebars\n\u003cnav class=\"navbar navbar-expand-lg navbar-light bg-light\"\u003e\n  \u003ca class=\"navbar-brand\" href=\"/\"\u003eNestjs MVC\u003c/a\u003e\n  \u003cbutton class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbarSupportedContent\"\n          aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\"\u003e\n    \u003cspan class=\"navbar-toggler-icon\"\u003e\u003c/span\u003e\n  \u003c/button\u003e\n\n  \u003cdiv class=\"collapse navbar-collapse\" id=\"navbarSupportedContent\"\u003e\n    \u003cul class=\"navbar-nav mr-auto\"\u003e\n      \u003cli class=\"nav-item\"\u003e\n        \u003ca class=\"nav-link\" href=\"/\"\u003eHome\u003c/a\u003e\n      \u003c/li\u003e\n      \u003cli class=\"nav-item\"\u003e\n        \u003ca class=\"nav-link\" href=\"/about\"\u003eAbout\u003c/a\u003e\n      \u003c/li\u003e\n    \u003c/ul\u003e\n  \u003c/div\u003e\n\u003c/nav\u003e\n```\n\n`./views/about.hbs` and `./views/home.hbs`\n\n```handlebars\n\u003ch1\u003e{{title}}\u003c/h1\u003e\n\u003cp\u003eWelcome to {{title}}\u003c/p\u003e\n```\n\n`./views/layout.hbs`\n\n```handlebars\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  \u003ctitle\u003e{{title}}\u003c/title\u003e\n\n  \u003clink rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css\"\n        integrity=\"sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T\" crossorigin=\"anonymous\"\u003e\n  \u003clink rel=\"stylesheet\" href=\"/stylesheets/custom.css\"\u003e\n\n  \u003cscript src=\"https://code.jquery.com/jquery-3.3.1.slim.min.js\"\n          integrity=\"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo\"\n          crossorigin=\"anonymous\"\u003e\u003c/script\u003e\n  \u003cscript src=\"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js\"\n          integrity=\"sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1\"\n          crossorigin=\"anonymous\"\u003e\u003c/script\u003e\n  \u003cscript src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js\"\n          integrity=\"sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM\"\n          crossorigin=\"anonymous\"\u003e\u003c/script\u003e\n\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cdiv class=\"container\"\u003e\n  {{\u003enavbar}}\n  \u003cdiv class=\"row\"\u003e\n    \u003cdiv class=\"col\"\u003e\n      {{{body}}}\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nUpdate `./src/app.controller.ts`.\n\n```typescript\nimport { Controller, Get, Render } from '@nestjs/common';\nimport { AppService } from './app.service';\n\n@Controller()\nexport class AppController {\n  constructor(private readonly appService: AppService) {}\n\n  @Get()\n  @Render('home')\n  root() {\n    return { title: 'Home Page' };\n  }\n\n  @Get('/about')\n  @Render('about')\n  about() {\n    return { title: 'About Page' };\n  }\n}\n```\n\nYou can update `nodemon` configuration files to watch handlebar files and the `views` folder.\n\n`./nodemon.json`\n\n```json\n{\n  \"watch\": [\"dist\", \"views\"],\n  \"ext\": \"js,hbs\",\n  \"exec\": \"node dist/main\"\n}\n```\n\n`./nodemon-debug.json`\n\n```json\n{\n  \"watch\": [\"src\", \"views\"],\n  \"ext\": \"ts,hbs\",\n  \"ignore\": [\"src/**/*.spec.ts\"],\n  \"exec\": \"node --inspect-brk -r ts-node/register -r tsconfig-paths/register src/main.ts\"\n}\n```\n\nYou can run your application with `yarn start:dev` and refresh your page in the browser. (http://localhost:3000)\n\nUpdate tests.\n\n`./src/app.controller.spec.ts`\n\n```typescript\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { AppController } from './app.controller';\nimport { AppService } from './app.service';\n\ndescribe('AppController', () =\u003e {\n  let appController: AppController;\n\n  beforeEach(async () =\u003e {\n    const app: TestingModule = await Test.createTestingModule({\n      controllers: [AppController],\n      providers: [AppService],\n    }).compile();\n\n    appController = app.get\u003cAppController\u003e(AppController);\n  });\n\n  it('renders root', () =\u003e {\n    expect(appController.root()).toStrictEqual({ title: 'Home Page' });\n  });\n\n  it('renders /about', () =\u003e {\n    expect(appController.about()).toStrictEqual({ title: 'About Page' });\n  });\n});\n```\n\n`./test/app.e2e-spec.ts`\n\n```typescript\nimport { Test, TestingModule } from '@nestjs/testing';\nimport * as request from 'supertest';\nimport { AppModule } from './../src/app.module';\nimport { join } from 'path';\nimport hbs = require('hbs');\n\ndescribe('AppController (e2e)', () =\u003e {\n  let app;\n\n  beforeEach(async () =\u003e {\n    const moduleFixture: TestingModule = await Test.createTestingModule({\n      imports: [AppModule],\n    }).compile();\n\n    app = moduleFixture.createNestApplication();\n\n    app.useStaticAssets(join(__dirname, '..', 'public'));\n    app.setBaseViewsDir(join(__dirname, '..', 'views'));\n    app.setViewEngine('hbs');\n\n    hbs.registerPartials(join(__dirname, '..', 'views', 'partials'));\n\n    await app.init();\n  });\n\n  it('/ (GET)', () =\u003e {\n    return request(app.getHttpServer())\n      .get('/')\n      .expect(200);\n  });\n\n  it('/about (GET)', () =\u003e {\n    return request(app.getHttpServer())\n      .get('/about')\n      .expect(200);\n  });\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoltan-nz%2Fnestjs-mvc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzoltan-nz%2Fnestjs-mvc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoltan-nz%2Fnestjs-mvc/lists"}