Nest js getting session data from the memory store - node.js

I was wondering if there is a method to access directly the session memory store and get the session data for a particular session id i'm using express-session package with the nest js freamwork.

Hope you found answers to your question since then. I came up with the following technique which is using NestJS DI system.
Background
According to NestJS official session documentation you can register a session middleware inside your main.ts file.
According to ExpressJS official session middleware documentation (the session(options) on top of the page), you can specify the session store that will be used by the middleware. If its omitted then the default MemoryStore is used.
All the session stores shares a common interface, so its a interchangeable part of your application, which makes a good candidate them to be provided as an injection token by a module. See a list of session stores advised by expressjs.
Implementation details
Create a new module (e.g. SessionModule), then add it to the imports array in your AppModule.
Next we create the injection token for the session store.
import { MemoryStore } from 'express-session';
import { SESSION_STORE } from './session.const';
#Module({
providers: [
{
provide: SESSION_STORE,
useFactory: () => {
return new MemoryStore();
},
},
],
exports: [SESSION_STORE],
})
export class SessionModule {}
where session.const.ts contains a simple export statement
export const SESSION_STORE = 'SESSION_STORE';
Now inside main.ts file, you can get a reference to the session store to setup session middleware.
const store = app.get(SESSION_STORE)
app.use(express.session({
store,
// ... other options
}
));
If you want to access sessionStore in other services / controllers you have to import the SessionModule to the module that declares them.
ex.
#Module({
imports: [SessionModule],
controllers: [UserController]
})
export class UserModule {}
// inside user controller you can inject a reference
// to the session store in the constructor
#Controller('user')
export class UserController {
constructor(#Inject(SESSION_STORE) private sessionStore) {}
}
With the sessionStore property you can get any stored session with the following method documented in the linked expressjs documentation. store.get(sid, callback)

Related

Use TypeORM repository or method on a service from outside any module

I have a nestjs app that has an AuthService which has these parts:
export class AuthService {
constructor(
#InjectRepository(User)
private readonly userRepo: Repository<User>,
) {}
async updateFromInternally () {
...
}
I have another file which is, crucially, outside of any module, which contains a number of helpful functions relating to Google oauth. For example, this file initiates Google's oauth2 client like so:
export const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
process.env.GOOGLE_CLIENT_REDIRECT
);
This file also has a listener function which I found in Google's documentation as a way to catch when my use of Google's oauth2 client automatically uses a refresh token to obtain a new access token:
oauth2Client.on('tokens', async (tokens) => {
[****]
})
At [****], I need to query in my database for a particular user and update them. Either of these conceptually work:
I somehow get userRepo into this file right here and use it to query + update
I somehow call updateFromInternally in AuthService from here
But I don't know how to interact with either TypeORM repositories or methods within services from outside of any module in nestjs! Can I do either of these?
The first question is why you're using nestjs?
You can access the internals of nestjs by using the instantiated app;
In your main.ts file you have something like this:
const app = await NestFactory.create(AppModule);
You can access the services or DataSource from the app by using the get method.
import {DataSource} from 'typeorm'
const dataSource = app.get<DataSource>(DataSource)
// or custom service
const someServices = app.get<SomeService>(SomeService)
you just need a way to export app from main.ts and import it in your outside world.
for example:
// main.ts
let app;
async bootstrap() {
app = await NestFactory.create(AppModule);
}
export const getApp = () => app;
bootstrap()
and in your other file
// outside.ts
import {getApp} from './main.ts'
const app = getApp()
I didn't write the whole logic, but I think it would give you an idea of what you need to do.
But in my opinion, it's the worst thing you can do. You're using 'nestjs` and try to respect its philosophy.
Just write a module that handles all the work you want.
I figured out #1:
import {getRepository} from "typeorm";
oauth2Client.on('tokens', async (tokens) => {
const gaRepo = getRepository(Googleauth);
// Can now use gaRepo like you do in a service
})

How to reference the app instance in a module in Nest.js

I'm working on a project that's using multiple Nest repos, around 4.
Every repo needs to implementing logging to log things like
Server lifecycle events
Uncaught errors
HTTP requests/responses
Ideally, I'd like to package everything up into a module which I can publish to my company's NPM organization and just consume directly in each of my projects.
That way, it would take very minimal code to get logging set up in each project.
One of the things I'd like to log in my server lifecycle event is the server's url.
I know you can get this via app.getUrl() in the bootstrapping phase, but it would be great to have access to the app instance in a module's lifecycle hook like so.
#Module({})
export class LoggingModule implements NestModule {
onApplicationBootstrap() {
console.log(`Server started on ${app.getUrl()}`)
}
beforeApplicationShutdown() {
console.log('shutting down')
}
onApplicationShutdown() {
console.log('successfully shut down')
}
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggingMiddleware).forRoutes('*')
}
}
Is this possible?
There's no way (besides hacky ones, maybe) to access the app itself inside modules.
As you can see here, app.getUrl() uses the underlying HTTP server. Thus I guess you can retrieve the same data using the provider HttpAdapterHost.
Ï thought I'd chime in and offer one of the hacky solutions. Only use this, if there is absolutely no other way or your deadline is coming in an hour.
Create a class that can hold the application instance
export class AppHost {
app: INesApplication
}
And a module to host it
#Module({
providers: [AppHost]
exports: [AppHost]
})
export class AppHostModule {}
In your bootstrap() function, retrieve the AppHost instance and assign the app itself
// after NestFactory.create() ...
app.select(AppHostModule).get(AppHost).app = app;
Now, the actual application will be available wherever you inject AppHost.
Be aware, though, that the app will not be available inside AppHost before the whole application bootstraps (in onModuleInit, onApplicationBootstrap hooks or in provider factories), but it should be available in shutdown hooks.
Not sure is that hacky... I'm using this to prevent the server from starting in case of pending migrations.
// AppModule.ts
export class AppModule implements NestModule {
app: INestApplication;
async configure(consumer: MiddlewareConsumer) {
if (await this.hasPendingMigrations()) {
setTimeout(()=> {
this.logger.error("There are pending migrations!")
process.exitCode = 1;
this.app.close();
}, 1000);
}
//...
}
public setApp(app: INestApplication) {
this.app = app;
}
//...
}
//main.ts
const app = await NestFactory.create(AppModule, {
logger: config.cfgServer.logger,
});
app.get(AppModule).setApp(app);

