How to Use TypeORM with Webpack

TypeORM is a great option if you are looking for an ORM to use on a Node project. However, if you use Webpack to bundle your backend code, you may face some issues when trying to integrate TypeORM into your project.

The Problem

After you’ve integrated TypeORM into your project, you should have no problem building your project. However, if you try running your bundled code, you will see something similar to the following error:

import {Entity, PrimaryGeneratedColumn, Column} from “typeorm”;
^^^^^^
SyntaxError: Cannot use import statement outside a module

This happens because TypeORM is running inside the Webpack build and trying to require the un-compiled code that is specified in the entities, migrations, and subscribers entries in the ormconfig.

Potential Solution #1

When I first encountered this issue, I tried to solve it by excluding my entities and migrations from being bundled in the Webpack output, then specifying the compiled entities in the ormconfig:

{
...
  "entities": ["dist/src/entity/**/*.js"],
  "migrations": ["dist/src/migration/**/*.js"],
  "subscribers": ["dist/src/subscriber/**/*.js"],
...
}

This worked when I ran the server code but caused tools like Jest that don’t compile the code to choke.

Potential Solution #2

Another solution I tried was to not rely on our ormconfig to specify where our entities and migrations are coming from. Instead, it’s possible to import them ourselves when we call createConnection:

import { User } from 'src/entity/User.ts';

const baseConnectionOpts = await getConnectionOptions();
await createConnection({
        ...baseConnectionOpts,
        entities: [
            User // pass your entities in here
        ]
    })

This works for both Jest and the server build! However, we no longer get the convenience of automatically detecting any entities or migrations that are added to our project.

In order to change that, we’ll need a way to import all of the desired exports from a given directory.

require.context to the Rescue!

Webpack gives us require.context, which allows us to get all matching modules from a given directory. This means we can get all of our entities and migrations automatically!

import {
  Connection,
  ConnectionOptions,
  createConnection as createTypeOrmConnection,
  getConnection,
  getConnectionOptions,
} from "typeorm";

type EntitiesAndMigrationsOpts = Pick<
  ConnectionOptions,
  "entities" | "migrations"
>;

const importAllFunctions = (
  requireContext: __WebpackModuleApi.RequireContext
) =>
  requireContext
    .keys()
    .sort()
    .map((filename) => {
      const required = requireContext(filename);
      return Object.keys(required).reduce((result, exportedKey) => {
        const exported = required[exportedKey];
        if (typeof exported === "function") {
          return result.concat(exported);
        }
        return result;
      }, [] as any);
    })
    .flat();

const entitiesViaWebpack: NonNullable<
  EntitiesAndMigrationsOpts["entities"]
> = importAllFunctions(require.context("./entity/", true, /\.ts$/));
const migrationsViaWebpack: NonNullable<
  EntitiesAndMigrationsOpts["migrations"]
> = importAllFunctions(require.context("./migration/", true, /\.ts$/));

export const createConnection = async (): Promise<Connection> => {
  const baseConnectionOptions = await getConnectionOptions();
  const connectionOptions: ConnectionOptions = {
    ...baseConnectionOptions,
    entities: entitiesViaWebpack,
    migrations: migrationsViaWebpack,
  };
  return createTypeOrmConnection(connectionOptions);
};

Note: If you are using Typescript, make sure to yarn add@types/webpack-env so you can use require.context without type errors.

But What About Jest?

While the above solution works when running our backend, it will not work when we want to create connections in Jest. This is because require.context is only available when running in code bundled by Webpack.

We can fix this by extracting our require.context calls to another file:


//entities-and-migrations.ts
import { ConnectionOptions } from "typeorm";

export type EntitiesAndMigrationsOpts = Pick<ConnectionOptions, "entities" | "migrations">;

const importAllFunctions = (
  requireContext: __WebpackModuleApi.RequireContext
) =>
  requireContext
    .keys()
    .sort()
    .map((filename) => {
      const required = requireContext(filename);
      return Object.keys(required).reduce((result, exportedKey) => {
        const exported = required[exportedKey];
        if (typeof exported === "function") {
          return result.concat(exported);
        }
        return result;
      }, [] as any);
    })
    .flat();

const entitiesViaWebpack: NonNullable< EntitiesAndMigrationsOpts["entities"] > = importAllFunctions(require.context("./entity/", true, /\.ts$/));
const migrationsViaWebpack: NonNullable< EntitiesAndMigrationsOpts["migrations"] > = importAllFunctions(require.context("./migration/", true, /\.ts$/));

export const entitiesAndMigrations: EntitiesAndMigrationsOpts = {
  entities: entitiesViaWebpack,
  migrations: migrationsViaWebpack,
};

And then conditionally requiring that file, like so:


import {
  Connection,
  ConnectionOptions,
  createConnection as createTypeOrmConnection,
  getConnection,
  getConnectionOptions,
} from "typeorm";

type CreateConnectionOpts = {
  loadEntitiesAndMigrations: boolean;
};
export const createConnection = async (
  opts: CreateConnectionOpts
): Promise => {
  let entitiesAndMigrations = null;
  if (opts.loadEntitiesAndMigrations) {
    entitiesAndMigrations = require("./entities-and-migrations")
      .entitiesAndMigrations;
  }
  const baseConnectionOptions = await getConnectionOptions();
  const connectionOptions: ConnectionOptions = {
    ...baseConnectionOptions,
    ...entitiesAndMigrations,
  };
  return createTypeOrmConnection(connectionOptions);
};

By putting require("./entities-and-migrations") into a conditional, we only call require.context when loadEntitiesAndMigrations is set to true. Otherwise, TypeORM uses the values set in our ormconfig. This allows us to create connections in a test environment by setting loadEntitiesAndMigrations to false!

If you would like to see this code in action, I pushed a sample project to github.