how to do nested routing - nestjs

Can someone recommend how to properly (nestjs way) implement nested router? For example,
/users/12/characters/765/tools
I'd like to have users controller and characters controller that is dependent on users (sub router) in separate files
( if this is not recommended way, could you suggest alternatives? ) thanks in advance!

You can do something like this in separate controllers:
#Controller('users')
export class UsersController
{...}
#Controller('users/:userId/characters')
export class CharactersController
{...}

You can use the router module
RouterModule.register([
{
path: 'admin',
module: AdminModule,
children: [
{
path: 'dashboard',
module: DashboardModule,
},
{
path: 'metrics',
module: MetricsModule,
},
],
},
]);

Related

RepositoryNotFoundError: No repository for "User" was found. Looks like this entity is not registered in current "default" connection? Typeorm

I am having a fun issue trying to get TypeOrm to work in my nestjs project.
I have the below code to configure my project, yes everything loads, and yes I am able to connect to my database.
import { CacheModule, Module } from '#nestjs/common';
import { JwtModule } from '#nestjs/jwt';
import { PassportModule } from '#nestjs/passport';
import { TypeOrmModule } from '#nestjs/typeorm';
import { User } from './entities/user.entity';
import { ConfigModule } from '#nestjs/config';
import { AuthenticationController } from './controllers/authentication.controller';
import { AuthenticationService } from './services/authentication.service';
import { Connection } from 'typeorm';
import { BaseEntity } from './entities/base.entity';
#Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: process.env.POSTGRE_PASSWORD,
database: process.env.DATABASE,
migrationsTableName: 'migration_table',
entities: [User, BaseEntity],
migrations: [__dirname + '/migrations/**/*.ts'],
subscribers: [__dirname + '/subscribers/**/*.ts'],
cli: {
entitiesDir: '/entitys',
migrationsDir: '/migrations',
subscribersDir: '/subscribers',
},
synchronize: true,
autoLoadEntities: true,
}),
CacheModule.register(),
PassportModule,
JwtModule.register({
secret: 'myprivatekey',
signOptions: { expiresIn: '1d' },
}),
],
controllers: [AuthenticationController],
providers: [AuthenticationService],
})
export class AppModule {
constructor(private connection: Connection) {}
}
and here are the entities:
import {
Column,
BeforeUpdate,
BeforeInsert,
} from 'typeorm';
export class BaseEntity {
#Column()
created_at: Date;
#Column({
default: new Date(),
})
updated_at: Date;
#BeforeUpdate()
updateUpdatedAt() {
this.updated_at = new Date();
}
#BeforeInsert()
updateCreatedAt() {
this.created_at = new Date();
}
}
import {
Entity,
Column,
PrimaryGeneratedColumn,
Generated,
} from 'typeorm';
import { BaseEntity } from './base.entity';
#Entity('users')
export class User extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
#Generated('uuid')
uuid: string;
#Column()
first_name: string;
#Column()
last_name: string;
#Column()
email: string;
#Column()
password: string;
#Column({
default: false,
})
confirmed: boolean;
#Column({
default: null,
})
seller_id: string;
#Column({
default: null,
})
auth_token: string;
#Column({
default: false,
})
is_admin: boolean;
}
I originally tried doing a glob pattern match, to no avail, so now I am directly importing in my Entities until I can get something to run. Also note, that all my modules load prior to the error above and the error is from using the #InjectRepository() decorator within either the AuthenticationController or AdminController. Everywhere I have looked has said its because my entities are not being loaded, which I am not sure how that is possible. Thanks.
In my case I had an error on Production mode, to fix it I added the path of the compiled JS files in the build folder.
const conn: MongoConnectionOptions = {
type: 'mongodb',
url: DB_URL,
synchronize: true,
useNewUrlParser: true,
useUnifiedTopology: true,
logging: true,
entities: ['src/entity/*.ts', './build/src/entity/*.js'], // <- Here!
migrations: ['src/migration/**/*.ts'],
subscribers: ['src/subscriber/**/*.ts'],
cli: {
entitiesDir: 'src/entity',
migrationsDir: 'src/migration',
subscribersDir: 'src/subscriber',
},
extra: {
authSource: DB_AUTH_SOURCE,
},
};
The short version could be: entities: ['**/src/entity/*{.ts,.js}'],
Try giving a name to your entity with the #Entity decorator:
import { Entity, PrimaryColumn, Column } from "typeorm";
#Entity("category") // <-- Right here
seems like it is, as you said, because the entities are not loaded.
my guess is :
the config file you have added tries to find the files at :
migrations: [__dirname + '/migrations/**/*.ts'],
subscribers: [__dirname + '/subscribers/**/*.ts'],
are those entity files in the same dir as the module is? could help to print the output of those paths just to make sure it is correct.
also note that typescript compiles to javascript, so you might run into the same problem if you run the code from /dist, because it will be able to see only the ".js" compiled files, so i'd suggest to use
migrations: [__dirname + '/migrations/**/*{.ts,.js}'],
subscribers: [__dirname + '/subscribers/**/*{.ts,.js}'],
if its nothing of the above 2 options, please provide the full routes of the entities and modules in play.
I faced the same issue, glob paths don't work in case of monorepo.
Note, however, that glob paths are not supported by webpack, so if you are building your application within a monorepo, you won't be able to use them. To address this issue, an alternative solution is provided. To automatically load entities, set the autoLoadEntities property of the configuration object (passed into the forRoot() method).
Note that entities that aren't registered through the forFeature() method, but are only referenced from the entity (via a relationship), won't be included by way of the autoLoadEntities setting.
-- NestJS Docs
Also, I was using ormconfig.ts, which also presents one more difficulty -
Note that the ormconfig.json file is loaded by the typeorm library. Thus, any of the extra properties described above (which are supported internally by way of the forRoot() method - for example, autoLoadEntities and retryDelay) won't be applied. Luckily, TypeORM provides the getConnectionOptions function that reads connection options from the ormconfig file or environment variables. With this, you can still use the configuration file and set Nest-specific options.
-- NestJS Docs
Final Solution
root/main module -
import { getConnectionOptions } from 'typeorm';
...
#Module({
imports: [
TypeOrmModule.forRootAsync({
useFactory: async () =>
Object.assign(await
getConnectionOptions(), {
autoLoadEntities: true,
}),
})
],
...
})
app/child module -
...
#Module({
imports: [TypeOrmModule.forFeature([<entities go here>])],
...
})
...
In my case, I solved it by declaring the entity in the connection file.
The TypeORM documentation explains this change.
Creating a connection to the database
Now, when our entity is created, let's create an index.ts (or app.ts whatever you call it) file and set up our connection there:
import "reflect-metadata";
import { createConnection } from "typeorm";
import { Photo } from "./entity/Photo";
createConnection({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "admin",
database: "test",
entities: [
Photo
],
synchronize: true,
logging: false
}).then(connection => {
// here you can start to work with your entities
}).catch(error => console.log(error));
Make sure you put the # sign in front of your entity decorator (3 of my entities didn't and the error message was related to the relations because it didn't find my entites)
I had the same problem and I added the following properties in my ormconfig:
"cli":{
"migrationsDir":"./src/database/migrations",
"entitiesDir": ".src/modules/cars/entities"
},
"entities": [
"./src/modules/cars/entities/**/*{.ts,.js}"
]
Works fine to me.
When reading the source code I realized that the strings are still there (inside the JS bundle), so it is calling something that is just not there as all files are inside of it, I tried importing the models directly within the app.module and not passing strings or patterns to the entities array, but the imported entity classes instead, that worked like a charm.
Given, this may not be the cleanest approach, because you have to write the paths in the imports to different modules and import them one by one. It would be great to see what others have to say on how to tackle and improve on this so that we can achieve a cleaner solution.
This works today as of Nov/2021.
Also check your connection, if entities are entities: ['dist/**/*.entity.js'], make sure your file name is {name}.entity.ts
in base entity you havent provided decorator #Entity()
#Entity()
export class BaseEntity {
#Column()
created_at: Date;
}
In my case it was because I had the repository inside the list of:
TypeOrmModule.forFeature([
TblX,
RepositoryX
]),
Removing RepositoryX from here fixed it.
I had everything correct except the filename.
It should be like
entityname.entity.ts
But it was like
entityname.ts
There is one additional case not covered by these answers. I made a copy of a resolver file and put it on the same level as my tsconfig.json file. I got this same error because, during the build and run, the top-level resolver file was processed. This meant that any entity it tried to load could not be found.
You should add this in typeorm config.
const ORMConfig: ConnectionOptions = {
type: 'postgres',
synchronize: true,
url,
logging: ['error'],
entities: [
path.join(__dirname, 'entities/*{.ts,.js}'),
path.join(__dirname, './dist/db/entities/*.ts,.js'),
],
migrations: [path.join(__dirname, 'migrations/*{.ts,.js}')],
subscribers: [path.join(__dirname, 'subscribers/*{.ts,.js}')],
cli: {
entitiesDir: 'src/db/entities',
migrationsDir: 'src/db/migrations',
subscribersDir: 'src/db/subscribers',
},
migrationsTableName: 'migration',
// add this
ssl: {
rejectUnauthorized: false,
},
};
You can replace your code entities: [User, BaseEntity] with autoLoadEntities: true
If all of the answers above do not work, you could simply just clear and rebuild the dist directory by running the following command
npm run build
Then, restart your application
npm run start:dev