Cannot set property 'userId' of undefined in session variable

Cannot read set property of 'userId' of undefined is a classic error that has been experienced in Express frameworks and documentation here covers what to do about it, but how do you resolve this issue in a NestJS application?
When you get an error message that says Cannot set property 'userId' of undefined you want to look at what is going on with your cookie-session.
Do you have a cookie-session installed?
The error is being thrown whenever you try to set the user id property on the users' session object, because that cookie middleware is not installed or it's not running, you get undefined.
So when you try to set a property on undefined, you get this classic error message.
So your cookie-session may not be set up.
Enough answers were given in a plain ExpressJS API, but what if you are working with NestJS? Well, here is the NestJS way to resolve this.
Import the following to your app.module.ts file:
import { Module, ValidationPipe } from '#nestjs/common';
import { APP_PIPE } from '#nestjs/core';
Go down to your list of providers and inside the array and a brand new object:
providers: [
AppService,
{
provide: APP_PIPE,
useValue: new ValidationPipe({
whitelist: true,
}),
},
],
So what does this really do? It says whenever we create an instance of the app module, automatically make use of it. Apply it to every incoming request that flows to the application, run it through the instance of a class. That's how to set up a global pipe, but you have to set up the cookie session middleware into a global middleware.
You need to import the following to the same file:
import { MiddlewareConsumer, Module, ValidationPipe } from '#nestjs/common';
At the bottom, add the following:
export class AppModule {
configure(consumer: MiddlewareConsumer) {}
}
The configure function gets called automatically whenever the application starts listening for incoming traffic. So inside of here I can set up some middleware that will run on every single incoming request.
To do, we call or reference consumer.apply() like so:
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(
cookieSession({
keys: ['dfghjkl'],
}),
);
}
}
I then need to ensure I add in a require statement for cookie session at the top:
const cookieSession = require('cookie-session');
And at the bottom also add:
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(
cookieSession({
keys: ['dfghjkl'],
}),
)
.forRoutes('*');
}
}
That means that I want to make use of the middleware on every single incoming request that comes into the application. That should be it.

When does session property (express session data object) gets added to request object when using express-session middleware?

When I use express-session middleware, when does the session property in request object(request.session) gets available?
Does it always stay available since the beginning when the server starts? when no session is made (no login) then session data is empty but session property itself is always available on request object. Is is this case? OR
does session object gets attached to request only when someone logs in? and when no user is logged in then request.session is always undefined . is it this?
The intention of this question is to know which, in typescript, out of the follow two type definition I have to use:
(one is using session? and in another session is used)
import { SessionData } from "express-session";
declare module "express-session" {
interface SessionData {
userId: string;
}
}
export interface MyContext {
session: SessionData;
}
OR
import { SessionData } from "express-session";
declare module "express-session" {
interface SessionData {
userId: string;
}
}
export interface MyContext {
session?: SessionData;
}
After long waiting I got the answer in Ben Awad discord channel.
Here it is
Session middleware is always defined, if no session exists on the request it'll return an empty object.
So the option-1 type definition is right, i.e.,
import { SessionData } from "express-session";
declare module "express-session" {
interface SessionData {
userId: string;
}
}
export interface MyContext {
session: SessionData;
}

Nestjs wrapping external module in forRootAsync and inject instance

I've been developing a wrapper module for nestjs based on a nodejs module. I created a static forRoot method in order to get the configuration. I created such a prodiver within the forRoot method:
const MyProvider = {
provide: PROVIDER_TOKEN,
useValue: new MyClass(options),
};
I also export it, so in consumer module it's easy to inject it in order to access to all methods of nodejs module. Besides, I am able to wrap up all methods of that module into my service methods. So, the following code give me access to the main module's instance:
constructor(#Inject(PROVIDER_TOKEN) private readonly myClass: MyClass) {}
Then I decided to create a forRootAsync method that can handle getting configuration with useFactory. Now this is my provider in forRootAsync method:
const MyProvider= {
provide: PROVIDER_TOKEN,
useFactory: optionsAsync.useFactory,
inject: optionsAsync.inject || []
};
But this time if I inject PROVIDER_TOKEN to the service, this is simply the configuration object (that I pass from the consumer module). So I guess I should create the instance within constructor. Maybe something like this:
constructor(#Inject(PROVIDER_TOKEN) private readonly myClass) {
if(!this.myClass typeof MyClass) {
this.myClass = new MyClass(this.myClass);
}
}
By this, I can't access the instance of the main module in the consumer modules by injecting PROVIDER_TOKEN token. The goal is to access all methods of that module without having to wrap all the methods up. Any idea?
We should handle this with two providers. In the first one, we pass the factory provider as following:
{
provide: HTTP_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
Then we create another provider which injects the first provider (which nestjs resolve the dependency at that point):
{
provide: AXIOS_INSTANCE_TOKEN,
useFactory: (config: HttpModuleOptions) => Axios.create(config),
inject: [HTTP_MODULE_OPTIONS],
},
Here is the example.

Resources