How do I use nunjucks template in nestjs framework? - node.js

How can I use nunjucks template under NestExpressApplication in right way?

For those who would want to use Nunjucks templating engine with NestJS framework and found this question, here is a minimal example:
import { NestFactory } from '#nestjs/core';
import { NestExpressApplication } from '#nestjs/platform-express';
import * as nunjucks from 'nunjucks';
import * as path from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const express = app.getHttpAdapter().getInstance();
const assets = path.join(__dirname, '..', 'assets'); // Directory with static HTML/CSS/JS/other files
const views = path.join(__dirname, '..', 'views'); // Directory with *.njk templates
nunjucks.configure(views, { express });
app.useStaticAssets(assets);
app.setBaseViewsDir(views);
app.setViewEngine('njk');
await app.listen(3000);
}
bootstrap();

import { join } from "path";
import { NestFactory } from "#nestjs/core";
import { NestExpressApplication } from "#nestjs/platform-express";
import * as nunjucks from "nunjucks";
import * as helmet from "helmet";
import { ApplicationModule } from "./app.module";
async function bootstrap() {
let options: any = {};
if (process.env.NODE_ENV === "product") options.logger = false;
const app = await NestFactory.create<NestExpressApplication>(
ApplicationModule,
options
);
app.use(helmet());
// app.useGlobalPipes(
// new ValidationPipe({
// whitelist: true,
// validationError: { target: false, value: false },
// }),
// );
app.useStaticAssets(join(__dirname, "..", "public"), { prefix: "/static/" });
const environment = nunjucks.configure(
[
join(__dirname, "..", "template"),
join(__dirname, ".", "system_template")
],
{
autoescape: true,
throwOnUndefined: false,
trimBlocks: false,
lstripBlocks: false,
watch: true,
noCache: process.env.NODE_ENV === "local" ? true : false,
express: app
}
);
app.engine("njk", environment.render);
app.setViewEngine("njk");
app.set("view cache", true);
await app.listen(process.env.APP_PORT);
}
bootstrap();

Related

Error: ENOENT: no such file or directory, open 'dist/index.html'

