How to access request body in an Angular Server Side Rendering application? - node.js

I'm implementing an application to generate pdf using angular server side rendering. In there I want to use request body inside the angular components, this is the code sample I trying
server.ts file
server.post('/api/v1/pdf', (req, res) => {
// res.status(404).send('data requests are not yet supported');
res.render(indexHtml, { req, providers: [
{ provide: APP_BASE_HREF, useValue: req.baseUrl },
{ provide: PDFRequestService, useValue: req}
]
});
});
service to access request data
pdf-request.service.ts
#Injectable()
export class PDFRequestService {
constructor(#Inject(REQUEST) private request: Request) {}
get requestBody(): {workflowId: string} {
return this.request.body;
}
}
and import it to the app.server.module.ts
#NgModule({
imports: [
AppModule,
ServerModule,
],
providers: [
// Add server-only providers here.
PDFRequestService
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
But I don't know how to access this PDFRequestService inside the angular application, when I import the service to app.module.ts file throwing an error saying null Injecttor for REQUEST, but it's in the express engine.
Does anyone knows how to access the request data inside angular application before rendering in server side?

Finally found a way to parse the request body to angular application.
In the route controller put the a provider like below(I was using body-parser to take in request body as json)
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [
{ provide: APP_BASE_HREF, useValue: req.baseUrl },
{ provide: 'body', useValue: req.body}]
});
});
and inside AppModule we cam access it as below
export class AppModule {
constructor(
#Optional() #Inject('body') private body: any
) {
console.log(`body`, body);
}
That all. But there is another problem, when we use angular ssr rendering process happening two times, one is the server and other one in the client, so we will loose the data in client side. To solve that we can use below modules
ServerTransferStateModule
BrowserTransferStateModule
TransferState

You can simply pass the request from the server using providers.
server.get('*', async (req, res) => {
var data = 'any data';
res.render(indexHtml, {
req,
providers: [
{provide: APP_BASE_HREF, useValue: req.baseUrl},
{provide: 'REQUEST', useValue: req},
{provide: 'RESPONSE', useValue: res},
{provide: 'data', useValue: data},
],
});
});
Not only the request and response but you can also send custom data to angular components using the same process.
And in the component you can access these datas by using Inject()
constructor(
#Optional() #Inject(REQUEST) private request: Request<any>,
#Optional() #Inject(RESPONSE) private response: Response,
#Optional() #Inject('data') private data: any,
) {
}
This mechanism typically passes the data to angular components from the express server.

Related

Nestjs Swagger css not loading when deployed to vercel

Nestjs swagger ui not loading styles when deployed to vercel but works well locally
console and network requests
I added vercel.json with the following configuration and deployed to vercel.
{
"version": 2,
"builds": [
{
"src": "src/main.ts",
"use": "#vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "src/main.ts",
"methods": ["GET", "POST", "PUT", "PATCH", "DELETE"]
}
]
}
main.ts
const swaggerConfig = new DocumentBuilder()
.setTitle('Tansfun')
.setDescription('API for Tansfun')
.setVersion('1.0')
.addBearerAuth(
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'APIKey',
name: 'APIKey',
description: 'Enter API Key',
in: 'header',
},
'APIKey-auth',
)
.build();
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const document = SwaggerModule.createDocument(app, swaggerConfig);
app.useGlobalPipes(new ValidationPipe());
SwaggerModule.setup('api', app, document);
await app.listen(port);
}
bootstrap();
I used #nestjs/swagger v6
I recently came across this issue.
Fortunately I found a working working solution
The solution is a bit hacky tho
The First Solution
Is to get you api's swagger JSON file host it and use it with a swagger ui explorer
Serve your swagger JSON file statically with nestjs
Get the path to the swagger JSON file on your vercel server
Use it with a swagger ui explorer
How To Achieve Solution 1
Steps
On your local machine / development machine set the NODE_ENV variable to development.
In your .env file
NODE_ENV="development"
Create a static folder in your projects root. eg: swagger-static
Statically serve content of the swagger-static folder
here is link to documentation on serving static files with nestjs
In your app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ServeStaticModule } from '#nestjs/serve-static';
import { join } from 'path';
#Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'swagger-static'),
serveRoot: process.env.NODE_ENV === 'development' ? '/' : '/swagger',
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Every time your app starts in development you then to generate your api
s swagger json and store in a swagger.json file in the swagger-static folder in your prjects root directory
This issue on github discusses and has a solution on how to generate a swagger JSON file for your api
Below is a code snippet on how to generate the swagger.json file
In your main.ts
import { NestFactory } from '#nestjs/core';
import { SwaggerModule, DocumentBuilder } from '#nestjs/swagger';
import { AppModule } from './app.module';
import { resolve } from 'path';
import { writeFileSync } from 'fs';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('/swagger', app, document);
await app.listen(process.env.PORT || 3000);
// get the swagger json file (if app is running in development mode)
if (process.env.NODE_ENV === 'development') {
const pathToSwaggerStaticFolder = resolve(process.cwd(), 'swagger-static');
// write swagger json file
const pathToSwaggerJson = resolve(
pathToSwaggerStaticFolder,
'swagger.json',
);
const swaggerJson = JSON.stringify(document, null, 2);
writeFileSync(pathToSwaggerJson, swaggerJson);
console.log(`Swagger JSON file written to: '/swagger-static/swagger.json'`);
}
}
bootstrap();
Now every time your app starts in development the swagger JSON file with the generated
In production it would be served on your vercel domain eg: https://yourprojectname.vercel.app/swagger/swagger.json
Push you changes to vercel and test your swagger api by using the path to the swagger.json file on your server
Eg: Head to the swagger ui explorer page https://petstore.swagger.io/?_ga=2.160760958.2144886769.1670328433-858019792.1670328433#/. On the the page enter the path to your swagger.json file in the explorer input and click explore. Your swagger docs your now be loaded
The Second Solution (recommend)
Is to get missing swagger files in development and manually serve them statically on vercel (your production serve)
How To Achieve Solution 2
Steps
On your local machine / development machine set the NODE_ENV variable to development.
In your .env file
NODE_ENV="development"
Create a static folder in your projects root. eg: swagger-static
Statically serve content of the swagger-static folder
here is link to documentation on serving static files with nestjs
In your app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ServeStaticModule } from '#nestjs/serve-static';
import { join } from 'path';
#Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'swagger-static'),
serveRoot: process.env.NODE_ENV === 'development' ? '/' : '/swagger',
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Every time your app starts in development you make and http call to fetch the missing swagger ui resources on your production server.
In my case the missing files were swagger-ui-bundle.js, swagger-ui-init.js, swagger-ui-standalone-preset.js and swagger-ui.css
In your main.ts file after your app has started check if your app is in development an fetch the missing swagger resources then store them in the swagger-static folder in your root directory
import { NestFactory } from '#nestjs/core';
import { SwaggerModule, DocumentBuilder } from '#nestjs/swagger';
import { AppModule } from './app.module';
// core
import { resolve } from 'path';
import { writeFileSync, createWriteStream } from 'fs';
import { get } from 'http';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('/swagger', app, document);
await app.listen(process.env.PORT || 3000);
// get the swagger json file (if app is running in development mode)
if (process.env.NODE_ENV === 'development') {
// write swagger ui files
get(
`${serverUrl}/swagger/swagger-ui-bundle.js`, function
(response) {
response.pipe(createWriteStream('swagger-static/swagger-ui-bundle.js'));
console.log(
`Swagger UI bundle file written to: '/swagger-static/swagger-ui-bundle.js'`,
);
});
get(`${serverUrl}/swagger/swagger-ui-init.js`, function (response) {
response.pipe(createWriteStream('swagger-static/swagger-ui-init.js'));
console.log(
`Swagger UI init file written to: '/swagger-static/swagger-ui-init.js'`,
);
});
get(
`${serverUrl}/swagger/swagger-ui-standalone-preset.js`,
function (response) {
response.pipe(
createWriteStream('swagger-static/swagger-ui-standalone-preset.js'),
);
console.log(
`Swagger UI standalone preset file written to: '/swagger-static/swagger-ui-standalone-preset.js'`,
);
});
get(`${serverUrl}/swagger/swagger-ui.css`, function (response) {
response.pipe(createWriteStream('swagger-static/swagger-ui.css'));
console.log(
`Swagger UI css file written to: '/swagger-static/swagger-ui.css'`,
);
});
}
}
bootstrap();
Now every time your app starts in development the missing swagger would be fetched localy and stored in the swagger-static folder
In production file missing would be served upon request on your vercel server
Push you changes to vercel and test your swagger. Everything should be working now
try with this, set the custom js and css
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// swagger setup
const config = new DocumentBuilder()
.setTitle('Backend Generator')
.setDescription('Documentation API Test')
.setVersion('1.0')
.setBasePath('api/v1')
.addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' })
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('swagger', app, document, {
customSiteTitle: 'Backend Generator',
customfavIcon: 'https://avatars.githubusercontent.com/u/6936373?s=200&v=4',
customJs: [
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-bundle.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.min.js',
],
customCssUrl: [
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.css',
],
});
const cors = { ...CorsConfig };
app.enableCors(cors);
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
app.setGlobalPrefix('api/v1');
useContainer(app.select(AppModule), { fallbackOnErrors: true });
await app.listen(5000);
}
bootstrap();

