How override globalpipes. I tried this one, but not work :(
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
And this is how I override the global.
// mycontroller
#Put(":id")
#UsePipes(new ValidationPipe({ groups: ["must_only"] }))
async change(#Body() userDTO: UserDTO, #Param('id') id) {
//.... some code
}
Related
My main.ts looks like this :
import { NestFactory } from '#nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '#nestjs/platform-fastify';
import { Logger } from 'nestjs-pino';
import { processRequest } from 'graphql-upload';
import { AppModule } from './app.module';
async function bootstrap() {
const adapter = new FastifyAdapter();
const fastifyInstance = adapter.getInstance();
fastifyInstance.addContentTypeParser('multipart', (request, done) => {
request.isMultipart = true;
done();
});
fastifyInstance.addHook('preValidation', async (request: any, reply) => {
if (!request.raw.isMultipart) {
return;
}
request.body = await processRequest(request.raw, reply.raw);
});
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
adapter,
{ bufferLogs: true },
);
app.useLogger(app.get(Logger));
app.enableCors();
await app.listen(parseInt(process.env.SERVER_PORT || '3000', 10), '0.0.0.0');
}
bootstrap();
According to the fastify doc the body limit is 1MiB by default, however I want it to be larger. So I tried like this :
const adapter = new FastifyAdapter({ bodyLimit: 124857600 }); but I still get the same problem with my payload being too large.
Try to add this when you are creating the app
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({ bodyLimit: 10048576 }),
I am running into the issue Error querying the database: db error: FATAL: sorry, too many clients already and I am convinced it is because a new instance of the app is being instantiated for every test suite. I have attempted to break the app creation out into a helper file, and that file looks as follows
import { INestApplication } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import { AppModule } from '../../src/app.module';
import { PrismaService } from '../../src/prisma.service';
declare global {
var app: INestApplication | undefined;
}
export const getApp = async () => {
if (global.app) {
return global.app;
}
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
providers: [PrismaService],
}).compile();
const app = moduleFixture.createNestApplication();
await app.init();
global.app = app;
return app;
};
This however does not work, when I add console logs, I can see that app is being instantiated for every test suite.
This is how my typical before hook looks
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
I had the same issue and solved it by using Jest's globalSetup and globalTeardown options. I create one instance of the app and seed the DB before running the tests, and destroy it and run the teardown when they've finished. I use a global variable to reference the app instance.
In my jest-e2e.json:
{
...
"globalSetup": "<rootDir>/test/test-setup.ts",
"globalTeardown": "<rootDir>/test/test-teardown.ts"
}
test-setup.ts:
import * as fs from 'fs';
import { FastifyAdapter, NestFastifyApplication } from '#nestjs/platform-fastify';
import { Test } from '#nestjs/testing';
import { AppModule } from './../src/app.module';
import { WriteDataSource } from './../src/database/database.module';
module.exports = async () => {
const moduleRef = await Test.createTestingModule({
imports: [ AppModule ]
})
.compile();
global.app = moduleRef.createNestApplication<NestFastifyApplication>(
new FastifyAdapter()
);
await global.app.init();
await global.app.getHttpAdapter().getInstance().ready();
const seedQuery = fs.readFileSync(__dirname + '/scripts/database-seed.sql', { encoding: 'utf-8' });
await WriteDataSource.manager.query(seedQuery);
};
test-teardown.ts:
import * as fs from 'fs';
import { WriteDataSource } from '../src/database/database.module';
module.exports = async () => {
const teardownQuery = fs.readFileSync(__dirname + '/scripts/database-teardown.sql', { encoding: 'utf-8' }).replace(/\n/g, '');
await WriteDataSource.query(teardownQuery);
await WriteDataSource.destroy();
await global.app.close();
};
I solved it by exporting the server instance and everything else i needed:
let server: Server
let app: INestApplication
let testService: TestService
export const initServer = async () => {
const module = Test.createTestingModule({
imports: [AppModule, TestModule]
})
const testModule = await module.compile()
app = testModule.createNestApplication()
app.useGlobalPipes(
new ValidationPipe({
whitelist: true
})
)
server = app.getHttpServer()
testService = app.get(TestService)
await app.init()
}
export { app, server, testService }
Here i exported the function to clear all my databases to use where needed:
export const clearAllDatabases = async () => {
models.map(async (model: any) => {
await model.destroy({
where: {},
truncate: true,
cascade: true
})
})
}
import { app, initServer } from '#tests/resources/config/test-server'
import { clearAllDatabases } from '#tests/resources/config/clear-databases'
export const setupTestData = async () => {
await initServer()
await clearAllDatabases()
await userRegister()
await app.close()
}
My prisma.service.ts looks like this:
#Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
}
According to Prisma docs I am supposed to put them outside the context of the request handler. That would be the app that I create on main.ts. Putting a middleware there before the app itself is defined looks wierd to me and doesn't work. I'd prefer to put it on the prisma.service.ts file itself
Not sure whether this is the "best" place to register them, but we do it in the constructor of the service along with the logging configuration and it works:
import { INestApplication, Injectable, Logger, OnModuleInit } from "#nestjs/common";
import { Prisma, PrismaClient } from "#prisma/client";
import { ConcurrencyErrorMiddleware } from "./concurrency-error.middleware";
#Injectable()
export class PrismaService extends PrismaClient<Prisma.PrismaClientOptions, "query"> implements OnModuleInit {
private readonly logger = new Logger(PrismaService.name);
constructor() {
super({ log: [{ emit: "event", level: "query" }] });
this.logger.log(`Prisma v${Prisma.prismaVersion.client}`);
this.$on("query", (e) => this.logger.debug(`${e.query} ${e.params}`));
this.$use(ConcurrencyErrorMiddleware());
}
async onModuleInit(): Promise<void> {
await this.$connect();
}
async enableShutdownHooks(app: INestApplication): Promise<void> {
this.$on("beforeExit", async () => {
await app.close();
});
}
}
// An example of such a middleware.
import { Prisma } from "#prisma/client";
export function ConcurrencyErrorMiddleware<T extends Prisma.BatchPayload = Prisma.BatchPayload>(): Prisma.Middleware {
return async (params: Prisma.MiddlewareParams, next: (params: Prisma.MiddlewareParams) => Promise<T>): Promise<T> => {
const result = await next(params);
if (
(params.action === "updateMany" || params.action === "deleteMany") &&
params.args.where.version &&
result.count === 0
) {
throw new ConcurrencyError();
}
return result;
};
}
I'm using nodejs with typescript, typeorm and inversify to manage dependency injection and inversify express utils to handle controllers, when I send a response inside then or catch block only returns a 204 no content response, but if I send the response out of the promise it works, someone who has worked with inversify know what could be happening?
User Controller:
#controller("/user")
export class UserController implements interfaces.Controller {
constructor(#inject(TYPES.UserService) private _userService: IUserService) {}
#httpGet("/")
public GetAll(#request() req: Request, #response() res: Response) {
this._userService
.GetAll()
.then((users) => res.send(users))
.catch((error) => res.send(error));
}
}
User Service:
#injectable()
export class UserService implements IUserService {
private readonly _userRepository: IUserRepository;
constructor(#inject(TYPES.UserRepository) userRepository: IUserRepository) {
this._userRepository = userRepository;
}
public GetAll(): Promise<Array<User>> {
return this._userRepository.GetAll();
}
}
User repository:
#injectable()
export class UserRepository implements IUserRepository {
public GetAll(): Promise<Array<User>> {
const userRepository = getRepository(User);
return userRepository.find();
}
Container and server config:
export abstract class ServerConfig {
protected app: Application;
constructor() {
this.Config();
}
private Config(): void {
const container = new Container();
container.bind<IUserService>(TYPES.UserService).to(UserService);
container.bind<IUserRepository>(TYPES.UserRepository).to(UserRepository);
const server = new InversifyExpressServer(container);
server.setConfig((app) => {
app.use(cors());
app.use(helmet());
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(morgan("dev"));
dotenv.config();
app.set("port", process.env.PORT || 8000);
});
this.app = server.build();
}
public Start(): void {
this.app.listen(this.app.get("port"), () => {
console.log(`Server on port ${this.app.get("port")}`);
});
}
}
you need to return the promise or just await the response from the service. Something like this:
#controller("/user")
export class UserController implements interfaces.Controller {
constructor(#inject(TYPES.UserService) private _userService: IUserService) {}
#httpGet("/")
public async GetAll(#request() req: Request, #response() res: Response): Response {
try {
const users = await this._userService.GetAll();
return res.status(200).json(users);
} catch(error) {
return res.status(500).json(error)
}
}
}
I have a NestJS backend with CSRF protection and an endpoint to get the CSRF token. I'm getting TypeError: req.csrfToken is not a function when testing this endpoint with jest and supertest.
My code is like this:
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(cookieParser());
app.use(csurf({ cookie: true }));
await app.listen(process.env.BACKEND_SERVER_PORT);
}
// authController.ts
import { Controller, Req, Get } from "#nestjs/common";
import { Request } from "express";
import * as AuthDto from "./modules/auth/dto/auth.dto";
#Controller("auth")
export class AppController {
constructor() {}
#Get("csrf")
async getCsrfToken(#Req() req: Request): Promise<AuthDto.CsrfOutput> {
return { csrfToken: req.csrfToken() }; // <-- ERROR HAPPENS HERE
}
}
// app.controller.spec.ts
import { Test, TestingModule } from "#nestjs/testing";
import { INestApplication } from "#nestjs/common";
import request from "supertest";
import AppModule from "../src/app.module";
describe("AppController (e2e)", () => {
let app: INestApplication;
let server: any;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
server = app.getHttpServer();
});
it("/csrf (GET)", () => {
return request(server).get("/auth/csrf").expect(200).expect("hello");
// I'll add more validations here once I get past this error
});
});
I believe this may be related with the test itself, since this endpoint is working fine when called by an external client (our frontend application or Postman). It just doesn't work with supertest.
Anybody has an idea why? Thanks.
You only register csurf in main.ts, but your test uses AppModule directly. AppModule doesn't register csurf on its own. Therefore, when the test creates your AppModule, it doesn't have the necessary middleware.