How to rewrite url path in Nestjs? - nestjs

I want to rewrite url like '/api/example' to '/example'
I've tried code below, but it does not work
import { Injectable, NestMiddleware } from '#nestjs/common';
#Injectable()
export class RewriteApiEndpointMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
req.originalUrl = req.originalUrl.replace(/^\/api/, '');
next();
}
}

I have found the solution
Step 1:
consumer
.apply(RewriteApiEndpointMiddleware)
.forRoutes('/') // <--- not the .forRoutes('*')
Step 2:
import { Injectable, NestMiddleware } from '#nestjs/common';
#Injectable()
export class RewriteApiEndpointMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
req.url = req.url.replace(/^\/api/, ''); // <--- not the .originalUrl
next();
}
}
Now it works as expected

Related

I created custom nest js guard which is running before every request and I have added a property called all() on the request in that guard

I have created a interface which is extending the request (of express)
I m adding property called all() in it
which contains the body , query and params in it
import { Request as BaseRequest } from 'express';
export interface Request extends BaseRequest {
all(): Record<string, any>;
}
this is the interface
which is extending the express request
and i m adding this all() property using the guard
this is the implementation of it
#Injectable()
export class RequestGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
this.requestHelper(context.switchToHttp().getRequest());
return true;
}
requestHelper(request: any): any {
const all = function (): Record<string, any> {
return {
...request.query,
...request.body,
...request.params,
};
};
request.all = all;
return request;
}
}
in the main.ts file i have used this guard
import { NestFactory } from '#nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '#nestjs/common';
import { RequestGuard } from './core/guards/request.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
app.useGlobalGuards(new RequestGuard());
await app.listen(3000);
}
bootstrap();
and i have tried consoling the all() property in the guard and it's working
its mean request is flowing in it
when i try to get this all() property in my controller then it showing
Cannot read properties of undefined (reading 'all')
That's how i m calling it
import {
Controller,
Get,
Post,
Param,
Body,
Req,
Res,
UseGuards,
} from '#nestjs/common';
import { RequestGuard } from 'src/core/guards/request.guard';
import { Request } from 'src/core/http/Request';
import { Response } from 'src/core/http/Response';
#UseGuards(RequestGuard)
#Controller('customers')
export class CustomersController {
constructor(private customersService: CustomersService) {}
#Get('/order-data/:id')
async OrderData(#Param('id') id: string, req: Request, #Res() res: Response) {
console.log(req.all());
const data = await this.customersService.allOrdersData(parseInt(id));
return data;
}
}
I m calling the route localhost:3000/customers/order-data/1
console.log(req.all());
It should print {id:{'1'}}
But it's giving error
Cannot read properties of undefined (reading 'all')
You're missing the #Req() for your req property in the OrderData method.

How to use mongoose.isValidObjectId as a middleware in nestjs?