How to tear down MikroOrm in NestJS

I've recently converted my AppModule to a dynamic module so that I'm able to provide different configurations to MikroOrm depending on context (E2E tests, etc) and it currently looks like this:
#Module({
imports: [
MikroOrmModule.forFeature({
entities: [Todo],
}),
],
providers: [TodoService],
controllers: [AppController, TodosController],
})
export class AppModule {
static register(options?: {
mikroOrmOptions?: MikroOrmModuleOptions;
}): DynamicModule {
return {
module: AppModule,
imports: [
MikroOrmModule.forRoot({
entities: [Todo],
type: 'postgresql',
host: process.env.DB_HOST,
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 5432,
user: process.env.DB_USER,
password: process.env.DB_PASS,
dbName: process.env.DB_DB,
...options?.mikroOrmOptions,
}),
],
};
}
}
Now I'm trying to ensure graceful shutdown of the app by disconnecting from the database, but not sure where to place a life-cycle hook in this case. It doesn't seem to be possible to have a dynamic module with life-cycle hooks, so I'm thinking of developing a separate provider that injects the orm and write the hook on that.
What would be the correct approach? Thanks.
Edit:
I came up with the following solution. Would appreciate someone indicating if this is the best way:
import { MikroORM } from 'mikro-orm';
...
#Module({
imports: [
MikroOrmModule.forFeature({
entities: [Todo],
}),
],
providers: [TodoService],
controllers: [AppController, TodosController],
})
export class AppModule implements OnModuleDestroy {
static register(options?: {
mikroOrmOptions?: MikroOrmModuleOptions;
}): DynamicModule {
return {
module: AppModule,
imports: [
MikroOrmModule.forRoot({
entities: [Todo],
type: 'postgresql',
host: process.env.DB_HOST,
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT) : 5432,
user: process.env.DB_USER,
password: process.env.DB_PASS,
dbName: process.env.DB_DB,
...options?.mikroOrmOptions,
}),
],
};
}
constructor(private orm: MikroORM) {}
async onModuleDestroy(): Promise<void> {
await this.orm.close();
}
}
As discussed in the issues, I would go with the way nestjs/typeorm is using, so using onApplicationShutdown hook.
Also linking the issue here for possible future readers:
https://github.com/dario1985/nestjs-mikro-orm/issues/10

