How should TypeORM in a TypeScript project be configured so it works everywhere?

Viewed 1978

I'm struggling to find a configuration that works for TypeORM in a TypeScript project in all environments.

For example, starting with this ormconfig.js:

var dotenv = require("dotenv")
dotenv.config()

var connectionOptions = [
  {
    "name": "default",
    "type": "postgres",
    "url": process.env.DATABASE_URL,
  },
  {
    "name": "testing",
    "type": "postgres",
    "url": `${process.env.DATABASE_URL}_test`,
  }];

module.exports = connectionOptions

But then, when I try to start the application, I get this error:

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

So, I add the entities to the config:

var dotenv = require("dotenv")
dotenv.config()

var connectionOptions = [
  {
    "name": "default",
    "type": "postgres",
    "url": process.env.DATABASE_URL,
    "entities": ["src/entity/**/*"],
  },
  {
    "name": "testing",
    "type": "postgres",
    "url": `${process.env.DATABASE_URL}_test`, // TODO: fix
    "entities": ["src/entity/**/*"],
  }];

module.exports = connectionOptions

At this point, running the app in dev (ts-node-dev src/main.ts) works. But when I compile it and try to run the JavaScript, I get this error:

C:\Users\pupeno\Documents\Flexpoint Tech\js\exp7\backend\src\entity\User.ts:1
import {BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn} from "typeorm"
^^^^^^

SyntaxError: Cannot use import statement outside a module

It's trying to load the User.ts source file instead of the compiled User.js.

The way I'm running my app in prod is by running node build/src/main.js, the problem is that ormconfig.js is still at the top level, printing __dirname and __filename shows:

C:\Users\pupeno\Documents\Flexpoint Tech\js\exp7\backend
C:\Users\pupeno\Documents\Flexpoint Tech\js\exp7\backend\ormconfig.js

How are you supposed to configure TypeORM to work in both development and production?

Renaming my entity to User.entity.ts and setting entities this way:

var dotenv = require("dotenv")
dotenv.config()

var connectionOptions = [
  {
    "name": "default",
    "type": "postgres",
    "url": process.env.DATABASE_URL,
    "entities": [__dirname + '/**/*.entity{.ts,.js}'],
  },
  {
    "name": "testing",
    "type": "postgres",
    "url": `${process.env.DATABASE_URL}_test`, // TODO: fix
    "entities": [__dirname + '/**/*.entity{.ts,.js}'],
  }];

console.log(connectionOptions)

module.exports = connectionOptions

cause the same error, as Node tries to load a TypeScript file:

(node:19344) UnhandledPromiseRejectionWarning: C:\Users\pupeno\Documents\Flexpoint Tech\js\exp7\backend\src\entity\User.entity.ts:1
import {BaseEntity, Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn} from "typeorm"
^^^^^^

SyntaxError: Cannot use import statement outside a module
2 Answers

My solution to this problem is to import models as classes instead of trying to get the glob to work. I also don't use an ormconfig.js but keep everything in TypeScript with a connection manager class. So your example rewritten for me would look like this:

import {createConnection, Connection} from "typeorm";

import {User} from "./models";

export getConnection = async (): Promise<Connection> => {
  const connection = await createConnection({
    name: "testing",
    type: "postgres",
    url: process.env.DATABASE_URL,
    entities: [User]
  });

  return await connection.connect();
}

That can work nicely if you have a small number of models, but it doesn't scale real well. If you have many models, you can export them all from an index and then import them all from that index.

export * from "./User";
export * from "./Foo";
export * from "./Bar";

Now here's the import

import {createConnection, Connection} from "typeorm";

import * as models from "./models";

export getConnection = async (): Promise<Connection> => {
  const connection = await createConnection({
    name: "testing",
    type: "postgres",
    url: process.env.DATABASE_URL,
    entities: [...Object.values(models)]
  });

  return await connection.connect();
}

And finally dotenv is a little simplistic for doing this work for my tastes. I recommend you check out https://github.com/lorenwest/node-config and see how you can have base config, env-specific config and envvars all play together well.

I got a similar issue. The solution is to run the app from inside the build directory, not the project directory, i.e. do cd dist and then node app.js instead of node dist/app.js.

My project structure is:

my-app-name/
-- node_modules/
-- dist/  # there go compiled JS files
-- entity/  # entities in TS
-- migration/  # migrations in TS
-- app.ts
-- tsconfig.json
-- ormconfig.json
-- package.json

# ormconfig.json
{
  # database specific code is omitted
  "entities": [
    "./entity/*.js"
  ],
  "migrations": [
    "./migration/*.js"
  ]
}

# tsconfig.json
{
  "files": [
    "app.ts",
    "entity/author.ts",
    "entity/photo.ts",
    "migration/1618859843962-AddAuthorToPhotos.ts"
  ],
  "compilerOptions": {
    "outDir": "dist",
    "target": "esnext",
    "strict": true,
    "lib": [
      "ES2020"
    ],
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "module": "commonjs",
    "moduleResolution": "node"
  }
}
Related