So, I've been trying for days to get a angular universal app running but I keep getting this issue when I try to run the server like
npm run dev:ssr
I have set my server.ts file as in the below link
https://github.com/Angular-RU/angular-universal-starter/blob/master/server.ts
My server.ts file is as below
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
// ssr DOM
const domino = require('domino');
const fs = require('fs');
const path = require('path');
// index from browser build!
const template = fs.readFileSync(path.join('.', 'dist', 'index.html')).toString();
// for mock global window by domino
const win = domino.createWindow(template);
// from server build
const files = fs.readdirSync(`${process.cwd()}/dist-server`);
// mock
global['window'] = win;
// not implemented property and functions
Object.defineProperty(win.document.body.style, 'transform', {
value: () => {
return {
enumerable: true,
configurable: true,
};
},
});
// mock documnet
global['document'] = win.document;
// othres mock
global['CSS'] = null;
// global['XMLHttpRequest'] = require('xmlhttprequest').XMLHttpRequest;
global['Prism'] = null;
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), 'dist');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run() {
const port = process.env.PORT || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
my app.server.module.ts file:
import { NgModule } from '#angular/core';
import { ServerModule } from '#angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
#NgModule({
imports: [
AppModule,
ServerModule,
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
I am using Angular 9 and at a stage where I am thinking of dropping the idea of using angular universal at all. Seems it its way too unstable to be used at the moment.
Contents of dist folder:
Does anyone here have a solution to this?
You incorrectly set the distFolder variable. The distFolder from server.ts must point to the files containing the client side app, which is dist\YourProjectName\browser in your app. Note that this configured in angular.json file.
To correct your error, try changing the distFolder path in your server.ts
const distFolder = join(process.cwd(), 'dist','YourProjectName', 'browser');

Angular 4 Server Side Rendering ngx-bootstrap carousel makes response hang

So I'm trying to implement server side rendering on my angular 4 website and I think I'm pretty close. I have gotten all routes to render server side except for 1 specific route and the default route. The routes that do not work are /home, /anythingthatisntdefined, and (no route at all loads, but does not server side render the home page).
So the no route, just isn't getting picked up by my catch all route... the anything that isn't defined I'm assuming the server is not correctly processing the default route... and the /home route not loading, just doesn't make sense to me. Any tips?
Here is my server.ts
import 'reflect-metadata';
import 'zone.js/dist/zone-node';
import { platformServer, renderModuleFactory } from '#angular/platform-server';
import { enableProdMode } from '#angular/core';
import { AppServerModuleNgFactory } from '../dist/ngfactory/src/app/app.server.module.ngfactory';
import * as express from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';
const PORT = 4000;
enableProdMode();
const app = express();
let template = readFileSync(join(__dirname, '..', 'dist', 'index.html')).toString();
app.engine('html', (_, options, callback) => {
console.log('url: ', options.req.url);
const opts = { document: template, url: options.req.url };
renderModuleFactory(AppServerModuleNgFactory, opts)
.then(html => callback(null, html));
});
app.set('view engine', 'html');
app.set('views', 'src')
app.use(express.static(join(__dirname, '..', 'dist')));
app.get('*', (req, res) => {
console.log('caught by *');
res.render('../dist/index.html', {
req: req,
res: res
});
});
app.listen(PORT, () => {
console.log(`listening on http://localhost:${PORT}!`);
});
Here is my router
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ServicesComponent } from './services/services.component';
import { ResourcesComponent } from './resources/resources.component';
import { ChurchesComponent } from './churches/churches.component';
import { GetAQuoteComponent } from './get-a-quote/get-a-quote.component';
const routes: Routes = [
{
path: 'home',
component: HomeComponent
},
{
path: 'services',
component: ServicesComponent
},
{
path: 'resources',
component: ResourcesComponent
},
{
path: 'about',
component: AboutComponent
},
{
path: 'churches',
component: ChurchesComponent
},
{
path: 'get-a-quote',
component: GetAQuoteComponent
},
{
path: '**',
component: HomeComponent
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Good news, I figured out why the one route/ random routes weren't loading. The ngx-bootstrap carousel (which I had on my home route) has an interval that needs to be disabled server side. Otherwise the route will hang forever and never load.
Reference: https://github.com/valor-software/ngx-bootstrap/issues/1353
And last, the fact of the no route not working was because of my static assets route which was intercepting the request before my express.get was.

NodeJS compiler server/client static class inheritance error

I have a few issues.
I'm currently coding a CMS using React for client side with Webpack and Express for server side with Babel.
What I am trying to do is setup a common X-CSRF-Token header on the axios module on my client side that gets a token generated from a shared file that the server side uses to verify the token.
The problem is that, when I use two different compilers, the static class inheritance doesn't work, so instead of verifying the existing generated token inside the class, it instead generates a whole new.
Would it work if I somehow managed to use the same Webpack config for both server and client side?
When I start up my server, I use this npm script:
nodemon --watch server --exec babel-node -- server/index.js
For the client side, I use Webpack HMR with this config:
import path from 'path'
import webpack from 'webpack'
const clientPath = path.join(__dirname, 'client')
//const bundlePath = path.join(__dirname, 'static', 'js')
export default {
devtool: 'eval-source-map',
entry: [
'webpack-hot-middleware/client',
clientPath
],
output: {
filename: 'bundle.js',
path: '/',
publicPath: '/'
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin()
],
module: {
loaders: [{
test: /\.js$/,
include: clientPath,
loaders: ['react-hot-loader', 'babel-loader']
}]
},
resolve: {
extensions: ['.js']
}
}
server/index.js
So in my server index file, I send the index.html file that has the bundle.js required.
import express, {app} from './app'
import config from '../config.json'
import path from 'path'
import './database'
const staticPath = path.join(__dirname, '..', 'static')
app.use((err, req, res, next) => {
if (err) {
res.status(err.statusCode || err.status || 500)
.send(err.data || err.message || {})
} else {
next()
}
})
app.use('/api', require('./api'))
app.use(express.static(staticPath))
app.get('*', (req, res) =>
res.sendFile(path.join(staticPath, 'index.html'))
)
app.listen(config.port, () =>
console.log(`Listening on port: ${config.port}`)
)
server/app.js
And in my server app file I initialize the Webpack and other middleware:
import webpackHotMiddleware from 'webpack-hot-middleware'
import webpackMiddleware from 'webpack-dev-middleware'
import expressSession from 'express-session'
import bodyParser from 'body-parser'
import webpack from 'webpack'
import express from 'express'
import cors from 'cors'
import path from 'path'
import webpackConfig from '../webpack.config'
const compiler = webpack(webpackConfig)
export const router = express.Router()
export const app = express()
.use(cors())
.use(bodyParser.urlencoded({ extended: false }))
.use(bodyParser.json())
.use(expressSession({
secret: 'test',
resave: false,
saveUninitialized: true,
cookie: { secure: true }
}))
.use(webpackMiddleware(compiler, {
hot: true,
publicPath: webpackConfig.output.publicPath
}))
.use(webpackHotMiddleware(compiler))
export default express
utils/csrf.js
This is the CSRF file that's shared between both the client and server side.
import CryptoJS from 'crypto-js'
import randomString from 'crypto-random-string'
import config from '../config.json'
export default class CSRF {
static secret: String
static csrf: String
static tokenCounter: Number
static generate(counter: Number = 10): String {
if (!this.csrf || this.tokenCounter >= (config.csrf.counter || counter)) {
// after 10 or defined times the token has been used, regenerate it
this.secret = config.csrf.secret !== '' ? config.csrf.secret : randomString(8)
let token = randomString(12)
this.csrf = CryptoJS.AES.encrypt(token, this.secret).toString()
this.tokenCounter = 0
}
return this.csrf
}
static verify(req: Object): Boolean {
const csrf = req.headers['x-csrf-token']
if (!csrf) return false
this.tokenCounter++
//const bytes = CryptoJS.AES.decrypt(csrf, this.secret)
//const decrypted = bytes.toString(CryptoJS.enc.Utf8)
return csrf === this.csrf
}
}
So in my client/index.js file, I setup the axios header like so:
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import axios from 'axios'
import Routes from './routes'
import store from './store'
import config from '../config.json'
import CSRF from '../utils/csrf'
if (config.production) process.NODE_ENV = "production"
axios.defaults.headers.common['X-CSRF-Token'] = CSRF.generate()
ReactDOM.render(
<Provider store={store}>
<Routes />
</Provider>,
document.querySelector('#root')
)
Then from a Redux action I send a post request:
import axios from 'axios'
export function userSignUpRequest(data) {
return (dispatch) => {
return new Promise((resolve, reject) => {
axios.post('/api/users/signup', data)
.then(res => res.data)
.then(data => {
if (data.status) {
resolve(data.user)
} else {
reject(data.errors)
}
})
})
}
}
And in server/api/users/signup I verify the CSRF token:
import Users from '../../database/models/users'
import Validate from '../../../utils/validate'
import CSRF from '../../../utils/csrf'
export default async function (req, res) {
if (!CSRF.verify(req)) {
return res.status(405).json({ status: false, reason: 'Invalid CSRF token' })
}
const {validationErrors, isValid} = Validate.registerInput(req.body)
if (!isValid) {
res.json({ status: false, errors: validationErrors })
} else {
const {email, username, password} = req.body
let errors = {}
try {
await Users.query().where('mail', email)
} catch(err) {
errors.email = 'This email is already taken'
}
try {
await Users.query().where('username', username)
} catch(err) {
errors.username = 'This username is already taken'
}
if (Validate.isObjEmpty(errors)) {
Users.query().insert({
mail: email, username, password
})
.then(user => res.status(200).json({ status: true, user }))
.catch(errors => res.json({ status: false, errors }))
} else {
res.json({ status: false, errors })
}
}
}
Whole source code: Github

Wepack 2 resolve alias does not work in the server-side

Wepack 2 resolve alias does not work server side
I run wepback middleware with my express server. If the server is started when the import is relative, it'll work fine:
`import from '../../foobar/stuff'`
While the server is already running and the initial server side render is ready in memory; If we change the import in the component, to use the resolve property previously defined through webpack, it'll work:
`import from 'foobar/stuff'`
Meanwhile, if the server is stopped, and re-run with this last change, where the import uses the webpack resolve property defined in the configuration, this will fail (triggers the error not found)
File structure:
[root]
node_modules
webpack.config.js
|____ src
|____ src/js
|____ src/js/modules/
|____ src/js/modules/foobar/containers|components/**/*.js
|____ src/js/modules/lorem/containers|components/**/*.js
It seems that the express server does not know how to resolve the path that it's defined in the webpack 2 resolve property (even though I pass the webpackDevConfig file to the middleware).
Here's the server.dev.js:
import express from 'express'
import path from 'path'
import superagent from 'superagent'
import chalk from 'chalk'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router'
import configureStore from './src/js/root/store'
import { Provider } from 'react-redux'
import MyApp from './src/js/modules/app/containers/app'
import Routes from './src/js/root/routes'
const myAppChildRoutes = Routes[0].routes
const app = express()
const port = process.env.PORT ? process.env.PORT : 3000
var serverInstance = null
var dist = path.join(__dirname, 'dist/' + process.env.NODE_ENV)
var config = null
const webpack = require('webpack')
const webpackHotMiddleware = require('webpack-hot-middleware')
const webpackDevConfig = require('./webpack.dev.config')
const compiler = webpack(require('./webpack.dev.config'))
var webpackDevMiddleware = require('webpack-dev-middleware')
const webpackAssets = require('./webpack-assets.json')
config = require('./config')
/**
* Process error handling
*/
process.on('uncaughtException', (err) => {
throw err
})
process.on('SIGINT', () => {
serverInstance.close()
process.exit(0)
})
app.set('views', path.join(__dirname, 'src'))
app.set('view engine', 'ejs')
app.use(webpackDevMiddleware(compiler, {
noInfo: true,
publicPath: webpackDevConfig.output.publicPath,
stats: {
colors: true,
hash: false,
version: true,
timings: false,
assets: false,
chunks: false,
modules: false,
reasons: false,
children: false,
source: false,
errors: true,
errorDetails: true,
warnings: true,
publicPath: false
}
}))
app.use(webpackHotMiddleware(compiler, {
log: console.log
}))
/**
* The Cross origin resource sharing rules
*/
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type')
res.setHeader('Access-Control-Allow-Credentials', true)
next()
})
/**
* Health check
*/
app.use('/healthcheck', (req, res) => {
res.json({
'env': {
'NODE_ENV': process.env.NODE_ENV
}
})
res.end()
})
app.use('/api/test', (req, res) => {
superagent
.get('https://jsonip.com/')
.end((err, response) => {
if (err) {
console.log('api test err', err)
}
res.send(response.body)
})
})
app.use('/assets', express.static(dist))
app.get('*', (req, res) => {
// (wip) migration to react-router v4 temporary solution
// let matches
// if (typeof routes.props.children !== 'undefined' && Array.isArray(routes.props.children)) {
// matches = routes.props.children.find((v) => {
// return v.props.path === req.url
// })
// } else {
// matches = routes.props.children.props.path === req.url
// }
let matches = true
if (!matches) {
res.status(404).send('Not found')
} else {
const preloadedState = {'foobar': 1}
// Create a new Redux store instance
const store = configureStore(preloadedState)
// Render the component to a string
const myAppHtml = renderToString(<StaticRouter context={{}} location={req.url}>
<Provider store={store}>
<MyApp routes={myAppChildRoutes} />
</Provider>
</StaticRouter>)
// Grab the initial state from our Redux store
const finalState = store.getState()
res.render('index', {
app: myAppHtml,
state: JSON.stringify(finalState).replace(/</g, '\\x3c'),
bundle: webpackAssets.main.js,
build: config.build_name,
css: '/assets/css/main.min.css'
})
}
})
serverInstance = app.listen(port, (error) => {
if (error) {
console.log(error) // eslint-disable-line no-console
}
console.log(chalk.green('[' + config.build_name + '] listening on port ' + port + '!'))
})
Finally, the webpack dev config file is:
var path = require('path')
var webpack = require('webpack')
var AssetsPlugin = require('assets-webpack-plugin')
var assetsPluginInstance = new AssetsPlugin()
var ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
context: path.resolve(__dirname, 'src'),
entry: [
'react-hot-loader/patch',
'webpack/hot/dev-server',
'webpack-hot-middleware/client',
'babel-polyfill',
'./js/index.js'
],
output: {
path: path.join(__dirname, '/dist/development'),
publicPath: '/assets/',
filename: 'js/bundle.js?[hash]'
},
devtool: 'inline-source-map',
devServer: {
hot: true,
// match the output path
contentBase: path.join(__dirname, '/dist/development'),
// match the output `publicPath`
publicPath: '/assets/'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
use: [{
loader: 'babel-loader'
}]
},
{
test: /\.scss$/,
use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
}))
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
use: [
'file-loader?name=[path][name].[ext]'
]
},
{
test: /\.(jpg|png|gif|svg)$/i,
use: [
'file-loader?name=[path][name].[ext]&emitFile=false'
]
}
]
},
plugins: [
new ExtractTextPlugin('css/[name].min.css?[hash]'),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('development')
}
}),
assetsPluginInstance
],
resolve: {
alias: {
modules: path.resolve(__dirname, 'src/js/modules')
}
}
}
Also, tried to include the path to resolve automaticaly, without success:
module.paths.unshift(path.resolve(__dirname, 'src/js'))
The process.env.NODE_PATH is:
process.env.NODE_PATH Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/johnColtrane/www/coolApp/server.js',
loaded: false,
children: [],
paths:
[ '/Users/johnColtrane/www/coolApp/src/js',
'/Users/johnColtrane/www/coolApp/node_modules',
'/Users/johnColtrane/www/node_modules',
'/Users/johnColtrane/node_modules',
'/Users/node_modules',
'/node_modules' ] }
NOTE: someone suggested babel-plugin-module-alias instead, because of some issues with the approach I want to take. I'll check it and if better then this approach, I'll post here.