NestJS - Can't resolve queue?

I am following doc to start to use queue. I installed #nestjs/bull, bull, #types/bull dependencies. And here is my app.module.ts:
#Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
BullModule.registerQueue({
name: 'create_checkin',
redis: {
host: 'localhost',
port: 6379,
},
}),
EventModule,
],
})
export class AppModule {}
I imported BullModule in root module. And here is my event.service.ts:
#Injectable()
export class EventService {
constructor(
#InjectQueue('create_checkin') private readonly createCheckinQueue: Queue,
) {
}
}
And when I start server, I got the following error message:
Nest can't resolve dependencies of the EventService
Please make sure that the argument BullQueue_create_checkin at index [0] is available in the EventModule context.
I don't know which step I did wrong. Someone can help me?
Had a similar problem, in my case it was enough to add the BullModule to the exports array in order to successfully run the whole project. Like this:
#Module({
imports: [
BullModule.registerQueue({ ... }),
...
],
...
exports: [
BullModule, // <— this is important!
...
]
})
Then in my service I've been able to inject the queue:
#InjectQueue('print') private queue: Queue<PrintJob>
You have to import bull module inside the module you are trying to setup queue. You can also refer https://medium.com/#shikhar01.cse14/bull-queues-in-nestjs-306c51cb0ec2
Make sure you are placing EventService under providers array in EventModule.
#Module({
providers: [EventService],
controllers :[],
imports: [YOUR_MODULES],
exports: [EventService]
})
export class EventModule {}
Try importing BullModule straight in Event Module - I had the same problem and doing it this way make it work.
#Module({
imports: [
BullModule.registerQueueAsync({
name: 'csv',
useFactory: async (config: ConfigService) => ({
redis: config.get('redis'),
}),
inject: [ConfigService],
}),
],
providers: [
CsvService
],
exports: [CsvService],
})
export class CsvModule {}
I know that it's async method, but maybe you should try.
You can do like below
#Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
EventModule,
],
})
export class AppModule {}
#Module({
imports: [
BullModule.registerQueue({
name: 'create_checkin',
redis: {
host: 'localhost',
port: 6379,
},
}),
],
})
export class EventModule {}