I have an issue with repetitive requests for checking an Order id, if it is valid ObjectId or not. I got this error:
CastError: Cast to ObjectId failed for value "629b9fbd620dbc419a52e8" (type string) at path "_id" for model "Order"
After a lot of Googling, I found two approaches to tackle the problem, however I'll have to duplicate these codes for each service, which isn't a good idea.
First approach:
if (!mongoose.Types.ObjectId.isValid(req.params.id)) {
throw new HttpException('Not a valid ObjectId!', HttpStatus.NOT_FOUND);
} else {
return id;
}
Second approach:
if (!mongoose.isValidObjectId(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
} else {
return id;
}
I used below codes for making and using a middleware, thus I could check ID whenever a service using an id parameter.
validateMongoID.ts
import {
BadRequestException,
Injectable,
NestMiddleware,
} from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
import mongoose from 'mongoose';
#Injectable()
export class IsValidObjectId implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
// Validate Mongo ID
if (req.params.id) {
if (!mongoose.isValidObjectId(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
}
}
next();
}
}
orders.module.ts
export class OrdersModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(IsValidObjectId).forRoutes('/');
}
}
After trying as a middleware in the orders.modules.ts, I got the same error mentioned above. So, any idea to use it as a middleware?
I had to do this exact thing a couple of weeks ago.
Here is my solution. Works perfectly fine. Not a middleware, though.
id-param.decorator.ts
import { ArgumentMetadata, BadRequestException, Param, PipeTransform } from '#nestjs/common';
import { Types } from 'mongoose';
class ValidateMongoIdPipe implements PipeTransform<string> {
transform(value: string, metadata: ArgumentMetadata) {
if (!Types.ObjectId.isValid(value)) {
throw new BadRequestException(`${metadata.data} must be a valid MongoDB ObjectId`);
}
return value;
}
}
export const IdParam = (param = '_id'): ParameterDecorator => (
Param(param, new ValidateMongoIdPipe())
);
Usage
// If param is called _id then the argument is optional
#Get('/:_id')
getObjectById(#IdParam() _id: string) {
return this.objectsService.getById(_id);
}
#Get('/:object_id/some-relation/:nested_id')
getNestedObjectById(
#IdParam('object_id') objectId: string,
#IdParam('nested_id') nestedId: string,
) {
return this.objectsService.getNestedById(objectId, nestedId);
}
How it works
When using the #Param decorator you can give it transform pipes that will validate and mutate incoming value.
#IdParam decorator is just a #Param with the ValidateMongoIdPipe provided as a second argument.
I have found another way to solve it with the help of Lhon (tagged in comments).
create a file (I named it globalErrorHandler.ts) as follows:
import {
ArgumentsHost,
ExceptionFilter,
HttpException,
HttpStatus,
InternalServerErrorException,
} from '#nestjs/common';
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: InternalServerErrorException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
/**
* #description Exception json response
* #param message
*/
const responseMessage = (type, message) => {
response.status(status).json({
statusCode: status,
path: request.url,
errorType: type,
errorMessage: message,
});
};
// Throw an exceptions for either
// MongoError, ValidationError, TypeError, CastError and Error
if (exception.message) {
const newmsg: any = exception;
responseMessage(
'Error',
newmsg.response?.message ? newmsg.response.message : exception.message,
);
} else {
responseMessage(exception.name, exception.message);
}
}
}
add below line to main.ts
app.useGlobalFilters(new AllExceptionsFilter());
create another file (I named it validateMongoID.ts) as follows:
import {
BadRequestException,
Injectable,
NestMiddleware,
} from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
#Injectable()
export class IsValidObjectId implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
// Validate Mongo ID
if (req.params.id) {
if (!/^[a-fA-F0-9]{24}$/.test(req.params.id)) {
throw new BadRequestException('Not a valid ObjectId');
}
}
next();
}
}
last step: import it as a middleware in app.module.ts
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(IsValidObjectId).forRoutes('*');
}
}

Method in http-proxy-middleware does not work, how to fix it?

From one service I send a request to an address for example http://gateway:3000/users, proxied using the http-proxy-middleware library to http://webamqplib:5557/. Works if I use the function
reverse-proxy-middlewareBuilder.ts
import { Request, Response, NextFunction } from 'express';
import { createProxyMiddleware, Filter, Options, RequestHandler } from 'http-proxy-middleware';
import { MiddlewareBuilder } from '#nestjs/core';
export function ReverseProxyMiddleware(req: Request, res: Response, next: NextFunction) {
const proxy = createProxyMiddleware(req.path, {
target: 'http://webamqplib:5557/',
changeOrigin: true,
})
proxy(req, res, next)
}
все хорошо отрабатывает, в консоле этого сервиса [HPM] Proxy created: /users -> webamqplib:5557
Module -> users.module.ts
import { Request, Response, NextFunction } from 'express';
import { Injectable, NestMiddleware, Scope } from '#nestjs/common';
import { createProxyMiddleware, Filter, Options, RequestHandler } from 'http-proxy-middleware';
import configs from './config/config.json';
#Injectable()
export class ReverseProxyMiddleware implements NestMiddleware {
private proxy(path: Filter | Options, option?: Options): RequestHandler {
return createProxyMiddleware(path, option)
}
use(req: Request, res: Response, next: () => void) {
this.proxy(
req.path,
{
target: configs.users.target,
changeOrigin: true
}
)
next()
}
Using a class reverse-proxy-middlewareBuilder.ts
import { Request, Response, NextFunction } from 'express';
import { Injectable, NestMiddleware } from '#nestjs/common';
import { createProxyMiddleware, Filter, Options, RequestHandler } from 'http-proxy-middleware';
import configs from './config/config.json';
#Injectable()
export class ReverseProxyMiddleware implements NestMiddleware {
private proxy(path: Filter | Options, option?: Options): RequestHandler {
return createProxyMiddleware(path, option)
}
use(req: Request, res: Response, next: () => void) {
this.proxy(
req.path,
{
target: configs.users.target,
changeOrigin: true
}
)
next()
}
}
When requesting http://gateway:3000/users, the console still displays [HPM] Proxy created: /users -> webamqplib:5557, as with the function, but the redirect does not occur to the address as in the first example
translated with the help of google translator :)
In your code, you create a middleware for each request, instead of connecting it once, after which it will catch the requests.
In your case, you have to do this in main.ts file:
// main.ts
import { createProxyMiddleware } from 'http-proxy-middleware';
...
app.use("*", createProxyMiddleware({
target: 'http://webamqplib:5557',
changeOrigin: true,
}));
That's how it works for me.

