Opening Mongoose connection in AdonisJS provider times out - mongodb

I was following this article to use Mongo in AdonisJS 5 project.
I have an AdonisJS provider which I have created by node ace make:provider Mongo (it is registered in .adonisrc.json):
import { ApplicationContract } from '#ioc:Adonis/Core/Application'
import { Mongoose } from 'mongoose'
export default class MongoProvider {
constructor(protected app: ApplicationContract) {}
public async register() {
// Register your own bindings
const mongoose = new Mongoose()
// Connect the instance to DB
await mongoose.connect('mongodb://docker_mongo:27017/mydb')
// Attach it to IOC container as singleton
this.app.container.singleton('Mongoose', () => mongoose)
}
public async boot() {
// All bindings are ready, feel free to use them
}
public async ready() {
// App is ready
}
public async shutdown() {
// Cleanup, since app is going down
// Going to take the Mongoose singleton from container
// and call disconnect() on it
// which tells Mongoose to gracefully disconnect from MongoBD server
await this.app.container.use('Mongoose').disconnect()
}
}
My model is:
import { Schema, model } from '#ioc:Mongoose'
// Document interface
interface User {
email: string
}
// Schema
export default model(
'User',
new Schema<User>({
email: String,
})
)
Controller:
import { HttpContextContract } from '#ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
export default class UsersController {
public async index({}: HttpContextContract) {
// Create a cat with random name
const cat = new User({
email: Math.random().toString(36).substring(7),
})
// Save cat to DB
await cat.save()
// Return list of all saved cats
const cats = await User.find()
// Return all the cats (including the new one)
return cats
}
}
And it is timeouting.
It is working, when I open the connection in controller like this though:
import { HttpContextContract } from '#ioc:Adonis/Core/HttpContext'
import User from 'App/Models/User'
import mongoose from 'mongoose'
export default class UsersController {
public async index({}: HttpContextContract) {
await mongoose.connect('mongodb://docker_mongo:27017/mydb')
// Create a cat with random name
const cat = new User({
email: Math.random().toString(36).substring(7),
})
// Save cat to DB
await cat.save()
// Return list of all saved cats
const cats = await User.find()
// Close the connection
await mongoose.connection.close()
// Return all the cats (including the new one)
return cats
}
}
I have just created an AdonisJS provider, registered it in .adonisrc.json, created a contracts/Mongoose.ts with typings, and use the model in controller.
Any idea? I'm stuck for a day with this.
Thanks

I managed to resolve this issue by not storing mongoose in a variable. It seems the mongoose variable you declare in your MongoProvider is the root of your timeout error.
So I did as follow :
export default class MongoProvider {
constructor(protected app: ApplicationContract) {}
public async register() {
await mongoose.connect('mongodb://localhost:27017/dbName')
this.app.container.singleton('Mongoose', () => mongoose)
}
public async boot() {
// All bindings are ready, feel free to use them
}
public async ready() {
// App is ready
}
public async shutdown() {
await this.app.container.use('Mongoose').disconnect()
}
}