How to access Request from factory in Nestjs?

I'm setting up a custom provider that dependes on some request properties. And I wonder if it's possible to achieve this without changing scope to Scope.REQUEST given that the request is undefined inside the factory if the scope is not REQUEST.
What is the best approach to access request from a factory under Scope.DEFAULT?
app.module.ts
...
#Module({
...
providers: [
...
{
provide: 'MY_TOKEN',
useFactory: (req: Request) => {
...
const myFunc = (arg1, arg1) => {
...
return result;
}
return myFunc;
}
}
]
})
EDIT: This is not possible without making the factory (and therefore the provider) REQUEST scoped. You basically want to make a new configuration per request, and to do that it must be REQUEST scoped
Add inject: [REQUEST], just like you would #Inject(REQUEST) for a constructor. The inject is essentially the tokens to be injected into the factory function, just like the constructor is what normally would define what is injected in a regular provider
...
#Module({
...
providers: [
...
{
provide: 'MY_TOKEN',
inject: [REQUEST],
useFactory: (req: Request) => {
...
const myFunc = (arg1, arg1) => {
...
return result;
}
return myFunc;
}
}
]
})
This will make whatever injects MY_TOKEN REQUEST scoped as well, so make sure you are aware of that

Angular 8 - cannot get httpClient POST to do its work maybe in conjunction with Socket.io?

