NestJS Angular Universal not loading "index.html" automatically on root path - nestjs

I am running an NestJS Angular Universal app on my local server using Angular 9 and Ivy. I can get everything to work when I run:
npm run serve:ssr
However, nothing loads unless I type the route manually. I would think it would automatically load "index.html" without having to type it in.
localhost:8080 ----- nothing
localhost:8080/index.html ---- works
Is there a way to modify the code to do a rewrite for the root path? I would think this would not be necessary:
main.ts
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
await app.listen(process.env.PORT || 8080);
}
// 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')) {
bootstrap().catch(err => console.error(err));
}
Or fix the problem at hand...

After days of working on this, I figured out I had a loop by including a component that was including another component which had a loop in the ts file. In my case I was subscribing to something I shouldn't have. I thought this was an IVY / NestJS compatibility problem, but turns out it was my code.

Related

Universal React: HMR for the backend (express.js)

i am been struggling for days, on this problem.
I am making an Universal React App with webpack 5.
I have enabled HMR on the client side by webpack-dev-middleware and webpack-hot-middleware
Now i want to do the same things for the backend witch is an express server.
I have tried nodemon but my biggest complain is that does auto restart the server and it doesn't match well with webpack-hot-middleware.
Then I have found webpack-hot-server-middleware witch just what i am looking for but it's not working and it is no longer maintained.
It is stuck in an infinite loop because of webpack-dev-middleware.
I have found this amazing github example linked by webpack-hot-middleware himseld on the troubleshooting section.
But again it is not quite working.
The longer it stay on this problem, I realise how much complex I try to accomplish.
Because I am on a webpack project where there two configuration (development and production) and each one does build theirs bundles.
I launch my node server on the server bundle and I want this to reload when I change the source code witch is in a another directory.
webpack-dev-middleware and webpack-hot-middleware does the job on the frontend because they both use the webpack configurations as a parameter but on backend it is much harder.
Here is what I have done so far:
Here is my main express server file:
server.js
import express from 'express';
import path from 'path';
import cors from 'cors';
import routerPage from './renderpage/routerPage.js';
import {envIsProduction, envIsDevelopment} from './envmode/envUtil.js';
import chokidar from 'chokidar';
const PORT = process.env.PORT || 8080;
let server = express();
if (envIsProduction()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( PRODUCTION ) |");
console.log("|_______________________________________|");
server.use(express.static(path.join(__dirname,'..'))); // we serve static file like the bundle-app.js to the browser from the current directory where the server is executed and we move to the top root to access the file
}
else if (envIsDevelopment()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( DEVELOPMENT ) |");
console.log("|_______________________________________|");
// TODO: Régler cohabitation webpack-dev-middleware qui rafraichit en boucle
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackHotServerMiddleware = require('webpack-hot-server-middleware');
const config = require('../../webpack.conf.dev.js');
const compiler = webpack(config);
server.use(webpackDevMiddleware(compiler, {
serverSideRender: true,
}));
server.use(webpackHotMiddleware(compiler.compilers.find(compiler => compiler.name === 'client')));
// Do "hot-reloading" of express stuff on the server
// Throw away cached modules and re-require next time
// Ensure there's no important state in there!
const watcher = chokidar.watch('.');
watcher.on('ready', function() {
watcher.on('all', function() {
console.log("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO Clearing /server/ module cache from server");
Object.keys(require.cache).forEach(function(id) {
console.log(id)
console.log("exist: " + /.[\/\\]/.test(id));
if (/.[\/\\]/.test(id)) delete require.cache[id];
});
});
});
// Do "hot-reloading" of react stuff on the server
// Throw away the cached client modules and let them be re-required next time
compiler.hooks.done.tap('done', (stats) => {
console.log("Clearing /client/ module cache from server");
Object.keys(require.cache).forEach(function(id) {
if (/[\/\\]client[\/\\]/.test(id)) delete require.cache[id];
});
})
}
server.use(cors());
server.use(express.urlencoded({extended:false}));
server.use(express.json());
server.get('/', routerPage); // when the user connect to the root of the server we send the page
server.get('/click', (req, res)=>{
console.log("IUHpzeijgàzhgzieghziêhg");
res.send("I got ur six");
});
server.get("*",function(req,res){
res.status(404).send('page not found');
}); //For all other type of request excluding the one specified here, we send back a 404 page;
server.listen(PORT, () => {
console.log(
`Server listening on \x1b[42m\x1b[1mhttp://localhost:${PORT}\x1b[0m in \x1b[41m${process.env.NODE_ENV}\x1b[0m`,
);
});
I use chokidar to watch all the file on the current directory including the file where the code is and I clear the cache to refresh the server.
Here is the tree structure of my project:
As you can see, I launch node on the bundle-server.js file but I want reload to occur when I change all the file in the serverside directory
But even the console appear, nothing change when I check.
Do you have any advices?
How can I make HMR work on the server side?
Thanks in advance for your responses.

