Getting TypeORM to work with Next.js and TypeScript
Tags: nextjs, typeorm, typescript, babel
Published: 2021-02-14 22:10:15

---
title: Getting TypeORM to work with Next.js and TypeScript
published: true
description: TypeORM config needs special tweaks to work inside the Next.js build pipeline for TypeScript; here is what's missing from the docs
tags: nextjs, typeorm, typescript, babel
canonical_url: https://unframework.com/2021/02/14/getting-typeorm-to-work-with-next-js-and-typescript/
---

I was setting up a new Next.js app (using NextAuth for SSO) and decided to use TypeORM as the database persistence layer. Next.js is great at running TypeScript code without hiccups and TypeORM, while new to me, seemed promising in its minimalism. However, I ran into some non-trivial error messages and bugs trying to get them to play together. And even though most answers were already discovered by other kind folks on the internet, it took me a while to track it all down and combine together into one solution.

There were three key areas to resolve:

- importing TypeScript-formatted entities in Next.js and CLI contexts
- support for TypeORM's decorator syntax + `reflect-metadata` in Next.js build pipeline
- preventing hot-module-reload (HMR) class instance confusion during development runtime

## Importing Entities and Connection Config

Normally, TypeORM runs inside a vanilla Node.js environment, which means it cannot consume TypeScript files (such as entity class definitions) without precompilation. For example, there is a common error that happens when the `entities` path in TypeORM config refers to TS source files (i.e. `src/entity/*.ts`):

```
Error during schema synchronization:
/project/path/src/entity/User.ts:1
import {
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:979:16)
...
```

With Next.js and its built-in support for TypeScript I did not expect this problem to happen, but still ended up encountering the above error message.

The reason is that when TypeORM creates a new connection, it tries to load all the entity class files dynamically based on a path wildcard. That process bypasses the entire Next.js Babel bundling pipeline and falls back on Node.js's built-in module loader. So even though my Next.js server code can import and run entity class TS files just fine, the TypeORM connection initializer lives in a "parallel universe" and naively tries to load them from scratch on its own, which then fails.

I tried to use `ts-node` to compile TS modules on the fly as they get loaded by TypeORM, but then I got a different kind of error:

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

In this scenario I ended up having *two clones* of each entity class co-existing in memory: one loaded and instantiated by TypeORM + `ts-node`, and another one bundled by Next.js pipeline with the rest of server code. Hence the class reference confusion.

Instead, my approach was to name and import all the entity files explicitly, without an `entities` wildcard, like so:

```ts
import { User } from './entity/User';
import { Account } from './entity/Account';
// etc
```

And then pass an explicit options object to `createConnection()` with entity classes directly referenced like this:

```ts
createConnection({
  entities: [
    User,
    Account,
    // etc
  ]
})
```

Correspondingly, I removed the `ormconfig.js` file to avoid any further conflicts.

I did keep around a separate `ormconfig.cli.js` file just for CLI schema sync and migrations. For that to work, I installed `ts-node` and added `require('ts-node/register')` to the top of the config file so that TS entity definitions can be loaded with no extra fuss. The command-line script looks like this:

```
typeorm --config ormconfig.cli.js schema:sync
```

## Decorator Syntax and `reflect-metadata` in Next.js

TypeORM entity class definitions use decorator syntax (e.g. `@Entity()`, `@Column()`, etc). Also, there has to be a bit of special plumbing to let TypeORM read TypeScript field types such as `string` and infer database column types such as `varchar`. To make the above work, the TypeORM documentation asks to install a package called `reflect-metadata` and also to tweak `tsconfig.json` to set `emitDecoratorMetadata` and `experimentalDecorators` to true.

However, Next.js does not use TSC (the original TypeScript compiler) and instead relies on Babel's `@babel/preset-typescript` package. Because of that, those `tsconfig.json` tweaks do not have any effect.

Instead, I added custom Babel configuration in my Next.js project and included the equivalent options for Babel (see [this issue about decorator support](https://github.com/vercel/next.js/issues/5231#issuecomment-423436252) and [this issue about metadata](https://github.com/typeorm/typeorm/issues/2897)). This is what's in the resulting `.babelrc` file:

```
{
  "presets": [
    [
      "next/babel",
      {
        "class-properties": {
          "loose": true
        }
      }
    ]
  ],
  "plugins": [
    "babel-plugin-transform-typescript-metadata",
    ["@babel/plugin-proposal-decorators", { "legacy": true }]
  ]
}
```

Extra packages needed to be installed (`class-properties` plugin is already included with Next.js): `@babel/plugin-proposal-decorators babel-plugin-transform-typescript-metadata @babel/core`.

Note: you can omit installing `reflect-metadata` and `babel-plugin-transform-typescript-metadata` if you specify database column types explicitly. Then TypeORM does not have to infer anything from TS types. For some folks that might be preferable from a stability perspective, but at the cost of being more verbose.

## Next.js HMR and TypeORM Entity Classes

Hot-module-reloading (HMR) throws another monkey-wrench in the works.

During development, every time you edit your Next.js pages, API routes or other files like entity classes your code ends up being recompiled and reloaded from scratch. Because TypeORM connection manager is not aware of entity class reloads, the connection object quickly gets out of sync and stops being useful.

E.g. if you have a User entity class, Next.js will load and create a class reference for it - let's call it "User v1". That reference is passed to `createConnection` and of course the rest of your code uses it too. Now, once you edit that class file, Next.js will perform a hot reload, and there are now *two different class references* living inside runtime memory. One is the original "User v1" and another one is the freshly recompiled "User v2". Your route code is now using the "User v2" class reference, but the connection still has "User v1" in its list of known entities. When you try to e.g. call `getRepository(User)` in your code, you will not be passing the same class reference as what TypeORM "knows", so you will get this error again:

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

I saw a few GitHub issues discussing solutions to this (such as [this workaround](https://github.com/typeorm/typeorm/issues/6241#issuecomment-643690383)). For me, the ultimate answer was to simply get any prior connection and close it before opening a new one.

Here is a sample code snippet; I put it in a shared central file like `src/db.ts`:

```ts
let connectionReadyPromise: Promise<void> | null = null;

function prepareConnection() {
  if (!connectionReadyPromise) {
    connectionReadyPromise = (async () => {
      // clean up old connection that references outdated hot-reload classes
      try {
        const staleConnection = getConnection();
        await staleConnection.close();
      } catch (error) {
        // no stale connection to clean up
      }

      // wait for new default connection
      await createConnection({
        // connection options go here
      });
    })();
  }

  return connectionReadyPromise;
}
```

Then in any function that calls out to TypeORM I add this before performing any database actions:

```
await prepareConnection();
```

It may seem a bit onerous to include this in many different spots but there always has to be some sort of wait-until-ready logic for database usage anyway, so this ends up serving that exact purpose.

# Conclusion

I hope that the TypeORM docs eventually include Babel-specific config recipes and HMR-friendly "connection refresh" helpers like the above. Also, the dynamic wildcard loader for entities would ideally be pluggable into a bundler pipeline like Next.js's. But for now this combination of settings worked pretty well, and I hope it helps you too!