Nx: node-apps: TypeORM not comparable with full WebPack

Created on 7 Oct 2018  ยท  22Comments  ยท  Source: nrwl/nx

I am exploring new node-apps builder with NestJS framework. so far I have success with ng test api, ng test api-e2e ng lint api etc. but facing an issue with ng serve api due to TypeORM runtime expects some entity models available outside webpack bundle.

TypeORM runtime expects entities source available loosely at a preconfigured location e.g., ./**/**.entity{.ts,.js}
When we run ng serve api , with TYPEORM_ENTITIES=./dist/**/**.entity.js env variable, where I pre-generated JS entities with npm run api:prestart:prod, I am getting following error:

[ExceptionHandler] No repository for "Notification" was found. Looks like this entity is not registered in current "default" connection? 

My ask is, either we have to use ts-node for ng serve instead of webpack, or allowing users to exclude some files form including in webpack bundle, and compile them loosely in /dist

sample repo to reproduce:
https://github.com/xmlking/ngx-starter-kit

node bug

Most helpful comment

There is a possible workaround using webpack context API.

carbon 5

All 22 comments

Sorry, I tried my best to understand the environment you are working in so I might not be correct.

I was able to start a typeorm connection with mysql after reconfiguring it with my environment.

The error indicates that there was a problem with locating the entities. You might want to make sure that the path for the entities in the environment variable is an absolute path. Also, you might want to change /**/**.entity.js to /**/*.entity.js.

Let me know if this helps you?

Those entity files are generated and available in the dist. If you're using relative paths.. you should be able to use a value of './**/*.entity.js'. The cwd of the process will be the outputPath of the build.. which defaults to dist/apps/appName.

I tried TYPEORM_ENTITIES=/full_path_to/ngx-starter-kit/dist/apps/api/**/*.entity.js
but still facing same error, will you be able to share sample setup project? or can you see any issues with my repo?

query: COMMIT [Nest] 55876 - 10/9/2018, 5:29:59 PM [InstanceLoader] TypeOrmCoreModule dependencies initialized +509ms [Nest] 55876 - 10/9/2018, 5:29:59 PM [ExceptionHandler] No repository for "Notification" was found. Looks like this entity is not registered in current "default" connection? +1ms RepositoryNotFoundError: No repository for "Notification" was found. Looks like this entity is not registered in current "default" connection?
image

some one saying glob pattern will not work for typeorm when used with webpack!
https://github.com/nestjs/nest/issues/755#issuecomment-394073763

workaround:
declare entities directly instead of glob pattern ['.//.entity{.ts,.js}']

        entities: [Notification, User],

Ref: https://github.com/xmlking/ngx-starter-kit/blob/develop/apps/api/src/core/core.module.ts

There is a possible workaround using webpack context API.

carbon 5

workaround:
declare entities directly instead of glob pattern ['.//.entity{.ts,.js}']

        entities: [Notification, User],

Ref: https://github.com/xmlking/ngx-starter-kit/blob/develop/apps/api/src/core/core.module.ts

This will not work if you plan to use migrations with typeorm.

Workaround for migrations:
Changed tsconfig module to commonjs. Generation of migrations works only with commonjs or umd.
Creating of ormConfig.json inside the root directory
ormconfig

Import
appmodule

@deimosOmegaChan that works. The commonjs was the problem here. Thank you!
This works great with migration files that are based on sql. Do you have experience with seedings that reference the entities?

export class SeedDefaultGroups1524199022084 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<any> {
    const gUser = await queryRunner.manager.getRepository<Group>(Group).save(
      plainToClass(Group, {
        name: 'user',
        title: 'User'
      })
    );
}}

One method that comes to mind is exporting the whole project in to .js files:

"build:ball-files": "rimraf dist && tsc -p ./apps/api/tsconfig.app.json"

then convert the tsconfig paths to relative paths

tscpaths -p tsconfig.json -s ./ -o ./dist/

and build the applicatin via

ng serve api

to overwrite the main.js file. Now the entities and migration files are there and could be read on application startup.

I am shure this is not good pipeline to apply seeds to the application....

No, I don't have any experience with seedings that reference the entities.

Solution by using webpackConfig Options from builder: @nrwl/builders:node-build
With this solution: TypeOrmModule.forRoot() works
angular
Need to use two ormconfig files: One for cli, the other one for production
ormconfig.json

