I am using mikro-orm in nestjs to create a sqlite database. For some reason, Mikro-Orm will create the database file(sandox.sqlite3) but will not create tables in the database. I have created a task module with entities.
App Module:
#Module({
imports: [
MikroOrmModule.forRoot({
entities: ['./dist/**/entities/*'],
entitiesTs: ['./src/**/entities/*'],
dbName: 'sandbox.sqlite3',
type: 'sqlite',
}),
TasksModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
TaskModule:
#Module({
imports: [MikroOrmModule.forFeature([Task])],
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}
Task Service:
When i make a post request to create, A 201 response is received back along with the data that i sent. But there is not table created in the database.
#Injectable()
export class TasksService {
constructor(
#InjectRepository(Task)
private readonly taskRepository: EntityRepository<Task>,
) {}
create(createTaskDto: CreateTaskDto) {
console.log(createTaskDto.name);
return this.taskRepository.create({ ...createTaskDto });
}
findAll() {
return this.taskRepository.findAll();
}
findOne(id: number) {
return this.taskRepository.findOne({ id });
}
async update(id: number, updateTaskDto: UpdateTaskDto) {
const task = await this.taskRepository.findOne({ id });
task.name = updateTaskDto.name ?? task.name;
task.description = updateTaskDto.description ?? task.description;
task.assign({ id });
// return this.taskRepository.(task);
}
remove(id: number) {
// return this.taskRepository.removeAndFlush();
}
}
Task Entity:
#Entity()
export class Task extends BaseEntity<Task, 'id'> {
#PrimaryKey()
id: number;
#Property()
name: string;
#Property()
description: string;
}
You need to sync the database manually, either via SchemaGenerator or via migrations. If you have CLI set up, you can do npx mikro-orm schema:update --run
https://mikro-orm.io/docs/faq#how-can-i-synchronize-my-database-schema-with-the-entities
Related
What I want to do is avoid circular dependency and at the same time now make my module extremely large. This issue is the following:
We have the entities Database and DatabaseUser and each one is its own separate module(DatabaseModule, DatabaseUserModule). How should I go about the issue where I have to create a DatabaseUser when a Database is created by also avoiding the usage of forwardRef?
The need to use forwardRef comes from the fact that the DatabaseService will require the DatabaseUserService in order to create the DatabaseUser for the given Database. And on the other side, the DatabaseUserService will need the DatabaseService to retrieve the Database for which the DatabaseUser is being created. Written in code it would look like so:
// src/database/database.dto.ts
export class CreateDatabaseDTO {
name: string;
password: string;
}
// src/database/database.service.ts
#Injectable()
export default class DatabaseService {
constructor(
private readonly databaseRepository: DatabaseRepository,
#Inject(forwardRef(() => DatabaseUserService))
private readonly databaseUserService: DatabaseUserService,
) {}
public async createDatabase(
createDatabaseDTO: CreateDatabaseDTO,
): Promise<Database> {
const database = await this.databaseRepository.createDatabase(
createDatabaseDTO.name,
);
await this.databaseUserService.createDatabaseUser({
name: createDatabaseDTO.name,
password: createDatabaseDTO.password,
databases: [database.id],
});
return database;
}
public async getDatabases(ids: number[]): Promise<Database[]> {
return [];
}
}
// src/database/database.module.ts
#Module({
imports: [forwardRef(() => DatabaseUserModule)],
providers: [DatabaseService, DatabaseRepository],
exports: [DatabaseService],
})
export default class DatabaseModule {}
// src/database-user/database-user.dto.ts
export class CreateDatabaseUserDTO {
name: string;
password: string;
databases: number[];
}
// src/database-user/database-user.service.ts
#Injectable()
export default class DatabaseUserService {
constructor(
private readonly databaseUserRepository: DatabaseUserRepository,
#Inject(forwardRef(() => DatabaseService))
private readonly databaseService: DatabaseService,
) {}
public async createDatabaseUser(
createDatabaseUserDTO: CreateDatabaseUserDTO,
): Promise<DatabaseUser> {
const databases = await this.databaseService.getDatabases(
createDatabaseUserDTO.databases,
);
return this.databaseUserRepository.createUser(
createDatabaseUserDTO.name,
createDatabaseUserDTO.password,
databases,
);
}
}
// src/database-user/database-user.module.ts
#Module({
imports: [forwardRef(() => DatabaseModule)],
providers: [DatabaseUserService, DatabaseUserRepository],
exports: [DatabaseUserService],
})
export default class DatabaseUserModule {}
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 };
}
Connecting to two databases using TypeORM and NestJS throws a ConnectionNotFoundError when a custom repository is registered using the connection (UserConnection & PhotoConnection) one for each database. I have created a minimal repo here https://github.com/masonridge/connissuewithndb. The TypeOrm registration is done in the AppModule of NestJS
(app.module.ts)
#Module({
imports: [
TypeOrmModule.forRootAsync({
name: 'PhotoConnection',
useFactory: async () => {
return {
type: 'sqlite',
synchronize: true,
database: 'TestPhoto.sqlite',
entities: [Photo],
} as SqliteConnectionOptions;
},
}),
TypeOrmModule.forRootAsync({
name: 'UserConnection',
useFactory: async () => {
return {
type: 'sqlite',
synchronize: true,
database: 'TestUser.sqlite',
entities: [User],
} as SqliteConnectionOptions;
},
}),
// TypeOrmModule.forFeature([User, UserRepository], 'UserConnection'),
// TypeOrmModule.forFeature([Photo, PhotoRepository], 'PhotoConnection'),
PhotoModule, UserModule,
],
(photo.module.ts) DB connection1 - PhotoConnection is registered here
#Module({
imports: [
TypeOrmModule.forFeature([PhotoRepository], 'PhotoConnection'),
],
providers: [
PhotoService,
],
controllers: [PhotoController],
exports: [TypeOrmModule],
})
export class PhotoModule {}
(user.module.ts) DB connection2 - UserConnection is registered here
#Module({
imports: [
TypeOrmModule.forFeature([UserRepository], 'UserConnection'),
],
providers: [
UserService,
],
exports: [TypeOrmModule],
controllers: [UserController],
})
export class UserModule {}
(user.repository.ts) Custom repository
#EntityRepository(User)
export class UserRepository extends Repository<User> {
async createUser(name: string): Promise<string> {
const user = this.create();
user.username = name;
user.salt = 'salt';
user.password = 'xxkdkdk';
user.save();
return name;
}
}
(photo.repository.ts)
#EntityRepository(Photo)
export class PhotoRepository extends Repository<Photo> {
async createPhoto(name: string): Promise<string> {
const photo = this.create();
photo.name = name;
photo.save();
return name;
}
}
The repo is injected into the service using the connection (PhotoConnection)
(photo.service.ts)
export class PhotoService {
constructor(
#InjectRepository(Photo, 'PhotoConnection')
private readonly photoRepository: PhotoRepository,
) {}
and here using UserConnection (user.service.ts)
#Injectable()
export class UserService {
constructor(
#InjectRepository(User, 'UserConnection')
private readonly userRepository: UserRepository,
) {}
The application starts fine but on a POST request it throws a ConnectionNotFoundError error
(node:190812) UnhandledPromiseRejectionWarning: ConnectionNotFoundError: Connection "default" was not found.
at new ConnectionNotFoundError (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\error\ConnectionNotFoundError.js:10:28)
at ConnectionManager.get (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\connection\ConnectionManager.js:38:19)
at Object.getConnection (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\index.js:244:35)
at Function.BaseEntity.getRepository (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\repository\BaseEntity.js:67:57)
at Photo.BaseEntity.save (C:\nestjs\type-orm-dbscratch\node_modules\typeorm\repository\BaseEntity.js:27:33)
at PhotoRepository.createPhoto (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.repository.js:15:15)
at PhotoService.createPhoto (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.service.js:24:43)
at PhotoController.addSetting (C:\nestjs\type-orm-dbscratch\dist\db\photo\photo.controller.js:22:27)
at C:\nestjs\type-orm-dbscratch\node_modules#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I would like to know if there is an issue with the registration. Any help would be appreciated.
It looks like the issue is between using the TypeORM approach of DataMapper and ActiveRecord. It seems using the ActiveRecord approach does not support named repositories with NestJS, and as such was looking for connection named 'default'. If you remove the extends BaseEntity and use the repository's save method instead this.save(user) vs user.save() you'll get a successful save. I didn't see an immediate way to set the connection of the ActiveRecord but it may be an option somewhere if that is still the approach you would like to follow.
I have a subscriber for NestJS to listen to any create, update or delete events (TypeORM). When one of these events is fired, I'd like to use an injected service in order to create a new revision entry.
However, it seems I cannot get the dependency loaded inside of the subscriber and the service comes back as being undefined
Key files:
EntityModificationSubscriber (Subscriber)
RevisionEntity (Entity)
app.module.ts
#Module({
imports: [
HttpModule,
TypeOrmModule.forRoot({
type: (process.env.DB_TYPE as any) || 'postgres',
host: process.env.DB_HOST || '127.0.0.1',
port: (process.env.DB_PORT as any) || 5432,
username: process.env.DB_USER || 'root',
password: process.env.DB_PASS || '',
database: process.env.DB_NAME || 'test',
entities: [join(__dirname, '**/**.entity{.ts,.js}')],
synchronize: true,
logging: 'all',
logger: 'advanced-console',
subscribers: [EntityModificationSubscriber],
}),
TypeOrmModule.forFeature([
RevisionEntity,
]),
TerminusModule.forRootAsync({
// Inject the TypeOrmHealthIndicator provided by nestjs/terminus
inject: [TypeOrmHealthIndicator, MicroserviceHealthIndicator],
useFactory: (db, msg) => getTerminusOptions(db, msg),
}),
GraphQLModule.forRoot({
debug: true,
playground: true,
typePaths: ['./**/*.graphql'],
}),
],
controllers: [AppController],
providers: [
RevisionService,
EntityModificationSubscriber,
],
})
entity_modification_subscriber.ts
import {EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent} from 'typeorm';
import {RevisionEntity, RevisonEntityStatus} from '../entities/revison.entity';
import {RevisionService} from '../services/revisions.service';
import {Injectable} from '#nestjs/common';
#Injectable()
#EventSubscriber()
export class EntityModificationSubscriber implements EntitySubscriberInterface {
constructor(private revisionService: RevisionService) {
}
// tslint:disable-next-line:no-empty
afterInsert(event: InsertEvent<any>): Promise<any> | void {
const revision = new RevisionEntity();
revision.action = RevisonEntityStatus.Created;
}
afterUpdate(event: UpdateEvent<any>): Promise<any> | void {
}
// tslint:disable-next-line:no-empty
afterRemove(event: RemoveEvent<any>) {
// this.revisionService.createRevisionEntry(revision);
}
}
The only way I found to inject a dependency into a subscriber using NestJS, was not to register that subscriber in the TypeORM configuration. I subscribe it manually into the TypeORM connection on subscriber's constructor.
import { EntitySubscriberInterface, EventSubscriber, InsertEvent, RemoveEvent, UpdateEvent, Connection } from 'typeorm';
import { RevisionEntity, RevisonEntityStatus } from '../entities/revison.entity';
import { RevisionService } from '../services/revisions.service';
import { Injectable } from '#nestjs/common';
#Injectable()
#EventSubscriber()
export class EntityModificationSubscriber implements EntitySubscriberInterface {
constructor(private readonly connection: Connection, private readonly revisionService: RevisionService) {
connection.subscribers.push(this); // <---- THIS
}
// tslint:disable-next-line:no-empty
afterInsert(event: InsertEvent<any>): Promise<any> | void {
const revision = new RevisionEntity();
revision.action = RevisonEntityStatus.Created;
//this.revisionService <- should be working!
}
afterUpdate(event: UpdateEvent<any>): Promise<any> | void {
}
// tslint:disable-next-line:no-empty
afterRemove(event: RemoveEvent<any>) {
// this.revisionService.createRevisionEntry(revision);
}
}
Then in your app.module on the TypeORM module configuration (TypeOrmModule.forRoot).
Remove the line:
subscribers: [EntityModificationSubscriber],
It solved to me, hope it help others.
You can find discussions about that in some NestJS issues/pull requests.
https://github.com/nestjs/typeorm/issues/85
https://github.com/nestjs/typeorm/pull/27
Hi the problem is that If you want to preform database actions you would have to use:
event.manager
and Don't use getEntityManager() or getRepository() or any other global function.
This is due to a transaction. Save is running in a transaction and your data is saved in a transaction which is not committed yet. But global functions you use are running out of transaction.
more about issue here:
https://github.com/typeorm/typeorm/issues/681
I would like to unittest corner cases for my TypeORM database calls. I have already mocked all my TypeORM repositories with valid data. But I would like to SpyOn the repository and change the return value form TypeORM. How do I do that?
import {INestApplication} from '#nestjs/common';
import {Test} from '#nestjs/testing';
import {CommonModule} from '#src/common/common.module';
import {AuthService} from './auth.service';
import {Repository} from 'typeorm';
import {V3User} from '#src/database/entity/user.v3entity';
describe('AuthService', () => {
let service: AuthService;
let app: INestApplication;
beforeEach(async () => {
const module = await Test.createTestingModule({
imports: [CommonModule.forRoot(`${process.env.DEV_ENV}`)],
providers: [
AuthService,
{provide: 'V3USER_REPOSITORY', useValue: mockRepositoryV3User()},
],
}).compile();
app = module.createNestApplication();
await app.init();
service = module.get<AuthService>(AuthService);
});
it('test auth service - with non existing user in v3 db', async () => {
jest.spyOn(?????? , 'findOne').mockImplementation(() => undefined);
const res = await service.loginUser("bad token");
await expect(service.tokenBasedAuth('example bad token'))
.rejects.toThrow('bad token exception');
});
});
I mock the database like this for normal test cases:
export const mockRepositoryV3User = () => ({
metadata: {
columns: [],
relations: [],
},
findOne: async () =>
Promise.resolve({
id: 3,
email: 'email#example.com',
first_name: 'david',
last_name: 'david',
last_login: '2019-07-15',
date_joined: '2019-07-15',
}),
});
Okay, after finally getting around to testing and playing with ideas I've found that this is a valid strategy
Assume we have set up a PhotoEntity with basic properties, nothing too special (id, name, description, etc.)
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class Photo {
#PrimaryGeneratedColumn()
id: number;
#Column({ length: 500 })
name: string;
#Column('text')
description: string;
#Column()
filename: string;
#Column('int')
views: number;
#Column()
isPublished: boolean;
}
Set up a PhotoService such as the following (super basic but it will illustrate the point):
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Photo } from './photo.entity';
#Injectable()
export class PhotoService {
constructor(
#InjectRepository(Photo)
private readonly photoRepository: Repository<Photo>,
) {}
async findAll(): Promise<Photo[]> {
return await this.photoRepository.find();
}
}
We can useClass: Repository so that we don't have to do any of the heavy lifting of setting up a repository class to use for testing (Repository is imported from the TypeORM package. We can then get the repo from the module and save it to a value for easy mocking and set up our tests like so:
import { Test, TestingModule } from '#nestjs/testing';
import { PhotoService } from './photo.service';
import { getRepositoryToken } from '#nestjs/typeorm';
import { Photo } from './photo.entity';
import { Repository } from 'typeorm';
describe('PhotoService', () => {
let service: PhotoService;
// declaring the repo variable for easy access later
let repo: Repository<Photo>;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PhotoService,
{
// how you provide the injection token in a test instance
provide: getRepositoryToken(Photo),
// as a class value, Repository needs no generics
useClass: Repository,
},
],
}).compile();
service = module.get<PhotoService>(PhotoService);
// Save the instance of the repository and set the correct generics
repo = module.get<Repository<Photo>>(getRepositoryToken(Photo));
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return for findAll', async () => {
// mock file for reuse
const testPhoto: Photo = {
id: 'a47ecdc2-77d6-462f-9045-c440c5e4616f',
name: 'hello',
description: 'the description',
isPublished: true,
filename: 'testFile.png',
views: 5,
};
// notice we are pulling the repo variable and using jest.spyOn with no issues
jest.spyOn(repo, 'find').mockResolvedValueOnce([testPhoto]);
expect(await service.findAll()).toEqual([testPhoto]);
});
});
Run your tests against the specified file or against all tests
▶ npm run test -- photo.service
> nestjs-playground#0.0.1 test ~/Documents/code/nestjs-playground
> jest "photo.service"
PASS src/photo/photo.service.spec.ts
PhotoService
✓ should be defined (17ms)
✓ should return for findAll (4ms) < -- test passes with no problem
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 3.372s, estimated 4s
Ran all test suites matching /photo.service/i.