Integrate nestjs with sentry - javascript

I want to integrate sentry with nest.js + express but I just found raven version but that is deprecated.
I follow the sentry docs for integrate with express but dont know how to handle the 'All controllers should live here' part.
const express = require('express');
const app = express();
const Sentry = require('#sentry/node');
Sentry.init({ dsn: 'https://5265e36cb9104baf9b3109bb5da9423e#sentry.io/1768434' });
// The request handler must be the first middleware on the app
app.use(Sentry.Handlers.requestHandler());
**// All controllers should live here
app.get('/', function rootHandler(req, res) {
res.end('Hello world!');
});**
// The error handler must be before any other error middleware and after all controllers
app.use(Sentry.Handlers.errorHandler());
// Optional fallthrough error handler
app.use(function onError(err, req, res, next) {
// The error id is attached to `res.sentry` to be returned
// and optionally displayed to the user for support.
res.statusCode = 500;
res.end(res.sentry + "\n");
});
app.listen(3000);

I just created a Sample Project on Github to answer this question:
https://github.com/ericjeker/nestjs-sentry-example
Below is a partial copy of the README file. Let me know if you have any questions.
Create the needed elements
Create Sentry module, service, and interceptor
$ nest g module sentry
$ nest g service sentry
$ nest g interceptor sentry/sentry
SentryModule
Create the SentryModule.forRoot() method and add the Sentry.init(options) in it.
Call the SentryModule.forRoot({...}) in the AppModule and integrate with your preferred configuration (I use ConfigModule and a .env file).
Add the call to the Express requestHandler middleware in the AppModule.configure().
configure(consumer: MiddlewareConsumer): void {
consumer.apply(Sentry.Handlers.requestHandler()).forRoutes({
path: '*',
method: RequestMethod.ALL,
});
}
It is important to use that middleware otherwise the current Hub will be global and
you will run into conflicts as Sentry creates a Hub by thread and Node.js is not multi-threaded.
SentryService
We want to initialize the transaction in the constructor of the service. You can
customize your main transaction there.
Note that because I inject the Express request, the service must be request scoped. You
can read more about that here.
#Injectable({ scope: Scope.REQUEST })
export class SentryService {
constructor(#Inject(REQUEST) private request: Request) {
// ... etc ...
}
}
SentryInterceptor
The SentryInterceptor will capture the exception and finish the transaction. Please also
note that it must be request scoped as we inject the SentryService:
#Injectable({ scope: Scope.REQUEST })
export class SentryInterceptor implements NestInterceptor {
constructor(private sentryService: SentryService) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// ... etc ...
}
}
As an example, I added a span. This is not necessary, but it will just make the trace nicer in the performance viewer of Sentry.
You can add more spans anywhere in your application simply by injecting the SentryService and calling startChild or by simply calling the startChild method of the current span.

For integrate sentry with nestjs we to follow this steps:
install npm i nest-raven
In main.ts
async function bootstrap() {
Sentry.init({ dsn: 'https://5265e36cb9104baf9b3109bb5da9423e#sentry.io/1768434' });
const app = await NestFactory.create(AppModule);
// middlewares
await app.listen(3000);
}
For use it to all controllers in app.module.ts
#Module({
imports: [
RavenModule,...
],
controllers: [],
providers: [{
provide: APP_INTERCEPTOR,
useValue: new RavenInterceptor({
filters: [
// Filter exceptions of type HttpException. Ignore those that
// have status code of less than 500
{ type: HttpException, filter: (exception: HttpException) => 500 > exception.getStatus() },
],
}),
}],
})
The issue was tracking here https://github.com/getsentry/sentry-javascript/issues/2269, there you can follow an example.

Related

In Nest js, I added Redis as a cache manager. And can't find any added data in Redis after calling the set function. So, am I missing something?