{
  "type": "sqlite",
  "synchronize": false,
  "logging": true,
  "migrationsRun": true,
  "database": "database/db.sqlite",
  "entities": ["dist/apps/backend/app/database/entity/*.js"],
  "migrations": ["dist/apps/backend/app/database/migration/*.js"],
  "subscribers": ["dist/apps/backend/app/database/subscriber/*.js"]
}

ormconfigdev.json

{
  "type": "sqlite",
  "synchronize": false,
  "logging": true,
  "migrationsRun": true,
  "database": "database/db.sqlite",
  "entities": ["apps/backend/src/app/database/entity/*.ts"],
  "migrations": ["apps/backend/src/app/database/migration/*.ts"],
  "subscribers": ["apps/backend/src/app/database/subscriber/*.ts"],
  "cli": {
    "entitiesDir": "apps/backend/src/app/database/entity",
    "migrationsDir": "apps/backend/src/app/database/migration",
    "subscribersDir": "apps/backend/src/app/database/subscriber"
  }
}

webpackConfig

const glob = require('glob');
const path = require('path');

const ormConfig = require('./ormconfigdev.json')

module.exports = function (webpackConfig, context) {
  const parentDirectory = path.dirname(context.options.sourceRoot).split(path.sep).pop()
  // clean base directory for dist
  const baseDirectory = path.resolve(__dirname, 'dist/' + parentDirectory);
  // generate additionalEntries
  const additionalEntries = {
    ...getAdditionalEntries(ormConfig.entities[0]),
    ...getAdditionalEntries(ormConfig.migrations[0]),
    ...getAdditionalEntries(ormConfig.subscribers[0])
  }

  // merge entries
  webpackConfig.entry = {
    ...webpackConfig.entry,
    ...additionalEntries
  }

  //output
  webpackConfig.output = {
    path: baseDirectory,
    filename: '[name].js',
    libraryTarget: 'commonjs',
  }

  return webpackConfig
}

function getAdditionalEntries(globPattern) {
  const relativePaths = glob.sync(globPattern, {
    absolute: false
  });
  const additionalEntries = {};
  for (let index = 0; index < relativePaths.length; index++) {
    const relativePath = relativePaths[index]
    const absolutePath = glob.sync(relativePath, {
      absolute: true
    });
    const key = relativePath.split('/src/')[1].split('.').slice(0, -1).join('.');
    additionalEntries[key] = absolutePath;
  }
  return additionalEntries;
}

How is this solution?

same problem with nestjs-config... https://github.com/nrwl/nx/issues/1430

@deimosOmegaChan it properly transpiles the files, and it works, until I want to use the entity in a module, where I have to import the entities like TypeOrmModule.forFeature([.....
So after using forFeature, I am gettting this "repository not found for..." error.
Any ideas on that? Anyone else has this problem too?

+1

I did today some tests to find out how to solve this problem.
At my solution I combined with the build command and nodemon to run the main.js file.
I published it to the repo deimosOmegaChan/nrwl-nest-typeorm-example

the real problem is nx when using ng serve puts entire nestjs project into single main.js file(look at dist/apps/app_name) and thats why typeorm cant find entities even the glob you've provided is correct.

questions is how do you use ng/nx scripts to build nest during dev and production without bundling it to a single file ?

@deimosOmegaChan even though it may be working thats not correct solution It is supposed to work out of the box. If I wanted to rewrite Nestjs's already working scripts and mimic poorly their cli / starter project I would just go with lerna

Dear everyone in this thread,

i managed to solve the migrations issue in my nrwl/nx application. This is, what i did:
1) (optional!) install scripty (https://github.com/testdouble/scripty), which lets you execute shell-scripts from npm; npm install --save-dev scripty. While this step is optional, it makes your life easier

2) add a few npm scripts to your package.json file to easily trigger the commands:

{
  // ...
  scripts : {
    // ...
    "migrate:create": "scripty",
    "migrate:run": "scripty"
  }
}

Note that those npm scripts just call the previous mentioned scripty library.

3) Create the proper scripty shell scripts: in your root folder (next to the package.json file) create a scripts folder. This folder is "crawled" by scripty when invoking a npm script. For example, if your command is called migrate:create (see step 2), you need to create a file in scripts/migrate/create.sh.

scripts/migrate/create.sh

#!/usr/bin/env sh

cd ./apps/"$1"/src
npx typeorm migration:create -n "$2"

scripts/migrate/run.sh

#!/usr/bin/env sh