404 (Not Found) error on calling local backend APIs in angular universal

I have implemented angular universal in my project for performance and SEO reasons! every thing works fine when I 'run ng serve' , the app can communicate with backend , but when I run 'npm run build:ssr 'and then 'ng run serve:ssr' it gives me the 404 (Not Found) for all API calls and does not render the entire app.
I would appreciate it if someone could help me .
thanks.
this is my server.ts file
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';
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/angular-universal/browser');
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(): void {
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';
If server.ts contains api
As you have mentioned here you are running npm run build:ssr and afterwards ng run serve:ssr. But about the backend, in your server.ts file there are no routes exposed for api calls in this server. So it is obvious that the result is 404. If you are trying to expose apis from the express server then you would need to define api routes. But in your given code the portion for api calls are commented.
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
I had work in similar projects. While running under localhost first you would need to expose the api and make sure that calls are being made on the right port.
If different backend for api
If you have a different server for api calls (not the given server.ts) then running ng serve would only run the angular portion of your app. So both ends (that is the angular app and server for api calls) are running separately. In this case upon running npm run build:ssr and npm run serve:ssr you are running angular-universal along with the express server. They are both running on port 4000 as your server.ts file indicates hence angular is looking for apis on the express app where there are no apis exposed and hence resulting in 404. In this case running angular-universal and the api backend on a different port is an easy solution.
Are you trying to use this server.ts (angular universal server) as backend? Or you have different server for api calls. Further clarification is needed

Executing code after all modules are resolved and imported in TypeScript

I use TypeScript for my Express app. I distributed the registration of certain endpoints across several other modules. These modules import main.ts and register them (see depFoo.ts). Unfortunately they don't seem to be registered and because app starts listening before deppFoo.ts executed it's registration!?
What is a proper way in TypeScript to fix this Heg-Enn problem? Can I register a callback that gets executed once TypeScript resolved all modules?
main.ts
depFoo.ts
main.ts
export const app: express.Application = express();
app.listen(3000, function () {
console.log("App is listening on port 3000!");
});
depFoo.ts
import { app } import "./main.ts"
app.get(...);
This tutorial gives a great explanation of the router style system with express TS. One of the cleaner ways to do it is to have a routes directory with files similar to
import * as express from "express";
export const register = ( app: express.Application ) => {
app.get( "/", ( req: any, res ) => {
res.render( "index" );
} );
};
and then in your main app file you can use
import * as routes from './routes'
...
const app = express()
routes.register(app)
Which makes your main file a whole lot cleaner and your endpoints more modular.

Express web server no response

I'm trying to run my Angular app in Server Side Rendering via Angular Universal.
This is the JS file that Angular Universal generated, I just edited the path and port:
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';
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = process.cwd();
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
// app.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 || 4001;
// 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';
The folder structure is fine, I run this on server:
> node .\server.js
Node Express server listening on http://localhost:4001
When I hit the site it just shows loading until it timeouts.
Express logs this on console:
DEPRECATED: DI is instantiating a token "ngmodule_material_carousel_MatCarouselHammerConfig" that inherits its #Injectable decorator but does not provide one itself.
This will become an error in v10. Please add #Injectable() to the "ngmodule_material_carousel_MatCarouselHammerConfig" class.
(node:6896) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
DEPRECATED: DI is instantiating a token "ngmodule_material_carousel_MatCarouselHammerConfig" that inherits its #Injectable decorator but does not provide one itself.
This will become an error in v10. Please add #Injectable() to the "ngmodule_material_carousel_MatCarouselHammerConfig" class.
Edit:
After removing the #ngmodule/material-carousel library from project I still get the Buffer warning but now I can see the website.
There is an open issue on github for that, it looks like the library does not support angular universal.
As a workaround, if you really want to use that library, you can avoid rendering the carousel if you are server side
component.html
<mat-carousel *ngIf="isBrowser" ></mat-carousel>
component.ts
import { isPlatformBrowser } from '#angular/common';
import {PLATFORM_ID} from '#angular/core';
public isBrowser: boolean;
constructor( #Inject(PLATFORM_ID) public platformId)
{
this.isBrowser = isPlatformBrowser(this.platformId);
}
The problem was the material-carousel library. I had to copy the necessary files from the project's GitHub to my project. The following files were needed:
carousel-slide.component.html
carousel-slide.component.scss
carousel-slide.component.ts
carousel-slide.ts
carousel.component.html
carousel.component.scss
carousel.component.ts
carousel.ts
_mixins.scss
These components require Angular Material so if you are not using the Angular Material in your project instead of ng add #angular/material which changes your project, run the following:
npm install #angular/material #angular/cdk #angular/animations
And finally add the necessary components to your module:
#NgModule({
declarations: [
...
MatCarouselComponent,
MatCarouselSlideComponent
],
imports: [
...
MatButtonModule,
MatIconModule
],
...
})
export class AppModule { }