Node version: v14.15.4
Nest-js version: 9.0.0
app.module.ts
Here is the code.
In the app module, I am registering Redis as a cache manager.
#Module({
imports: [
CacheModule.register({
isGlobal: true,
store: redisStore,
url: process.env.REDIS_URL,
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
service.ts
The cache data method is for storing data with a key. -> the problem is the set function doesn't save anything
And get Data for returning the data by key.
#Injectable()
export class SomeService {
constructor(#Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async cacheData(key: string, data): Promise<void> {
await this.cacheManager.set(key, data);
}
async getData(key: string, data): Promise<any> {
return this.cacheManager.get(key);
}
}
It doesn't throw any error in runtime.
i has met the same problem as you,the way to fix this is using the install cmd to change the version of cache-manager-redis-store to 2.0.0 like 'npm i cache-manager-redis-store#2.0.0'
when you finish this step, the use of redisStore can be found,then the database can be linked.
The default expiration time of the cache is 5 seconds.
To disable expiration of the cache, set the ttl configuration property to 0:
await this.cacheManager.set('key', 'value', { ttl: 0 });
I had the same problem.
It looks like NestJS v9 incompatible with version 5 of cache-manager. So you need to downgrade to v4 for now, until this issue is resolved: https://github.com/node-cache-manager/node-cache-manager/issues/210
Change your package.json to have this in the dependencies:
"cache-manager": "^4.0.0",`
Another commenter also suggested lowering the redis cache version.
I am not sure why your getData function returns Promise<void> have you tried returning Promise<any> or the data type you are expecting e.g. Promise<string>. You could also try adding await.
async getData(key: string, data): Promise<any> {
return await this.cacheManager.get(key);
}
Are you sure that you are connecting to redis successfully ?. Have you tried adding password and tls (true/false) configuration ?
#Module({
imports: [
CacheModule.register({
isGlobal: true,
store: redisStore,
url: process.env.REDIS_URL,
password: process.env.REDIS_PASSWORD,
tls: process.env.REDIS_TLS
})
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Set the redis store as redisStore.redisStore (You will get an autocomplete)
CacheModule.register({
isGlobal: true,
store: redisStore.redisStore,
url: process.env.REDIS_URL,
})

Spying On/Mocking Import of an Import

I'm writing unit tests using vitest on a VueJS application.
As part of our application, we have a collection of API wrapper services, e.g. users.js which wraps our relevant API calls to retrieve user information:
import client from './client'
const getUsers = () => {
return client.get(...)
}
export default {
getUsers
}
Each of these services utilise a common client.js which in turn uses axios to do the REST calls & interceptor management.
For our units tests, I want to check that the relevant url is called, so want to spy on, or mock, client.
I have followed various examples and posts, but struggling to work out how I mock an import (client) of an import (users.js).
The closest I've been able to get (based on these posts - 1, 2) is:
import { expect, vi } from 'vitest'
import * as client from '<path/to/client.js>'
import UsersAPI from '<path/to/users.js>'
describe('Users API', () => {
beforeEach(() => {
const spy = vi.spyOn(client, 'default') // mock a named export
expect(spy).toHaveBeenCalled() // client is called at the top of users.js
})
test('Users API.getUsers', () => {
UsersAPI.getUsers()
expect(spy).toHaveBeenCalled()
})
})
but it's tripping on:
❯ async frontend/src/api/client.js:3:31
2| import store from '#/store'
3|
4| const client = axios.create({
| ^
5| headers: {
6| 'Content-Type': 'application/json'
where it's still trying to load the real client.js file.
I can't seem to mock client explicitly because the import statements run first, and so client is imported inside users.js before I can modify/intercept it. My attempt at the mocking was as follows (placed between the imports and the describe):
vi.mock('client', () => {
return {
default: {
get: vi.fn()
}
}
})
Mocking a module
vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:
// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js') // => resolves to path/to/client.js
It often helps to use path aliases here.
Spying/mocking a function
To spy on or mock a function of the mocked module, do the following in test():
Dynamically import the module, which gets the mocked module.
Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '#/users.js'
vi.mock('#/client')
describe('Users API', () => {
test('Users API.getUsers', async () => {
1️⃣
const client = await import('#/client')
2️⃣
const response = { data: [{ id: 1, name: 'john doe' }] }
client.default.get = vi.fn().mockResolvedValue(response)
const users = await UsersAPI.getUsers()
expect(client.default.get).toHaveBeenCalled()
expect(users).toEqual(response)
})
})
demo
Late to the party but just in case anyone else is facing the same issue.
I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.
import { client } from 'client';
vi.mock('client', () => {
const client = vi.fn();
client.get = vi.fn();
return { client }
});
Then in those tests calling client.get() behind the scenes as a dependency, just add
client.get.mockResolvedValue({fakeResponse: []});
and the mocked function will be called instead of the real implementation.
If you are using a default export, look at the vitest docs since you need to provide a default key.
If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.
I've accepted the above answer, as that did address my initial question, but also wanted to include this additional step I required.
In my use case, I need to mock an entire module import, as I had a cascading set of imports on API files that in turn, imported more and more dependencies themselves.
To cut this, I found this in the vuex documentation about mocking actions:
https://vuex.vuejs.org/guide/testing.html#testing-actions
which details the use of webpack and inject-loader to substitute an entire module with a mock, preventing the source file loading at all.

Tests fail when using #InjectRepository: Nest can't resolve Repository

I'm using NestJs with Typeorm, normal setup. UsersService gets the Typeorm Repository injected:
constructor(
#InjectRepository(User)
private usersRepository: Repository<User>,
) {}
In the UsersModule:
#Module({
imports:[ TypeOrmModule.forFeature([User])],
controllers: [UsersController ],
providers: [UsersService]
})
Nothing special as you can see. But the auto-generated test for UsersService fails, no matter what I do:
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
I get the following error:
Nest can't resolve dependencies of the UsersService (?). Please
make sure that the argument UserRepository at index [0] is available
in the RootTestModule context.
The solutions on Stackoverflow that I found seem to be obsolete, or over-complicated. I understand that the problem stems from the usage of #InjectRepository.
What is the solution? I tried downloading other people's fairly-similar projects, and get the same error! Both with nest 8 and 7.
Nest can't resolve the dependency because you don't provide the repository in the testing module. If you're doing unit tests you probably want to mock the repository using a custom provider:
import { getRepositoryToken } from '#nestjs/typeorm';
describe('UsersService', () => {
let service: UsersService;
let repository: Repository<User>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: getRepositoryToken(User),
useValue: {},
}
],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
})
You can provide an object, a class or a factory function, more details in the doc: https://docs.nestjs.com/fundamentals/custom-providers
Then in your tests you can mock the methods of the repository this way:
jest.spyOn(repository, 'find').mockResolvedValueOnce([])
It's not the only way to mock, but that's a simple and standard one.
The docs are pretty clear on how to write tests when using #nestjs/typeorm: https://docs.nestjs.com/techniques/database#testing
There are a bunch of samples here as well: https://github.com/jmcdo29/testing-nestjs
So I managed to "solve" this myself. I should've mentioned perhaps that i didn't intend to do any mocking, but wanted the test to work "as is"(I prefer using a test-dedicated DB, rather than mocking units. Seems more realistic to me).
So it appears i kind of misunderstood, that every call to createTestingModule() needs to make sure all relevant dependencies are created, including stuff like ORM initialization, which is usually done in the AppModule(here i'm testing a service in UserModule..). So what I did in users.service.specs.ts:
const module: TestingModule = await Test.createTestingModule({
imports: [TypeOrmModule.forFeature([User]), TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: '',
database: 'postgres',
schema: 'test-db',
entities: [User],
synchronize: true,
}), TypeOrmModule.forFeature([User])],
providers: [UsersService],
}).compile();
Notice that I had to both create the TypeOrm connection, and register the entity.
Now I understand that each test suite is totally isolated, and therefore needs all relevant dependencies to be passed to it, even if in the "original" application this code is already imported in the root module.

NestJS: How to get ExecutionContext in Middleware

We are using NestJS for our NodeJS application.
In our app we have some middlewares/guards/interceptors to create a user-request-context, validate jwt token, intercept request/response, etc.
We also implemented some custom decorators, to set metadata for our endpoints.
It's very easy to use this data in guards / intercetpors, because you have the ExecutionContext in canActivate / intercept functions.
But we are deeply missing this functionality in middlewares.
Is there any chance to get / inject the ExecutionContext in a NestJS middleware?
e.g.
export class SomeMiddleware implements NestMiddleware {
constructor(#Inject('ExecutionContext') context: ExecutionContext) {}
use(req, res, next) {
// get data from context / decorator
Reflect.getMetadata(SOME_KEY, context.getHandler());
next();
}
}

Runtime Configuration for Angular 6+ Applications

What is the recommended best practice for loading environment specific configuration during runtime of an Angular application? The Angular documentation mentions the use of APP_INITIALIZER, but that is still not early enough in the load process for things such as runtime configuration of imported modules that make use of the .forRoot() convention.
In my use case, I have an authentication service built and imported via a Core module, which is imported by the App module. The authentication library I am using (the angular-oauth2-oidc library) allows for configuration of the automatic appending of access tokens during when importing the module (see this segment). Since there are constraints in the build environment I am working with that only allows me to produce one common build package to deploy to all environments, I am unable to dynamically set values by using different environment.ts files.
One initial idea is to use the fetch API on the index.html page to load a JSON file containing the configuration onto a global variable, but since the call is asynchronous, there is a chance the configuration will not be fully loaded when the import of the Core module occurs.
This was part of my config setup to bring my app through the build pipeline and took me days. I ended up in a solution using the APP_INITIALIZER loading a REST service and build a AppConfigService for my App. I am using the same angular-oauth2-oidc library.
My Solution for this issue was not to setup the OAuthModule in its forRoot() method. It is called before any configs via APP_INITIALIZER are available - this results in undefined values when applied to the config object given to the forRoot() Method.
But we need a token in the http header. So I used a http interceptor for the attaching of the token like described here. The trick is to setup the OAuthModuleConfig in the factory. Obviously this is called after the app is initialized.
Configure Module
#NgModule({
imports: [
// no config here
OAuthModule.forRoot(),
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useFactory: authenticationInterceptorFactory,
deps: [OAuthStorage, AuthenticationErrorHandler, OAuthModuleConfig],
multi: true
}
]
})
Factory for interceptor
const authenticationInterceptorFactory = (oAuthService: OAuthStorage, authenticationErrorHandler: AuthenticationErrorHandler, oAuthModuleConfig: OAuthModuleConfig) => {
const config = {
resourceServer: {
allowedUrls: [
// Include config settings here instead
AppConfigService.settings.apiURL,
AppConfigService.settings.anotherApiURL,
]
sendAccessToken: true
},
}
return new AuthenticationInterceptor(oAuthService, authenticationErrorHandler, config);
};
I have created a library angular-runtime-config for runtime configuration loading for Angular.
Simple usage example
Your custom Configuration class:
export class Configuration {
readonly apiUrl!: string; // only example
readonly apiKey?: string; // only example
// add some other configuration parameters
}
Registering angular-runtime-config module with declaring which configuration files to load. For example, you can determine it by application URL or you can even use Angular injector in the factory or make the factory asynchronous.
import { AngularRuntimeConfigModule } from 'angular-runtime-config';
#NgModule({
...
imports: [
...
AngularRuntimeConfigModule.forRoot(Configuration, {
urlFactory: () => [ 'config/config.common.json', 'config/config.DEV.json' ]
})
],
}
Then request your Configuration class in any injection context.
#Injectable({...})
export class SomeService {
constructor(private readonly config: Configuration) {}
}

Categories

Resources