cd ./apps/"$1"/src
npx tsc database/migrations/src/*.ts --outDir database/migrations/generated
npx typeorm migration:run

4) add an ormconfig.js file into the root directory of your application that works with a database. As your mono-repository may contain multiple apps that interact with databases, you may want to have different ormconfig.js files.

apps/api/src/ormconfig.js

module.exports = {
  type: 'postgres',
  host: 'localhost',
  port: 5432,
  username: 'databaseuser',
  password: 'databasepassword',
  database: 'databasename',
  migrationsTableName: 'migrations',
  migrations: ['database/migrations/generated/*.js'],
  cli: {
    migrationsDir: 'database/migrations/src',
  },
  synchronize: false,
};

5) To create a new migration, call

# npm run migrate:create PROJECTNAME MIGRATIONNAME
npm run migrate:create api CreateUsersTable

This will create a new file apps/api/src/database/migrations/src/123456789-CreateUsersTable.ts. You can now fill this migration with life!

6) To run (i.e., execute) all migrations and replicate them to the database, you can call

# npm run migrate:run PROJECTNAME
npm run migrate:run api

This command will first transpile all existing apps/api/src/database/migrations/src/*.ts into javascript and store them in apps/api/src/database/migrations/generated. The latter are then executed by typeorm.

I hope this solution works for you guys!

If you use VSCode, you can create a .vscode/tasks.json file in your project (top level) and add the following content there:

./.vscode/tasks.json

{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "label": "migration:create",
      "type": "shell",
      "command": "npm run migrate:create ${input:availableApps} -n ${input:userInput}"
    },
    {
      "label": "migration:run",
      "type": "shell",
      "command": "npm run migrate:run ${input:availableApps}"
    }
  ],
  "inputs": [
    {
      "type": "promptString",
      "id": "userInput",
      "description": "Enter a Value"
    },
    {
      "type": "pickString",
      "id": "availableApps",
      "description": "Select a nrwl/nx app to debug?",
      "options": ["appA", "appB", "appC"]
    }
  ]
}

Now you can simply use the VSCode Task Runner (Press F1, Select "Task: Run" and then select the defined Task). This will show you some nice GUI elements to select / input the proper information. Please note that you need to adapt the available Options in the availableApps input!

Hi, sorry about this.

This was mislabeled as stale. We are testing ways to mark _not reproducible_ issues as stale so that we can focus on actionable items but our initial experiment was too broad and unintentionally labeled this issue as stale.

I've solved it with next content in package.json

"migration:create": "ts-node -O '{\"module\": \"commonjs\"}' node_modules/typeorm/cli.js migration:create",
"migration:generate": "ts-node -O '{\"module\": \"commonjs\"}' node_modules/typeorm/cli.js migration:generate",
"migration:show": "ts-node -O '{\"module\": \"commonjs\"}' node_modules/typeorm/cli.js migration:show",
"migration:run": "ts-node -O '{\"module\": \"commonjs\"}' node_modules/typeorm/cli.js migration:run -t=false",
"migration:revert": "ts-node -O '{\"module\": \"commonjs\"}' node_modules/typeorm/cli.js migration:revert",
"schema:drop": "ts-node -O '{\"module\": \"commonjs\"}' ./node_modules/typeorm/cli.js schema:drop",
"schema:sync": "ts-node -O '{\"module\": \"commonjs\"}' ./node_modules/typeorm/cli.js schema:sync"

And .env file

TYPEORM_URL=postgres://user:password@localhost/database
TYPEORM_ENTITIES=libs/backend/**/*.entity.ts
TYPEORM_MIGRATIONS=apps/api/src/migrations/*.ts
TYPEORM_MIGRATIONS_DIR=apps/api/src/migrations
โžœ yarn schema:drop  
yarn run v1.22.4
$ ts-node -O '{"module": "commonjs"}' ./node_modules/typeorm/cli.js schema:drop
query: START TRANSACTION
query: SELECT 'DROP VIEW IF EXISTS "' || schemaname || '"."' || viewname || '" CASCADE;' as "query" FROM "pg_views" WHERE "schemaname" IN (current_schema()) AND "viewname" NOT IN ('geography_columns', 'geometry_columns', 'raster_columns', 'raster_overviews')
query: SELECT 'DROP TABLE IF EXISTS "' || schemaname || '"."' || tablename || '" CASCADE;' as "query" FROM "pg_tables" WHERE "schemaname" IN (current_schema()) AND "tablename" NOT IN ('spatial_ref_sys')
query: DROP TABLE IF EXISTS "public"."migrations" CASCADE;
query: DROP TABLE IF EXISTS "public"."users" CASCADE;
query: SELECT 'DROP TYPE IF EXISTS "' || n.nspname || '"."' || t.typname || '" CASCADE;' as "query" FROM "pg_type" "t" INNER JOIN "pg_enum" "e" ON "e"."enumtypid" = "t"."oid" INNER JOIN "pg_namespace" "n" ON "n"."oid" = "t"."typnamespace" WHERE "n"."nspname" IN (current_schema()) GROUP BY "n"."nspname", "t"."typname"
query: COMMIT
Database schema has been successfully dropped.
โœจ  Done in 2.57s.
โžœ yarn migration:generate -n Users
yarn run v1.22.4
$ ts-node -O '{"module": "commonjs"}' node_modules/typeorm/cli.js migration:generate -n Users
Migration /Users/i_skiridomov/Projects/nx-koko/apps/api/src/migrations/1592933949014-Users.ts has been generated successfully.
โœจ  Done in 2.20s.
โžœ yarn migration:run
yarn run v1.22.4
$ ts-node -O '{"module": "commonjs"}' node_modules/typeorm/cli.js migration:run -t=false
query: SELECT * FROM "information_schema"."tables" WHERE "table_schema" = current_schema() AND "table_name" = 'migrations'
query: CREATE TABLE "migrations" ("id" SERIAL NOT NULL, "timestamp" bigint NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_8c82d7f526340ab734260ea46be" PRIMARY KEY ("id"))
query: SELECT * FROM "migrations" "migrations" ORDER BY "id" DESC
0 migrations are already loaded in the database.
1 migrations were found in the source code.
1 migrations are new migrations that needs to be executed.
query: CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "isVerified" boolean NOT NULL DEFAULT false, "resetPasswordToken" character varying DEFAULT null, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))
query: INSERT INTO "migrations"("timestamp", "name") VALUES ($1, $2) -- PARAMETERS: [1592933760663,"Users1592933760663"]
Migration Users1592933760663 has been executed successfully.
โœจ  Done in 2.59s.
โžœ tree libs 
libs
โ”œโ”€โ”€ api-interfaces
โ”‚   โ”œโ”€โ”€ README.md
โ”‚   โ”œโ”€โ”€ jest.config.js
โ”‚   โ”œโ”€โ”€ src
โ”‚   โ”‚   โ”œโ”€โ”€ index.ts
โ”‚   โ”‚   โ””โ”€โ”€ lib
โ”‚   โ”‚       โ””โ”€โ”€ api-interfaces.ts
โ”‚   โ”œโ”€โ”€ tsconfig.json
โ”‚   โ”œโ”€โ”€ tsconfig.lib.json
โ”‚   โ”œโ”€โ”€ tsconfig.spec.json
โ”‚   โ””โ”€โ”€ tslint.json
โ””โ”€โ”€ backend
    โ””โ”€โ”€ users
        โ”œโ”€โ”€ README.md
        โ”œโ”€โ”€ jest.config.js
        โ”œโ”€โ”€ src
        โ”‚   โ”œโ”€โ”€ index.ts
        โ”‚   โ””โ”€โ”€ lib
        โ”‚       โ”œโ”€โ”€ backend-users.controller.spec.ts
        โ”‚       โ”œโ”€โ”€ backend-users.controller.ts
        โ”‚       โ”œโ”€โ”€ backend-users.module.ts
        โ”‚       โ”œโ”€โ”€ backend-users.service.spec.ts
        โ”‚       โ”œโ”€โ”€ backend-users.service.ts
        โ”‚       โ”œโ”€โ”€ create-user.dto.ts
        โ”‚       โ””โ”€โ”€ user.entity.ts
        โ”œโ”€โ”€ tsconfig.json
        โ”œโ”€โ”€ tsconfig.lib.json
        โ”œโ”€โ”€ tsconfig.spec.json
        โ””โ”€โ”€ tslint.json

I found how to import the entity in a module with the webpack method. I am currently testing this approach.
I inject using the name of entity instead of the class.
As example for the entity Project:
TypeOrmModule.forFeature(['Project' as any])
@InjectRepository('Project' as any)

Another problem may appear if you would like to import some shared code in your entity files like:

// inside user.entity.ts
import { SomeEnum } from "@yourorg/api-interfaces";

I tried with compilerOptions.path but with no success.

@uRTLy Does anyone know how I can modify my workspace.json to tell Webpack not to bundle into one file

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jon301 picture jon301  ยท  3Comments

Svancara picture Svancara  ยท  3Comments

kmkatsma picture kmkatsma  ยท  3Comments

ZempTime picture ZempTime  ยท  3Comments

Koslun picture Koslun  ยท  3Comments