How to transpile node cli app using babel

I am writing mt first cli app with node and I am facing some issues using babel to transpile my code.
Basically, the app should start an express server which does ssr for react (similar to what next does).
Somewhere in the process I use jsx syntax to render react component, so I need to transpile my code with babel.
I am familiar on how to do this with babel cli or with webpack,
howevere, I`m still facing issues implementing it for cli app.
In my package.json file I have:
"bin": {
"ssr": "./cli/ssr.js"
},
and my ssr.js file:
#!/usr/bin/env node
const server = require('../server');
const routes = require('../../routes.js');
const createStore = require('redux').createStore;
const port = process.env.PORT || 3000;
const args = process.argv;
const defReducer = function(state={}, action){
return state;
}
const configureStore = createStore(defReducer);
const instance = server(routes, configureStore, {}, {});
instance.listen(port, ()=>{
console.log(`ssr server runs on localhost://${port}`);
});
and my server.js file is just a regular express server:
const express = require('express');
const cors = require('cors');
const renderer = require('./renderer');
module.exports = (Routes, createStore=()=>null, renderOpts={}, routerContext={})=>{
const app = express();
app.use(express.static('public'));
app.use(cors());
const port = process.env.PORT || 3000;
app.get('*.js', (req, res, next) => {
req.url = req.url + '.gz';
res.set('Content-Encoding', 'gzip');
next();
});
app.all('*', (req, res) => {
const store = createStore();
const promises = matchRoutes(Routes, req.path).map(( { route } ) => {
if (typeof route.path === 'undefined') { return null; }
let ctx = {store, module:route.module, req, res}
return route.loadData ? route.loadData(ctx) : null;
});
Promise.all(promises).then(() => {
const content = renderer(Routes, req, store, renderOpts, routerContext);
if (context.url) {
return res.redirect(301, context.url);
}
if (context.notFound) {
res.status(404);
}
res.send(content);
});
});
return app;
}
inside server.js file I call renderer which does:
const content = renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={routerContext} basename= {opts.baseName || ''}>
<div>{renderRoutes(Routes)}</div>
</StaticRouter>
</Provider>
);
and this is where I get my syntax errors...
I also tried to precompile my server.js file using webpack and babel
and than link the bin command to the bundle.js output but it didn`t work
I get this error popping on the screen:
What is the correct way of using babel with cli app?
I followed a few steps here which you can find by going here https://babeljs.io/setup and clicking "CLI". I was able to transpile your server.js JSX following those steps, plus a couple extra in a fresh new folder. In order to transpile your code, here's what I did:
Created a package.json by running (used all default values)
npm init
Created src\server.js file with your small <Provider> excerpt above
Ran the following commands to install babel and the react libraries:
npm install --save-dev #babel/core #babel/cli
npm install --save-dev #babel/preset-env
npm install --save-dev #babel/preset-react
Created a .babelrc file with this single line:
{ "presets": ["#babel/preset-env", "#babel/preset-react"] }
Created build script in package.json
scripts: { "build": "babel src -d lib" }
Ran the build:
npm run-script build
And it successfully ran and transpiled the js into a new file within the lib folder. Try it out in a brand new folder and let me know how goes works for you

Resources