An Introduction of the Integration Library with NestJS and NEXT.js

saltyshiomix

Shiono Yoshihide

Posted on October 10, 2019

An Introduction of the Integration Library with NestJS and NEXT.js

TLDR

In your controller like server/app.controller.ts:

import {
  Controller,
  Get,
  Req,
  Res,
} from '@nestjs/common';
import { NextService } from '@nestpress/next';

@Controller()
export class AppController {
  constructor(
    private readonly next: NextService,
  ) {}

  @Get()
  public async showIndexPage(@Req() req, @Res() res) {
    ///////////////////////////////////////////
    //                                       //
    //  this will render `pages/index.tsx`!  //
    //                                       //
    ///////////////////////////////////////////
    await this.next.render('/index', req, res);
  }
}

Why?

NestJS is a progressive Node.js framework, so we need to decide the view engine like handlebars.

I love to use React and sometimes want to get better SEO results when creating blogging system.

In that case, NEXT.js is a good choice.

But how do we use NEXT.js on top of NestJS?

NEXT.js is an SSR library, so it already includes the server which generates static html files.

And how do we pass the server data to the NEXT.js client dynamically?

Why and how are coming up to me, so I decided to write the integration library.

#showdev

(Be careful, the blogging system below is under construction! lol)

GitHub logo saltyshiomix / nestpress

A production ready personal blogging system on top of NestJS and NEXT.js

A production ready personal blogging system on top of NestJS and NEXT.js

Roadmaps

  • Cross Platform
  • Dark Theme
  • Authentication
  • Blogging
  • Testing

Usage

Database Setup

For Mac Users

# install postgresql
$ brew install postgresql
# if you want to start postgresql in startup, try do this
$ brew services start postgresql
# create user "nestpressuser" with password "nestpresspass"
$ createuser -P nestpressuser
# create database "nestpressdb" owened by "nestpressuser"
$ createdb nestpressdb -O nestpressuser

For Windows Users

PostgreSQL
> postgresql-11.2-1-windows-x64.exe --install_runtimes 0
pgAdmin
  • Download a latest installer at https://www.pgadmin.org/download
  • Run the pgAdmin and login with a root user
  • Right click Login/Group Roles and Create > Login/Group Role
    • General Panel
      • Name: nestpressuser
    • Definition Panel:
      • Password: nestpresspass
    • Priviledges Panel:
      • Check all Yes
  • Right click Databases and Create > Database
    • General Tab
      • Database: nestpressdb
      • Owner

And the core library is @nestpress/next.

In this article, I will introduce @nestpress/next and its usage.

Installation

$ npm install --save @nestpress/next

Usage

First, populate package.json, tsconfig.json and tsconfig.server.json:

package.json

{
  "name": "sample-app",
  "scripts": {
    "dev": "ts-node --project tsconfig.server.json server/main.ts",
    "build": "next build && tsc --project tsconfig.server.json",
    "start": "cross-env NODE_ENV=production node .next/production-server/main.js"
  },
  "dependencies": {
    "@nestjs/common": "latest",
    "@nestjs/core": "latest",
    "@nestpress/next": "latest",
    "next": "latest",
    "react": "latest",
    "react-dom": "latest",
    "reflect-metadata": "latest",
    "rxjs": "latest"
  },
  "devDependencies": {
    "@types/node": "latest",
    "@types/react": "latest",
    "@types/react-dom": "latest",
    "cross-env": "latest",
    "ts-node": "latest",
    "typescript": "latest"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "preserve",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "strict": true,
    "noEmit": true,
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "resolveJsonModule": true,
    "forceConsistentCasingInFileNames": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "incremental": true
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}

tsconfig.server.json

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "module": "commonjs",
    "noEmit": false,
    "outDir": ".next/production-server"
  },
  "include": [
    "server"
  ]
}

Second, implement these files

  • NestJS side
    • server/app.module.ts
    • server/app.controller.ts
    • server/main.ts
  • NEXT.js side
    • pages/index.tsx

server/app.module.ts

Register NextModule in your application module so that the Nest can handle dependencies:

import {
  Module,
  NestModule,
  MiddlewareConsumer,
  RequestMethod,
} from '@nestjs/common';
import {
  NextModule,
  NextMiddleware,
} from '@nestpress/next';
import { AppController } from './app.controller';

