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)
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
-
Definition
Panel:
-
Priviledges
Panel:
- 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
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
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).
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…