I had a working AngularJS Frontend and a backend written in node.js' express, http and socket.io. Now I want to translate the frontend to Angular 8 and further use the old backend (because it worked well and was a ton of work).
Now I use ngx-socket-io for the chat communication and want to use the HttpClientModule of Angular for the API-requests.
app.module.ts
...
import { HttpClientModule } from '#angular/common/http';
import { SocketIoModule, SocketIoConfig } from 'ngx-socket-io';
...
const apiConfig: SocketIoConfig = { url: 'http://localhost:8000', options: {} };
#NgModule({declarations: [...], imports: [
...
HttpClientModule,
SocketIoModule.forRoot(apiConfig)
]
...
login.component.ts
import { CommunicationService } from '../services/communication.service';
...
constructor(
...
private comm: CommunicationService
) { }
submitForm() {
const formValue = this.loginGroup.value;
this.comm.checkAuthentication(formValue.userName, formValue.password);
}
communication.service.ts
import { Socket } from 'ngx-socket-io';
import { HttpClient, HttpErrorResponse } from '#angular/common/http';
...
#Injectable({
providedIn: 'root'
})
export class CommunicationService {
private api = 'http://localhost:8001/api';
constructor(private socket: Socket, private http: HttpClient) { }
checkAuthentication(username: string, password: string) {
console.log(`send: ${username}, ${password}`);
const test = this.http.post<any>(
`${this.api}/authenticate`,
{ username, password }
).pipe(
catchError(this.errorHandler)
);
console.log(test);
const test2 = this.http.get(
`${this.api}/users`
);
console.log(test2);
}
...
}
I can see the request going out, but on the server side it doesn't arrive. I would see a log entry there. If I use postman I see the request arriving, so the server still works.
I could imagine that angular has a problem with the constantly open connection on port 8000 to the socket.io and then use the same socket to throw a request on. Could that be? And yes, the site uses a chat (works on sockets) and some other features that work by api requests. So I need both options working together.
edit: I changed the port of the api to 8001 and it still gets no response. The console.log shows a nearly complete empty object:
{…}
​_isScalar: false
​operator: Object { selector: errorHandler()
, caught: {…} }
​source: Object { _isScalar: false, source: {…}, operator: {…} }
Http client returns an observable so You have to subscribe on it to catch the data , please check this ref
this.http.post<any>(
`${this.api}/authenticate`,
{ username, password }
).pipe(
catchError(this.errorHandler)
).subscribe((data) => {
console.log(data)
});
Before you can actually send the httpclient request you need to subscribe to the returned observable from the post() and get() methods. Change your checkAuthentication() method as follows:
checkAuthentication(username: string, password: string) {
console.log(`send: ${username}, ${password}`);
this.http.post<any>(
`${this.api}/authenticate`,
{ username, password }
).pipe(
catchError(this.errorHandler)
).subscribe((data) => {
console.log(data)
});
this.http.get(
`${this.api}/users`
).subscribe((data) => {
console.log(data)
});
}