How use external entities in nestjs project with typeorm?

I'm trying to use type orm entities defined outside of the project. That is, I have two projects: models and core-api. Models is an npm package that exports the entities definition. So, from core-api(nestjs project) i'm trying to define the connection using those entities. Something like this:
#Module({
imports: [ TypeOrmModule.forRoot({
type: 'postgres',
host: 'postgres',
port: 5432,
username: 'myuser',
password: 'mypass',
database: 'mydb',
entities: [
__dirname + '../node_modules/something/models/src/*.entity.ts'
],
synchronize: true,
}), AModule ],
controllers: [],
providers: [],
})
export class AppModule {}
Then, in A module, I'm importing the A entity from the npm package:
import { Module } from '#nestjs/common';
import { TypeOrmModule } from '#nestjs/typeorm';
import { AsController } from './As.controller';
import { AsService } from './As.service';
import { A } from 'something/models';
#Module({
imports: [TypeOrmModule.forFeature([ A ])],
controllers: [AController],
providers: [AService],
})
export class AModule {}
This ends up with the next error:
RepositoryNotFoundError: No repository for "A" was found. Looks like this entity is not registered in current "https://stackoverflow.com/a/54191513/3943197https://stackoverflow.com/a/54191513/3943197default" connection?
I'm quite lost in how to follow since i don't understand how it works under the hood. I saw the code of typeorm and #nestjs/typeorm but I couldn't figure out how to solve it.
Thanks!
UPDATE:
I could make it work adding the entity class instead of a path like #serianox said. Also, the something/models npm package exports a standalone nest module. This module import/export the entities defined on the package. Thus, I could inject the entities on the main application.
You can pass the references to the classes to "entities" instead of the path,
for example:
import {Entity1, Entity2} from '#models'
#Module({
imports: [ TypeOrmModule.forRoot({
type: 'postgres',
host: 'postgres',
port: 5432,
username: 'myuser',
password: 'mypass',
database: 'mydb',
entities: [
Entity1, Entity2
],
synchronize: true,
}), AModule ],
controllers: [],
providers: [],
})
export class AppModule {}
I think found a workaround for this problem;
we can import modules from npm module like this
import * as Models from "your-models-package";
then iterate them and store in an array
const models = [];
for (const key in Models) {
if (Models.hasOwnProperty(key)) {
const model = Models[key];
models.push(model);
}
}
finaly use in module definition
const dbConfig = TypeOrmModule.forRoot({
type: "postgres",
database: config.postgres.db,
host: config.postgres.host,
port: Number(config.postgres.port),
username: config.postgres.user,
password: config.postgres.pass,
entities: models,
synchronize: true,
});
I hope it helps
cheers
It seems you are importing .ts files from node_modules. I believe once you have built your npm package that these entities are now .js files. You should try modifying your expression so it can parse .js files.
Personally I never use ts-node as it brings this kind of troubles and debugging is not really available with it. I feel you should always use the node runtime and parsing only .js files (also, it is closer to production runtime).

