I'm stating learning about Nestjs and i start a basic "task app", but i start using mongoose on my project and show an error:
> Potential solutions:
- If TaskModel is a provider, is it part of the current AppModule?
- If TaskModel is exported from a separate #Module, is that module imported within AppModule?
#Module({
imports: [ /* the Module containing TaskModel */ ]
})
i try diffrents ways for solving it, but im soo noob on this tool,so is soo frustrating can't found how to solve this error.. pls help a noob on this framework
tasks.service.ts
import { InjectModel } from "#nestjs/mongoose";
import { Injectable } from '#nestjs/common';
import { Task } from './interface/ITasks';
import { Model } from "mongoose";
#Injectable()
export class TasksService {
constructor(#InjectModel('Task') private taskModel: Model<Task>) { }
async getTasks() {
return await this.taskModel.find()
}
async getTask(id: string) {
return await this.taskModel.findById(id)
}
}
task.module.ts
import { TaskSchema } from './schema/task.schema';
import { MongooseModule } from '#nestjs/mongoose';
import { Module } from '#nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
#Module({
imports: [MongooseModule.forFeature([
{ name: 'Task', schema: TaskSchema }
], 'Tasks'),
],
controllers: [TasksController],
providers: [TasksService]
})
export class TasksModule { }
task.controller.ts
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './DTO/create-task.dto';
import { Controller, Get, Post, Put, Delete, Body, Param } from '#nestjs/common';
import { Task } from './interface/ITasks';
#Controller('tasks')
export class TasksController {
constructor(private taskService: TasksService) { }
#Get() // Decorador
getTasks(): Promise<Task[]> {
return this.taskService.getTasks()
}
#Get(':id') // Decorador
getTask(#Param('id') id: string) {
return this.taskService.getTask(id)
}
#Post('/newTask')
postTask(#Body() task: CreateTaskDto): { title: string, description: string, done: boolean } {
console.log(task.title, task.description, task.done)
return task
}
#Put(':id')
updateTask(#Body() task: CreateTaskDto, #Param('id') id) {
console.log(task)
console.log(id)
let action = task
return action
}
#Delete(':taskId')
deleteTask(#Param('taskId') taskId): { status: string, id: string } {
let action = { status: "Deleted task", id: taskId }
console.log(action);
return action
}
}
task.schema.ts
import { Schema } from "mongoose";
export const TaskSchema = new Schema({
title: String,
decription: String,
done: Boolean
})
In the future, please show the entire error as it will be more helpful. This partial message is enough for some, but maybe not for everyone.
According to the error, a potential solution would be to add the TaskModel to the AppModule. This leads me to believe you have TasksService in your AppModule's providers array. If that is the case, what is happening is Nest is trying to instantiate two instances of your TasksService, one for the TasksModule and one for the AppModule. You probably didn't mean to do this, and the way to resolve it would be to remove the TasksService from the AppModule's providers array and only have it in the TasksModule's providers array.
Related
This is not a duplicate Q. Please don't mark this as that.
Following is not I want
import { EntityRepository, Repository } from "typeorm";
import { Test } from "./test.model";
import { Injectable } from "#nestjs/common";
#EntityRepository(Test)
export class TestRepository extends Repository<Test> {}
the #EntityRepository decorator is now deprecated.
I also don't want to make a fake repository like in here:
https://stackoverflow.com/a/73352265/5420070
Don't want this either as I've to extract manager from dataSource, I don't want this because I think this is not the best way.
export const UserRepository = dataSource.getRepository(User).extend({
// ^^^^^^^^^^ from where this came from
findByName(firstName: string, lastName: string) {
return this.createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName })
.andWhere("user.lastName = :lastName", { lastName })
.getMany()
},
})
Found above in: https://orkhan.gitbook.io/typeorm/docs/custom-repository#how-to-create-custom-repository
I don't think this is in NestJS context.
What I want
Want to know right way to make custom repository in latest version of NestJS (v9) & TypeORM (v0.3). In #EntityRepository deprecation note, they said that need to extend the repo to create custom repo like someRepo.extend({}). I want to know how to do it in NestJS way
In order to achieve what you want, you could do something like the following.
This solution is inspired by the official NestJS docs related to this topic, with some customization.
Steps to achieve it:
Create your TypeOrm entity as usual, let's say UserEntity (user.entity.ts file)
Create a UserRepository class (user.repository.ts file)
Create a UserService class as usual (user.service.ts file)
Import the UserRepository into your UserService
Update UserModule in order to provide the UserRepository and needed UserEntity
Detailed implementation example
1. user.entity.ts file
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class UserEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName: string;
#Column({ default: true })
isActive: boolean;
}
2. user.repository.ts file
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserEntity } from './user.entity';
export class UserRepository extends Repository<UserEntity> {
constructor(
#InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>
) {
super(userRepository.target, userRepository.manager, userRepository.queryRunner);
}
// sample method for demo purposes
async findByEmail(email: string): Promise<UserEntity> {
return await this.userRepository.findOneBy({ email }); // could also be this.findOneBy({ email });, but depending on your IDE/TS settings, could warn that userRepository is not used though. Up to you to use either of the 2 methods
}
// your other custom methods in your repo...
}
3. & 4. user.service.ts file
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { UserRepository } from './user.repository';
import { UserEntity } from './user.entity';
#Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository, // import as usual
) {}
findAll(): Promise<UserEntity[]> {
return this.userRepository.find();
}
// call your repo method
findOneByEmail(email: string): Promise<UserEntity> {
return this.userRepository.findByEmail({ email });
}
findOne(id: number): Promise<UserEntity> {
return this.userRepository.findOneBy({ id });
}
async remove(id: string): Promise<void> {
await this.userRepository.delete(id);
}
// your other custom methods in your service...
}
5. Updating UserModule (user.module.ts file)
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { UserEntity } from './user.entity';
#Module({
imports: [TypeOrmModule.forFeature([UserEntity])], // here we provide the TypeOrm support as usual, specifically for our UserEntity in this case
providers: [UserService, UserRepository], // here we provide our custom repo
controllers: [UserController],
exports: [UserService, UserRepository] // add this only if you use service and/or custom repo within another module/service
})
export class UserModule {}
With this in place, you should be able to import the UserModule in your AppModule and be able to both implement custom methods in the UserRepository and use them in the UserService. You should also be able to call the manager and queryRunnner of the custom repository.
Additional Note
If you need to directly call your UserRepository methods from within another module/service, then update UserModule to export the UserRepository
Hope it helps, don't hesitate to comment.
import { Column, Entity, JoinColumn, ManyToOne, OneToMany } from "typeorm";
import { CustomBaseEntity } from "../core/custom-base.entity";//custom-made
#Entity({name: 'rcon_log', schema: 'dbo'})
export class LogEntity extends CustomBaseEntity{
------------your code-----------
}
I'm posting here because I have been stuck on a problem for few hours now.
I am creating an API using Nest JS 8 and MongoDB, and I test it using Postman. When I want to execute a POST request (http://localhost:3000?nom=Antoine) to insert an object in my database, I have an error (500 : Internal server error) message that says "Client validation failed: nom: Path 'nom' is required (nom is the name of my object's property).
I've wandered every topic about this kind of issue, tried to upgrade my version of Nest, to use a middleware, to make sure the right version of every depedency was installed.
I don't want to remove the "required: true" property because i think it is necessary. I tried to set it to "false", which enabled me to insert the object in the database but without my property 'nom' (name in french).
If you guys have any help, here's my schema :
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type ClientDocument = Client & Document;
#Schema()
export class Client {
#Prop({ required: true })
nom: string;
}
export const ClientSchema = SchemaFactory.createForClass(Client);
And here is my controller :
import { Body, Controller, Delete, Get, Param, Post, Put} from '#nestjs/common';
import { ClientService } from './client.service';
import { ClientDto } from './dto/client.dto';
import { CreateClientDto } from './dto/create-client.dto';
import { UpdateClientDto } from './dto/update-client.dto';
#Controller('/client')
export class ClientController {
constructor(private readonly clientService: ClientService) {}
#Get()
async index(){
return await this.clientService.findAll();
}
#Get(':id')
async find(#Param('id') id: string) {
return await this.clientService.findOne(id);
}
#Post()
async create(#Body() createClientDto: CreateClientDto) {
console.log(createClientDto);
return await this.clientService.create(createClientDto);
}
#Put(':id')
async update(#Param('id') id: string, #Body() updateClientDto: ClientDto) {
return await this.clientService.update(id, updateClientDto);
}
#Delete(':id')
async delete(#Param('id') id: string) {
return await this.clientService.delete(id);
}
}
Thanks for looking
I found the solution (i still don't know why it works this way tho).
In my client.service.ts, i updated the create function from this :
async create(createClientDto: CreateClientDto): Promise<Client> {
return await new this.model({createClientDto}).save();
}
To this
async create(createClientDto: CreateClientDto): Promise<Client> {
return await new this.model({
...createClientDto,
createdAt: new Date(),
}).save();
}
Thanks for taking the time to answer, I hope this will help
I am using TypeORM by way of NestJS to connect to a PostgreSQL database. For every connection I make to the database, I need to 'SET ROLE my-service', preferably just once when the connection is established. What are my options?
I have tried to find a way using the config that's passed to TypeOrmModule.forRoot(). I've considered the possibility that I can issue instructions to the PostgreSQL client ahead of time, but I don't have a strong sense of how to do that.
Please scroll to Update 2, per your unpated question.
Option 1: As an injectable service
You can create a service SomeTypeOrmConfigService as an #Injectable(), and then inject it like so.
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '#nestjs/typeorm';
// create your injectable here
#Injectable()
export class SomeTypeOrmConfigService implements TypeOrmOptionsFactory {
constructor(private configService: ConfigService) {}
createTypeOrmOptions(): TypeOrmModuleOptions {
return {
type: 'mysql',
host: this.configService.get('DATABASE_HOST'),
username: this.configService.get('DATABASE_USERNAME'),
password: this.configService.get('DATABASE_PASSWORD'),
};
}
}
Option 2: Save the information in a common .env, or b) another approach database.provider.ts file and export that copied ans./credit to from here
import { ConfigModule, ConfigService } from '#nestjs/config';
import { TypeOrmModule } from '#nestjs/typeorm';
export const databaseProviders = [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get('PGHOST'),
port: +configService.get<number>('PGPORT'),
username: configService.get('PGUSER'),
password: configService.get('PGPASSWORD'),
database: configService.get('PGDATABASE'),
entities: ['dist/**/*.entity{.ts,.js}'],
synchronize: false,
logging: true,
}),
}),
];
Then import it in your database.module.ts
import { databaseProviders } from './database.provider';
import { DatabaseService } from './database.service';
import { Module } from '#nestjs/common';
#Module({
imports: [...databaseProviders],
providers: [DatabaseService],
exports: [...databaseProviders],
})
export class DatabaseModule {}
And that's all, you can add multiple connections in your database.provider.ts if you want it, also, donĀ“t forget to create the .env and import the database module in your root module.
Update Answer (V2) to updated question
In a nutshell.. I think you are missing a) session module like nestjs-session , once you use that you can optionally b) cache it in your role with your session object
Step 1: use nestjs-session
Step 2: Now you can have access/use role in the session property object of your request
Step 3: You can then make it an injectable or a user factory with the help of the NestSessionOptions
My humble recommendation, save yourself time, use a pre-rolled package as a decorator.. try this package from here - use their sample ref. below ->
npm i nestjs-roles
1. Create Your roles
// role.enum.ts
export enum Role {
ADMIN = 'ADMIN',
USER = 'USER',
MODERATOR = 'MODERATOR',
}
2. Let's say you use nestjs-session and keep role in session property object of request. So then create guard with getRole callback:
// roles.guard.ts
import { ExecutionContext } from '#nestjs/common';
import { createRolesGuard } from 'nestjs-roles';
import { Role } from './role.enum';
function getRole(context: ExecutionContext) {
const { session } = context.switchToHttp().getRequest();
if (!session) {
return;
}
return (session as { role?: Role }).role;
}
export const Roles = createRolesGuard<Role>(getRole);
3. After that we can set Roles guard globally (don't forget to pass Reflector instance):
// bootstrap.ts
import { NestFactory, Reflector } from '#nestjs/core';
import { Roles } from './roles.guard';
const app = await NestFactory.create(AppModule);
const reflector = app.get<Reflector>(Reflector);
app.useGlobalGuards(new Roles(reflector));
4. All settings are done. Now you can set up access in your controllers:
// secrets.controller.ts
import { Roles } from './roles.guard';
#Controller('secrets')
#Roles.Params(true) // setup access on Controller for users with any existing role
export class SecretsController {
#Get('my')
async readMy() {
// ...
}
#Patch(':id')
#Roles.Params(Role.ADMIN) // override access on certain handler
async update() {
// ...
}
}
I try to block some root if it's not an admin, but when I run the code I have a TypeError but I don't know how to resolve it.
Thanks
roles.guards.ts
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { Role } from './role.enums';
import { ROLES_KEY } from './roles.decorator';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((Role) => user.roles?.includes(Role));
}
}
articles.controllers.ts
#UseGuards(JwtAuthGuard)
#Roles(Role.Admin)
async addArticle(
#Body('title') artTitle: string,
#Body('description') artDescription: string,
#Body('url') artUrl: string,
#Body('cover') artCover: string,
#Body('content') artContent: string,
#Body('category') artCategory: string,
){
const generatedId = await this.articlesService.insertArticle(
artTitle,
artDescription,
artUrl,
artCover,
artContent,
artCategory
);
return { id: generatedId };
}
when I run the code I have a TypeError but I don't know how to resolve it.
Thanks
I'd like to add more detail to Jay McDoniel's answer since it still took me a few hours to get around this issue.
Create JWT.module.ts (JwtModule is already used by #nestjs/jwt hence my use of caps) file with the following:
import { ConfigModule, ConfigService } from "#nestjs/config";
import { JwtModule } from "#nestjs/jwt";
#Module({
imports: [
{
...JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secretOrKeyProvider: () => (configService.get<string>("JWT_SECRET")),
signOptions: {
expiresIn: 3600,
},
}),
inject: [ConfigService],
}),
global: true
}
],
exports: [JwtModule]
})
export class JWTModule {}
Add this class to your app.module.ts's imports array.
if you have
{
provide: APP_GUARD,
useClass: RolesGuard,
},
in any of your modules... DELETE IT. declaring guards in any providers will automatically make it global and endpoints which you don't want to be guarded will end up getting guarded (I'm aware https://docs.nestjs.com/security/authorization#basic-rbac-implementation tells you to register the role guard in your providers but that just didn't work for me). You only need to import your strategies to the relevant routes.
In your controller, this should now work
#ApiBearerAuth()
#Roles(Role.Admin)
#UseGuards(JwtAuthGuard, RolesGuard)
#Get()
findAll() {
return this.usersService.findAll();
}
so this endpoint accepts users with a valid JWT and an admin role inside said JWT.
If I had to bet, your RolesGuard is bound to the global scope, whereas the JwtAuthGuard is bound to the route handler scope. Global guards execute first and foremost, so the RolesGuard executes before the JwtAuthGaurd can set req.user (passport is what does this under the hood). What you can do is either ensure that there is a req.user property (either via a middleware or jutt running the JwtAuthGuard globally) or you can move the RolesGuard to be scoped at the route handler level after the JwtAuthGuard runs.
Use JwtGuard and RoleGuard in the controller like #UseGuards(JwtAuthGuard, RolesGuard). The issue because of RoleGuards is not used in guard.
#Roles(Role.ADMIN)
#UseGuards(JwtAuthGuard,RolesGuard)
#Query(() => [User], { name: 'User' })
articles.module.ts
in this module file update in provider rolesGuards
providers: [AuthResolver, AuthService,LocalStrategy,JwtStrategy,RolesGuard],
use #Post() above your controller
#UseGuards(JwtAuthGuard)
#Roles(Role.Admin)
#Post('')
async addArticle(
#Body('title') artTitle: string,
#Body('description') artDescription: string,
#Body('url') artUrl: string,
#Body('cover') artCover: string,
#Body('content') artContent: string,
#Body('category') artCategory: string,
){
const generatedId = await this.articlesService.insertArticle(
artTitle,
artDescription,
artUrl,
artCover,
artContent,
artCategory
);
return { id: generatedId };
}
Currently, I am using #ApiExcludeEndpoint() ### on top of all methods to hide the end-point in the swagger-ui, like this:
import { Controller, Get, Query, Param } from '#nestjs/common';
import { ResourceService } from './resource.service';
import { Auth } from 'src/auth/auth.decorator';
import {
ApiTags,
ApiSecurity,
ApiOkResponse,
ApiForbiddenResponse,
ApiCreatedResponse,
ApiExcludeEndpoint
} from '#nestjs/swagger';
#Controller()
#ApiTags('Resources')
#ApiSecurity('apiKey')
export class ResourceController {
constructor(private readonly resourceService: ResourceService) {}
#Get('get_url')
#ApiExcludeEndpoint()
#Get()
#ApiOkResponse({
description: 'Resources list has succesfully been returned',
})
#ApiForbiddenResponse({ description: 'You are not allowed' })
#Auth(...common_privileges)
findAll(#Query() query: any): any {
......
}
#Get('get_url/:id')
#ApiExcludeEndpoint()
#ApiOkResponse({ description: 'Resource has succesfully been returned' })
#ApiForbiddenResponse({ description: 'You are not allowed' })
#Auth(...common_privileges)
findById(#Param('id') id: string, #Query() query: any): any {
......
}
}
I Need to know is there a way to hide all the end-point in the controller using a single decorator, I checked some documents it says to use #ApiIgnore() and #Hidden() but I can't find those in nestjs-swagger. Please comment on this
One possibility is to explicitly include the modules that you'd like to include in the swagger docs instead of just "including all modules" by default. Example:
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const catDocument = SwaggerModule.createDocument(app, options, {
include: [LionsModule, TigersModule], // don't include, say, BearsModule
});
SwaggerModule.setup('api/cats', app, catDocument);
Without the explicit include:[] property, LionsModule, TigersModule, and BearsModule would be automatically included.
To hide all the end-point in the controller.ts, you must use ApiExcludeController instead of ApiExcludeEndpoint as in the example.
https://docs.nestjs.com/openapi/decorators
import { Controller, Get, Query, Param } from '#nestjs/common';
import { ResourceService } from './resource.service';
import { Auth } from 'src/auth/auth.decorator';
import {
ApiTags,
ApiSecurity,
ApiOkResponse,
ApiForbiddenResponse,
ApiCreatedResponse,
ApiExcludeController
// ApiExcludeEndpoint
} from '#nestjs/swagger';
#Controller()
#ApiTags('Resources')
#ApiSecurity('apiKey')
#ApiExcludeController()
export class ResourceController {
constructor(private readonly resourceService: ResourceService) {}
#Get('get_url')
// #ApiExcludeEndpoint()
#Get()
#ApiOkResponse({
description: 'Resources list has succesfully been returned',
})
#ApiForbiddenResponse({ description: 'You are not allowed' })
#Auth(...common_privileges)
findAll(#Query() query: any): any {
......
}
#Get('get_url/:id')
// #ApiExcludeEndpoint()
#ApiOkResponse({ description: 'Resource has succesfully been returned' })
#ApiForbiddenResponse({ description: 'You are not allowed' })
#Auth(...common_privileges)
findById(#Param('id') id: string, #Query() query: any): any {
......
}
}