Angular routing acting strange when url is manually typed

I'm building a web app using Angular 7 for the frontend and NodeJS, MongoDB and ExpressJS for the backend. The application runs as intended as long as I interact with the application to navigate, but when I manually type the path, it seems like it doesn't hit the component for the route. It works all good locally, but not on Heroku.
Here's is my app-routing.module.ts:
const routes: Routes = [{
path: "login",
component : LoginComponent
},
{
path: "registration",
component : SignupComponent
},
{
path: "votes",
component : VotesComponent
},
{
path: "votes/:website",
component : VoteComponent
},
{
path: "**",
redirectTo: "votes"
}
];
#NgModule({
imports: [CommonModule, RouterModule.forRoot(routes, { enableTracing: false})],
exports: [RouterModule]
})
export class AppRoutingModule { }
All my routes in Express is prefixed with /api/, such as:
app.get('/api/votes', (req, res, next) => {
const limit = parseInt(req.query.limit)
db.getVotes(limit).then((votes) => {
res.json(votes);
});
});
I check the jwt like this:
app.use(
checkJwt({ secret: process.env.JWT_SECRET }).unless({ path: [
{ url: "/api/authenticate", methods: ['POST'] },
{ url: "/api/votes", methods: ["GET"]}]})
);
app.use((err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
res.status(401).send({ error: 'Not authorized.' });
};
});
My call to votes is performed like this:
private baseUrl = environment.apiUrl;
this.http.get<any[]>(`${this.baseUrl}/votes`, this.httpOptions))).subscribe(data => {
this.data = data;
});
The baseUrl is defined as follows in the evironment.prod.ts:
export const environment = {
production: true,
apiUrl: '/api'
};
Here comes the problem. If I go to my deployment on Heroku and access https://myurl.com I'm redirected to https://myurl.com/votes and I can see the requested url is https://myurl.com/api/votes, so thats all fine and I get my angular application with all the data. But if I manually type in https://myurl.com/votes I get JSON back in the browser with error "Not authorized", and I can see that the requested URL is https://myurl.com/votes, instead of https://myurl.com/api/votes. Any ideas whats causing this? It seems like Angular is not routing it properly. When it's typed manually it doesn't hit any of my components.
For non-match route path, the router configuration should include below one.
{
path: "",
component: "404NotFoundComponent"
}