System is not defined with Router Parameter - Angular 2

I am trying to implement routing with id parameter on the url such as localhost:1000/register/id
Using that path will always trigger system is not defined while other urls without parameters are working fine. I even tried following the guide from angular.io's routing format doesn't seems to fix the problem. What am I missing in my code?
app.routing.ts
import { ModuleWithProviders } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
import { RegisterComponent } from './registration/register.component';
import { CodeComponent } from './registration/code.component';
const appRoutes: Routes = [
{
path: 'register/:id',
component: RegisterComponent
},
{
path: '',
component: CodeComponent
}
];
export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes);
app.module.ts
import { NgModule } from '#angular/core';
import { BrowserModule } from '#angular/platform-browser';
import { HttpModule } from '#angular/http';
import { FormsModule } from '#angular/forms';
import { routing } from './app.routing';
import { AppComponent } from './app.component';
import { CodeComponent } from './registration/code.component';
import { RegisterComponent } from './registration/register.component';
#NgModule({
imports: [BrowserModule, HttpModule, FormsModule, routing],
declarations: [AppComponent, CodeComponent, RegisterComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
server.js
var express = require('express'),
app = express(),
mongoose = require('mongoose'),
bodyParser = require('body-parser'),
path = require('path'),
passport = require('passport'),
session = require('express-session'),
port = process.env.PORT || 1000;
db = require('./config/db');
mongoose.Promise = global.Promise;
mongoose.connect(db.url);
app.use(session({
secret: 'test',
saveUninitialized: true,
resave: true
}));
app.use(passport.initialize());
app.use(passport.session());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
require('./config/passport')(passport);
require('./routes/main')(app, passport);
app.use(express.static(path.join(__dirname)));
app.use('/node_modules', express.static(path.join(__dirname+ '/node_modules')));
console.log(path.join(__dirname+ '/node_modules'))
app.all('/*', function (req, res, next) {
res.sendFile('/view/index.html', { root: __dirname });
});
app.listen(port);
console.log('Magic happens on port ' + port);
UPDATE:
By using this.router.navigate(['/register', '1']); works perfectly, but by typing on the url localhost:1000/register/1 is not working
From the picture above, there is mouse hover showing the url to be
localhost:1000/register/node_modules/core-js....
- I think there is something I've missed in my server NodeJS side.
I've also added
app.use('/node_modules', express.static(__dirname + '/node_modules'));
But no changes
Note:
Server side (NodeJS,Express)

Resources