If someone would be interested:
with the help of the article author the reason why it is not working was missing Mongoose when creating the model (Mongoose.model instead of just model:
export default Mongoose.model(
'User',
new Schema<User>({
email: String,
})
)

I followed this article too, and I have the same issue you discussed. but resolved this by importing mongoose in my model a little differently.
import mongoose in the model like this import Mongoose, { Schema } from '#ioc:Mongoose' instead of import { Schema, model } from '#ioc:Mongoose'
Example:
import Mongoose, { Schema } from '#ioc:Mongoose'
// Document interface
interface User {
email: string
}
// Schema
export default model(
'User',
new Schema<User>({
email: String,
})
)

Related

Nest JS user authentication issue with parameter name

I am just learning nestjs for about a day and I came across this strange bug, probably has something to do with me not understanding what Im doing and rushing the project so please bear with me. My main issue is that while using JWT authentication, JSON coming from body is "username" and I can't change it. I want to log in using {"email":"test#gmail.com", "password": "password123"}, but instead it only accepts {"username":"test#gmail.com", "password": "password123"}. The word "username" is not defined or mentioned anywhere in my codebase
users.controller.ts
import { Controller, Get, Post, Body, Param, UseGuards } from '#nestjs/common';
import { UsersService} from './users.service';
import { CreateUserDto} from './dto/create-user.dto';
import { AuthGuard} from '#nestjs/passport';
#Controller('/users')
export class UsersController {
// constructor(private readonly usersService: UsersService) {}
constructor(private readonly userService: UsersService) {}
#UseGuards(AuthGuard('jwt'))
#Get('username')
getUserByEmail(#Param() param) {
return this.userService.getUserByEmail(param.email);
}
#Post('register')
registerUser(#Body() createUserDto: CreateUserDto) {
return this.userService.registerUser(createUserDto);
}
}
users.service.ts
import { Injectable, BadRequestException } from '#nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { Model } from 'mongoose';
import { InjectModel } from '#nestjs/mongoose';
import { HashService } from './hash.service';
import { User, UserDocument} from '../schemas/user.schema'
#Injectable()
export class UsersService {
constructor(#InjectModel(User.name) private userModel: Model < UserDocument > , private hashService: HashService) {}
async getUserByEmail(email: string) {
return this.userModel.findOne({
email
})
.exec();
}
async registerUser(createUserDto: CreateUserDto) {
// validate DTO
const createUser = new this.userModel(createUserDto);
// check if user exists
const user = await this.getUserByEmail(createUser.email);
if (user) {
throw new BadRequestException();
}
// Hash Password
createUser.password = await this.hashService.hashPassword(createUser.password);
return createUser.save();
}
}
auth.controller.ts
import { AuthService} from './auth.service';
import { Controller, Request, UseGuards, Post} from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#UseGuards(AuthGuard('local'))
#Post(`/login`)
async login(#Request() req) {
console.log(req.user, "here")
return this.authService.login(req.user);
}
}
Here is the source code https://github.com/networkdavit/pillicam_test
Any help or suggestion is highly appreciated!
I tried changing all the parameter names, user schemas, adding a DTO, I googled how to add a custom parameter name or override it, tried to find if "default username param" actually exists. Nothing has worked for me so far
It's in there username in your code. https://github.com/networkdavit/pillicam_test/blob/main/src/users/entities/user.entity.ts#:~:text=class%20User%20%7B-,username%3A%20string%3B,-password%3A%20string
You can change it.
Or you can refer to this article for JWT implementation in nest.js
Just in case anyone ever gets this problem, I found a solution.
All I had to do was to add this to my local.strategy.ts file in constructor
super({
usernameField: 'email',
passwordField: 'password'
});
The default expects a username and password, so have to modify it manually

How to save api response in MongoDB in NestJs

I am using NestJs as a backend service where I am hitting some third party API and want to save response in MongoDB. I am unable to get how can I save data in MongoDB as I have DTO class for the data I want to save.
Below is my code:
app.module.ts
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
#Module({
imports: [UserModule,
MongooseModule.forRoot('mongodb://localhost/status')],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
user.module.ts
import { HttpModule } from '#nestjs/axios';
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { ScheduleModule } from '#nestjs/schedule';
import { StatusSchema } from './schemas/status.schema';
import { UserController } from './user.controller';
import { UserService } from './user.service';
#Module({
imports:[HttpModule,
ScheduleModule.forRoot(),
MongooseModule.forFeature([{ name: 'Status', schema: StatusSchema }])],
controllers: [UserController],
providers: [UserService]
})
export class UserModule {}
status.schema.ts
import { Prop, Schema, SchemaFactory } from "#nestjs/mongoose";
import { Document } from "mongoose";
export type StatusDocument = Status & Document;
#Schema()
export class Status{
#Prop()
total:String;
}
export const StatusSchema = SchemaFactory.createForClass(Status);
status.dto.ts
export class StatusDto{
total:string;
}
user.service.ts
#Injectable()
export class UserService {
constructor(private httpService:HttpService,
private schedulerRegistry:SchedulerRegistry,
#InjectModel('Status') private readonly statusModel:Model<Status>){}
private readonly logger = new Logger(UserService.name);
async dynamicJob(){
this.logger.log("in main function");
const dat = await this.nameFun();
this.logger.warn(dat);
//Here I want to save the dat inside MongoDB
}
nameFun = async () =>{
const url = 'https://reqres.in/api/unknown';
const result = await axios.get(url);
this.logger.log("In nameFun " + result.data.total);
return result.data.total;
}
}
How can I add data inside MongoDB at specified place in above function?
Here's a working example with json placeholder data that I can test with since I don't know what your response looks like. I'm just passing in the text from the title field of the response into the total field of your Status schema.
Your 2 functions of UserService would look like this.
async dynamicJob() : Promise<Status> {
this.logger.log('in main function');
const dat = await this.nameFun();
this.logger.warn(dat);
const dto = { total: dat }; // creating dto in the form of your schema
this.logger.log(dto);
return await this.statusModel.create(dto); // saves and returns saved object
}
nameFun = async () => {
const url = 'https://jsonplaceholder.typicode.com/todos/2';
const result = await axios.get(url);
// you'll need to change this back to correct parsing of your actual response
this.logger.log('In nameFun ' + result.data.title);
return result.data.title;
};
Then the corresponding function in your UserController would look something like this, which whatever endpoint you want to use. Here I'm just using from-api.
#Get('from-api')
async getFromApi() : Promise<Status> {
return this.userService.dynamicJob();
}
Get request from this endpoint
http://localhost:3000/user/from-api/
returns the newly created document in Mongo
{
"total": "quis ut nam facilis et officia qui",
"_id": "622a1a6e990efa55c984dc4b",
"__v": 0
}

Postgres SET runtime variables with TypeORM, how to persist variable during the life of the connection between calls

I have NodeJS web server using GraphQL using 2 connections. One has admin access, the other crud access.
Underlying Postgres DB has a Row Level Security policy, i.e.:
ALTER TABLE main.user ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_isolation_policy ON main.user USING (id = current_setting('app.current_user_id')::UUID);
Before I login a user, I need to get their id from the db, then set the current_user_id variable in Postgres session while logging in.
However, when I try to fetch users back, I am expecting to get back only the logged in user, not everyone - this is how it behaves using pgAdmin. However, here I am getting the following error:
error: error: unrecognized configuration parameter "app.current_user_id"
Here is how I log a user in:
#Resolver()
export class LoginResolver {
#Mutation(() => LoginResponse)
public async login(
#Arg('email') email: string,
#Arg('password') password: string,
#Ctx() { res }: AppContext
): Promise<LoginResponse> {
try {
// get user from the admin repo so we can get their ID
const userRepository = (await adminConnection).getRepository(User)
const user = await userRepository.findOne({ where: { email } })
if (!user) throw new Error('user not found')
// using the api repo (not admin), set the variable
User.getRepository().query(`SET app.current_user_id TO "${user.id}"`)
const isValid = await bcrypt.compare(password, user.password)
if (!isValid) throw new Error('incorrect password')
if (!user.isConfirmed) throw new Error('email not confirmed')
sendRefreshToken(res, user.createRefreshToken())
return { token: user.createAccessToken() }
} catch (error) {
throw new Error(error)
}
}
}
Here is how I try to fetch back users:
#Resolver()
export class UsersResolver {
#Authorized(UserRole.admin, UserRole.super)
#Query(() => [User])
public users(): Promise<User[]> {
return User.find()
}
}
Please note that, if I remove the policy, GraphQL runs normally without errors.
The set variable is not persisting. How do I persist the variable while the user is logged in?
This approach works for me:
import { EntityManager, getConnection, getConnectionManager, getManager } from "typeorm";
import { EventSubscriber, EntitySubscriberInterface, InsertEvent, UpdateEvent, RemoveEvent } from "typeorm";
#EventSubscriber()
export class CurrentUserSubscriber implements EntitySubscriberInterface {
// get the userId from the current http request/headers/wherever you store it (currently I'm typeorm only, not as part of nest/express app)
async setUserId(mng: EntityManager, userId: string) {
await mng.query(`SELECT set_config('app.current_user_id', '${userId}', true);`)
}
async beforeInsert(event: InsertEvent<any>) {
await this.setUserId(event.manager, 'myUserId');
}
async beforeUpdate(event: UpdateEvent<any>) {
await this.setUserId(event.manager, 'myUserId');
}
async beforeRemove(event: RemoveEvent<any>) {
await this.setUserId(event.manager, 'myUserId');
}
}
Don't forget to configure the subscribers property in ormconfig.js, e.g. :
"subscribers": [
"src/subscribers/CurrentUserSubscriber.ts",
],

Next JS connection with Apollo and MongoDB

I am new to Next.js and using this example from Next.js https://github.com/zeit/next.js/tree/master/examples/api-routes-apollo-server-and-client.
However, the example is silent on MongoDB integration (also I could not find any other example for the same). I have been able to make database-connection but NOT able to use it in resolvers.
My Code
pages/api/graphql.js
import { ApolloServer } from 'apollo-server-micro'
import { schema } from '../../apollo/schema'
const MongoClient = require('mongodb').MongoClient;
let db
const apolloServer = new ApolloServer({
schema,
context: async () => {
if (!db) {
try {
const client = await MongoClient.connect(uri)
db = await client.db('dbName')
const post = await Posts.findOne()
console.log(post)
// It's working fine here
}
catch (e) {
// handle any errors
}
}
return { db }
},
})
export const config = {
api: {
bodyParser: false,
},
}
export default apolloServer.createHandler({ path: '/api/graphql' })
apollo/schema.js
import {makeExecutableSchema} from 'graphql-tools';
import {typeDefs} from './type-defs';
import {resolvers} from './resolvers';
export const schema = makeExecutableSchema({
typeDefs,
resolvers
});
apollo/resolvers.js
const Items = require('./connector').Items;
export const resolvers = {
Query: {
viewer(_parent, _args, _context, _info) {
//want to populate values here, using database connection
return { id: 1, name: 'John Smith', status: 'cached' }
},
...
}
}
I am stuck in the resolvers.js part. Don't know how to get the cached database connection inside resolvers.js. If I create a new database connection file, top-level await is not supported there, so how do I proceed?
If context is a function, whatever you return from the function will be available as the context parameter in your resolver. So if you're returning { db }, that's what your context parameter will be -- in other words, you can access it as context.db inside your resolver.

What is the proper way to do seed mongoDB in NestJS, using mongoose and taking advantage of my already defined schmas

We are using NestJS with mongoose and want to seed mongoDB.
Wondering what is the proper way to seed the database, and use the db schemas already defined to ensure the data seeded is valid and properly maintained.
Seeding at the module level (just before the definition of the Module) feels hacky and ends in threadpool being destroyed, and therefore all following mongo operations fail
I've done using the nestjs-command library like that.
1. Install the library:
https://www.npmjs.com/package/nestjs-command
2. Then I've created a command to seed my userService like:
src/modules/user/seeds/user.seed.ts
import { Command, Positional } from 'nestjs-command';
import { Injectable } from '#nestjs/common';
import { UserService } from '../../../shared/services/user.service';
#Injectable()
export class UserSeed {
constructor(
private readonly userService: UserService,
) { }
#Command({ command: 'create:user', describe: 'create a user', autoExit: true })
async create() {
const user = await this.userService.create({
firstName: 'First name',
lastName: 'Last name',
mobile: 999999999,
email: 'test#test.com',
password: 'foo_b#r',
});
console.log(user);
}
}
3. Add that seed command into your module. I've created a SeedsModule in a shared folder to add more seeds in future
src/shared/seeds.module.ts
import { Module } from '#nestjs/common';
import { CommandModule } from 'nestjs-command';
import { UserSeed } from '../modules/user/seeds/user.seed';
import { SharedModule } from './shared.module';
#Module({
imports: [CommandModule, SharedModule],
providers: [UserSeed],
exports: [UserSeed],
})
export class SeedsModule {}
Btw I'm importing my userService into my SharedModule
4. Add the SeedsModule into your AppModule
On your AppModule usually at src/app.module.ts add the SeedsModule into imports
Final
If you followed the steps in the nestjs-command repo you should be able to run
npx nestjs-command create:user
That will bootstrap a new application and run that command and then seed to your mongo/mongoose
Hope that help others too.
actually you can do it easily with onModuleInit(), here i'm using Mongoose ORM. This all done with zero dependencies, hope it helps
import { Injectable, OnModuleInit } from '#nestjs/common';
import { UserRepository } from './repositories/user.repository';
#Injectable()
export class UserService implements OnModuleInit {
constructor(private readonly userRepository: UserRepository) {}
// onModuleInit() is executed before the app bootstraped
async onModuleInit() {
try {
const res = await this.userRepository.findAll(); // this method returns user data exist in database (if any)
// checks if any user data exist
if (res['data'] == 0) {
const newUser = {
name: 'yourname',
email: 'youremail#gmail.com',
username: 'yourusername',
};
const user = await this.userRepository.create(newUser); // this method creates new user in database
console.log(user);
}
} catch (error) {
throw error;
}
}
// your other methods
}
For my case, I needed to insert seed during the tests, the best I could find is to create a seed service, imported and used only during tests.
Here is my base class using the schema model, all is needed is to extend and pass the model.
// # base.seed.service.ts
import { Model, Document } from 'mongoose';
import { forceArray, toJson } from 'src/utils/code';
export abstract class BaseSeedService<D extends Document> {
constructor(protected entityModel: Model<D>) {}
async insert<T = any>(data: T | T[]): Promise<any[]> {
const docs = await this.entityModel.insertMany(forceArray(data));
return toJson(docs);
}
}
// # utils
const toJson = (arg: any) => JSON.parse(JSON.stringify(arg));
function forceArray<T = any>(instance: T | T[]): T[] {
if (instance instanceof Array) return instance;
return [instance];
}
// # dummy.seed.service.ts
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { DummyDocument } from './dummy.schema';
#Injectable()
export class DummySeedService extends BaseSeedService<DummyDocument> {
constructor(
#InjectModel(Dummy.name)
protected model: Model<DummyDocument>,
) {
super(model);
}
}
Then inside the tests
describe('Dymmy Seeds', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [DummySeedService],
imports: [
MongooseModule.forRoot(__connect_to_your_mongodb_test_db__),
MongooseModule.forFeature([
{
name: Dummy.name,
schema: DummySchema,
},
]),
],
}).compile();
const seeder = module.get<DummySeedService>(DummySeedService);
const initData = [__seed_data_here__];
const entities: Dummy[] = await seeder.insert(initData);
expect(entities.length > 0).toBeTruthy();
});
});