@Module({
  imports: [
    // register NextModule
    NextModule,
  ],
  controllers: [
    AppController,
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    // handle scripts
    consumer
      .apply(NextMiddleware)
      .forRoutes({
        path: '_next*',
        method: RequestMethod.GET,
      });

    // handle other assets
    consumer
      .apply(NextMiddleware)
      .forRoutes({
        path: 'images/*',
        method: RequestMethod.GET,
      });

    consumer
      .apply(NextMiddleware)
      .forRoutes({
        path: 'favicon.ico',
        method: RequestMethod.GET,
      });
  }
}

server/app.controller.ts

Use NextService in your controllers like this:

import {
  IncomingMessage,
  ServerResponse,
} from 'http';
import {
  Controller,
  Get,
  Req,
  Res,
} from '@nestjs/common';
import { NextService } from '@nestpress/next';

@Controller()
export class AppController {
  constructor(
    private readonly next: NextService,
  ) {}

  @Get()
  public async showHome(@Req() req: IncomingMessage, @Res() res: ServerResponse) {
    // this will render `pages/index.tsx`!
    await this.next.render('/index', req, res);
  }
}

server/main.ts

Prepare the Next.js service in the main entry point:

import { NestFactory } from '@nestjs/core';
import { NextModule } from '@nestpress/next';
import { AppModule } from './app.module';

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

  app.get(NextModule).prepare().then(() => {
    app.listen(3000, 'localhost', () => {
      console.log('> Ready on http://localhost:3000 with Next.js!');
    });
  });
}

bootstrap();

pages/index.tsx

In the pages directory, we can do the same as the Next.js way:

export default () => (
  <p>Next.js on top of NestJS!</p>
);

Development Mode

$ yarn dev (or `npm run dev`)

Go to http://localhost:3000 and you'll see Next.js on top of NestJS!.

Production Mode

$ yarn build (or `npm run build`)
$ yarn start (or `npm start`)

Options

import { NestFactory } from '@nestjs/core';
import { NextModule } from '@nestpress/next';
import { AppModule } from './app.module';

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

  app.get(NextModule).prepare({
    /**
     * Whether to launch Next.js in dev mode
     */
    dev: process.env.NODE_ENV !== 'production',
    /**
     * Where the Next project is located
     */
    dir: process.cwd(),
    /**
     * Hide error messages containing server information
     */
    quiet: false,
    /**
     * Object what you would use in next.config.js
     */
    conf: {},
  }).then(() => {
    app.listen(3000, 'localhost', () => {
      console.log('> Ready on http://localhost:3000 with Next.js!');
    });
  });
}

bootstrap();

Advanced Usage: Pass the Server Data to the NEXT.js client

server/app.controller.ts

@Controller()
export class AppController {
  constructor(
    private readonly next: NextService,
    private readonly articleService: ArticleService,
  ) {}

  @Get()
  public async showHome(@Req() req: IncomingMessage, @Res() res: ServerResponse) {
    const articles = await this.articleService.findAll();
    const data = { articles };
    await this.next.render('/index', data, req, res);
  }
}

pages/index.tsx

import fetch from 'isomorphic-unfetch';

const HomePage = (props) => {
  const { articles } = props;

  return (
    <ul>
      {articles.map((article, index) => (
        <li key={index}>{article.title}</li>
      ))}
    </ul>
  );
};

// we must define `getInitialProps` so that the NEXT.js can generate static markups
HomePage.getInitialProps = async ({ req, query }) => {
  const isServer: boolean = !!req;

  let articles;
  if (isServer) {
    // in the NEXT.js server side, we can pass the server data
    // this `query.articles` is passed from AppController
    articles = query.articles;
  } else {
    // in the NEXT.js client side, we need to fetch the same data above
    const response = await fetch('http://localhost:3000/api/articles');
    articles = await response.json();
  }

  return {
    articles,
  };
};

export default HomePage;

Conclusion

The integration of NestJS and NEXT.js is a little tricky, but if we need to host React apps with SSR, I hope it helps you :)

Related Repository

GitHub logo saltyshiomix / ark

An easiest authentication system on top of NestJS, TypeORM, NEXT.js(v9.3) and Material UI(v4).

An easiest authentication system on top of NestJS, TypeORM, NEXT.js (v9) and Material UI (v4).

Package License (MIT)

Features

  • Cross platform - Mac, Linux and Windows
  • Database synchronization with entities - powered by TypeORM
  • Server Side Rendering - powered by NEXT.js
  • API server - powered by NestJS
  • Authentication - powered by Passport
  • Material UI design

Technologies

  • Hot reloading for the developer experience :)
    • ts-node-dev - Compiles your TS app and restarts when files are modified
    • NEXT.js - The React Framework
  • Lang
  • Database
    • PostgreSQL - The World's Most Advanced Open Source Relational Database
  • ORM (Object-relational mapping)
    • TypeORM - ORM for TypeScript and JavaScript (ES7, ES6, ES5)
  • Server
    • NestJS - A progressive Node.js framework for building efficient, reliable and scalable server-side applications
      • internally using Express - Fast, unopinionated, minimalist web framework for Node.js
    • NEXT.js - The React Framework
  • Environment variables
    • dotenv - Loads environment variables from .env…




💖 💪 🙅 🚩
saltyshiomix
Shiono Yoshihide

Posted on October 10, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related