Nest.JS use AuthGuard as Middleware for GraphQL

I'm trying to make secure my GraphQL endpoint with passportJS in order that every call to this endpoint uses the AuthGuard for validating the token and setting the user on request.user, just as it does in a controller with this code:
#Get('findAll')
#UseGuards(AuthGuard('jwt'))
findAll(#Req() request): Promise<Array<Thing>> {
return this.thingService.findByUser(request.user.email);
}
The thing is I want to use it in the graphQL endpoint, which is created like this:
consumer
.apply(graphiqlExpress({ endpointURL: '/graphql' }))
.forRoutes('/graphiql')
.apply(
graphqlExpress(req => ({ schema, rootValue: req })),
¿?,
)
.forRoutes('/graphql');
I suppose I can just set it like a middleware function after the graphqlExpress function, but I have not been successful. Any thoughts?
Thank you in advance!
Edit
As a workaround I have implemented the solution proposed on Nest Docs where it uses the #UseGuard in every query/mutation that must be protected.
However, I want to protect the entire endpoint so that the guard is not called for every protected resolver, but only once on the main request. Is this even possible?
This technically is possible, but it's a pretty sloppy thing to write, and there's absolutely no guarantees it will work with Fastify so heads up. The meat of the functionality comes from the module where you implement the middleware. I ended up doing this all with the AppModule which I do not suggest (at least not all of the code there), but it works nonetheless.
You need to make the guard a custom provider so it can be injected into any context.
Then you need to mock up the ExecutionContext using req, res, next. This is easier said than done if you want type safety, but if you don't care about that (which I didn't for this) then slap up an as any and call it a day.
After that, in the middleware consumer you run the apply and make use of this.guard.canActivate with that mock ExecutionContext you created. Make this middleware async and await the canActivate call. Check that it comes back as true and if not then throw new <ErrorOfYourChoice>() and boom. It's set up. The code would look (vaguely) like this:
import {
BadRequestException,
CanActivate,
Inject,
MiddlewareConsumer,
Module,
NestModule,
} from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppResolver } from './app.resolver';
import { GraphQLModule } from '#nestjs/graphql';
import { JwtModule } from '#nestjs/jwt';
import { AuthGuard, PassportModule } from '#nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
#Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: true,
}),
JwtModule.register({ secret: 'secret' }),
PassportModule.register({ defaultStrategy: 'jwt' }),
],
controllers: [AppController],
providers: [
AppService,
AppResolver,
JwtStrategy,
{ provide: 'CustomGuard', useClass: AuthGuard() },
],
})
export class AppModule implements NestModule {
constructor(#Inject('CustomGuard') private readonly guard: CanActivate) {}
configure(consumer: MiddlewareConsumer) {
consumer
.apply(async (req, res, next) => {
const canActivate = await this.guard.canActivate({
switchToHttp: () => ({
getRequest: () => req,
getResponse: () => res,
getNext: () => next,
}),
} as any);
if (canActivate) {
next();
} else {
throw new BadRequestException();
}
})
.forRoutes('graphql');
}
}
You can check this repository for everything wired up and working. Login with POST /login -d 'username=test1&password=changeme', get the JWT and play around with it as you like.
However, I want to protect the entire endpoint so that the guard is not called for every protected resolver, but only once on the main request. Is this even possible?
I was able to get a middleware function to resolve on every query/mutation by using the reference global approach from NestJS here: https://docs.nestjs.com/graphql/field-middleware#global-field-middleware.

Resources