How to send response from middleware created in a Nest fastify server?

I've created a NestJs project with Fastify, and have created a middleware for it, but I can't figure out how to send a response to the client, similar to how we could do in express, any help would be appreciated, thanks!, here's my middleware code:
import {
Injectable,
NestMiddleware,
HttpException,
HttpStatus,
} from '#nestjs/common';
#Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: any, res: any, next: Function) {
console.log('Request...', res);
// throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
next();
}
}
Looks like Fastify abstraction uses NodeJS vanila http objects (The res injected here is http,ServerResponse )
// app.middleware.ts
import { Injectable, NestMiddleware } from '#nestjs/common';
import { ServerResponse, IncomingMessage } from 'http';
#Injectable()
export class AppMiddleware implements NestMiddleware {
use(req: IncomingMessage, res: ServerResponse, next: Function) {
res.writeHead(200, { 'content-type': 'application/json' })
res.write(JSON.stringify({ test: "test" }))
res.end()
}
}
// app.module.ts
import { Module, MiddlewareConsumer, RequestMethod } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppMiddleware } from './app.middleware';
#Module({
imports: [],
controllers: [AppController],
providers: [],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AppMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL }); // apply on all routes
}
}
An example with adaptation of Daniel code to kill execution after middleware validations (cache) using express
import { BadRequestException, Injectable, NestMiddleware } from '#nestjs/common';
import { Request, Response, NextFunction } from 'express';
#Injectable()
export class CacheMiddleware implements NestMiddleware {
constructor(
private cacheService: CacheService
){}
async use(req: Request, res: Response, next: NextFunction) {
const cache = await this.cacheService.getCache(req.url)
if(cache){
res.writeHead(200, { 'content-type': 'application/json' })
res.write(cache)
res.end()
return
}
next();
}
}

How right to use the DRY principle in NestJS when creating controllers?

Good day !
I created a basic controller BaseController with basic endpoints...
import { Get, Post, Put, Delete, HttpStatus, Request, Response } from '#nestjs/common';
import { MessageCodeError } from './../index';
export class BaseController {
public dataService: any;
constructor(public DataService: any) {
this.dataService = DataService;
}
#Get('/')
public async findAndCountAll(#Request() req, #Response() res) {
const params = req.query;
const offset = Number(params.skip) || 0;
const limit = Number(params.limit) || 10;
delete params.offset;
delete params.limit;
const records = await this.dataService.findAndCountAll({ where: params, offset, limit });
return res.status(HttpStatus.OK).send({ total: records.count, data: records.rows });
}
}
I try to use it in other controllers. For example, in UserController...
import { Controller, Get, Post, Put, Delete, HttpStatus, Request, Response } from '#nestjs/common';
import { MessageCodeError } from '../common/index';
import { UserService } from './user.service';
import { BaseController } from './../common/shared/base.controller';
#Controller('users')
export class UserController extends BaseController {
constructor(public userService: UserService) {
super(userService);
}
}
I thought since I was in class UserController doing inheritance from class BaseController, then the functions (which act as endpoints) should be available in UserController. But it seems that I was wrong...
The database is OK, the connection is stable, the code is working (at least was before I decided to take out endpoints from the UserController to the BaseController). What could be the mistake ?
Annotate your BaseController with #Controller() decorator.
import { Get, Post, Put, Delete, HttpStatus, Request, Response } from '#nestjs/common';
import { MessageCodeError } from './../index';
#Controller()
export class BaseController {
public dataService: any;
constructor(public DataService: any) {
this.dataService = DataService;
}
#Get('/')
public async findAndCountAll(#Request() req, #Response() res) {
const params = req.query;
const offset = Number(params.skip) || 0;
const limit = Number(params.limit) || 10;
delete params.offset;
delete params.limit;
const records = await this.dataService.findAndCountAll({ where: params, offset, limit });
return res.status(HttpStatus.OK).send({ total: records.count, data: records.rows });
}
}

Resources