Angular Universal not rendering route

I'm having a problem with Angular Universal, although all guides are different (the official one seems outdated aswell) I've managed to run node.js server with server side rendering.
There's still a huge problem which I can't solve, because I actually have no idea on what's going on
This is the app.module.ts
#NgModule({
declarations: [
AppComponent
],
imports: [
HttpClientModule,
BrowserModule.withServerTransition({
appId: 'ta-un-certificate'
}),
RouterModule.forRoot([{
path: '', loadChildren: './page/page.module#PageModule'
}], {
enableTracing: false,
useHash: false
}),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
})
],
providers: [
SeoService,
DataService, {
provide: HTTP_INTERCEPTORS,
useClass: HttpErrorInterceptor,
multi: true
}],
bootstrap: [
AppComponent
]
})
It simply loads another module, PageModule with its components and stuff
#NgModule({
imports: [
CommonModule,
TranslateModule,
RouterModule.forChild([{
path: ':name/:id', component: PageComponent
}, {
path: '', pathMatch: 'full', component: RedirectComponent
}])
],
declarations: [
RedirectComponent,
PageComponent,
BannerComponent,
BodyComponent,
FooterComponent
]
})
export class PageModule {
}
For the server part, I made another module, app.server.module.ts which is the one used by node.js
#NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
ServerTransferStateModule
],
providers: [
SeoService
],
bootstrap: [AppComponent],
})
export class AppServerModule {
}
The problem is that if I try to call a route from node.js server, eg. http://localhost:4000/foo/bar, the node.js server console prints out a huge error, starting with this:
Error: Uncaught (in promise): ReferenceError: navigator is not defined
[...]
(it's really huge, if u need something please ask)
And page doesn't get rendered, as from cURL I get only <app-root><router-outlet></router-outlet></app-root> inside html body.
I think I've checked so many guides that I've completely lost the right way to do it, but cloning Angular Universal Starter seems doing what I'm expecting from Universal
Searching on compiled server.js script, the one executed by node, it seemed like there was an error inside Translator. So I focused searching for issues between html rendering and Translation pipe, but then I've just found a navigator.language.split inside a service (app wasn't built by me). Moved that control inside a isPlatformServer block solved my issue.
This was the breaking part of code
private _language = navigator.language.split('-')[0];
constructor(private _http: HttpClient) {
}
Which I edited as following
private _language;
constructor(#Inject(PLATFORM_ID) private platformId,
private _http: HttpClient) {
if (isPlatformServer(this.platformId)) {
this._language = 'en';
} else {
this._language = navigator.language.split('-')[0];
}
}
Fixed the issue

Resources