NextJS + TypeORM 設定

## package

```
yarn add typeorm
yarn add -D babel-plugin-transform-typescript-metadata @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators ts-node
```

## env

```.env.development.local
DATABASE_TYPE=mysql
DATABASE_HOST=localhost
DATABASE_PORT=3306
DATABASE_USERNAME=root
DATABASE_PASSWORD=
DATABASE_NAME=testdb
```

## `ormconfig.js`作成

```js:ormconfig.js
require('dotenv').config({ path: '.env.development.local' });

module.exports = {
  type: process.env.DATABASE_TYPE,
  host: process.env.DATABASE_HOST,
  port: process.env.DATABASE_PORT,
  username: process.env.DATABASE_USERNAME,
  password: process.env.DATABASE_PASSWORD,
  database: process.env.DATABASE_NAME,
  synchronize: false,
  logging: true,
  entities: ['db/entities/**/*.ts'],
  migrations: ['db/migrations/**/*.ts'],
  subscribers: ['db/subscribers/**/*.ts'],
  cli: {
    entitiesDir: 'db/entities',
    migrationsDir: 'db/migrations',
    subscribersDir: 'db/subscribers'
  }
};

```

## `tsconfig.json`一部オプション追加

```json:tsconfig.json
{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
  }
}
```

## babel plugin 追加

```js:.babelrc.js
module.exports = {
  presets: ['next/babel'],
  plugins: [
    'babel-plugin-transform-typescript-metadata',
    ['@babel/plugin-proposal-decorators', { legacy: true }],
  ]
};
```

こうしないと吐かれるれるエラー

```
Syntax error: Support for the experimental syntax 'decorators-legacy' isn't currently enabled:
```

```
ColumnTypeUndefinedError: Column type for User#** is not defined and cannot be guessed. Make sure you have turned on an "emitDecoratorMetadata": true option in tsconfig.json. Also make sure you have imported "reflect-metadata" on top of the main entry file in your application (before any entity imported).If you are using JavaScript instead of TypeScript you must explicitly provide a column type.
```

## Entity作成

```ts:db/entities/User.ts
import { Entity, BaseEntity, PrimaryGeneratedColumn, Column, OneToOne, JoinColumn } from 'typeorm';
import { UserProfile } from './index';

@Entity({ name: 'users' })
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  readonly id: number;

  @OneToOne(() => UserProfile, (userProfile: UserProfile) => userProfile.user)
  @JoinColumn()
  userProfile: UserProfile;
}
```

```ts:db/entities/UserProfile.ts
import {
  Entity,
  BaseEntity,
  PrimaryGeneratedColumn,
  Column,
  OneToOne,
  JoinColumn
} from 'typeorm';
import { User } from './index';

@Entity({ name: 'userProfiles' })
export class UserProfile extends BaseEntity {
  @PrimaryGeneratedColumn()
  readonly id: number;

  @OneToOne(() => User, (user: User) => user.userProfile)
  @JoinColumn()
  user: User;
```


importするときは、以下のようにまとめてこのファイルからimportするようにしないとエラーになる

```ts:db/entities/index.ts
import { User } from './User';
import { UserProfile } from './UserProfile';

export { User, UserProfile };
```

こうしないと吐かれるれるエラー

```
ReferenceError: Cannot access 'User' before initialization
```


## migration実行

### package.json

```json:package.json
{
  "scripts": {
    "typeorm": "ts-node ./node_modules/typeorm/cli.js",
    "db:migrate:create": "yarn typeorm migration:generate -n",
    "db:migrate:run": "yarn typeorm migration:run",
    "db:reset:init": "yarn typeorm schema:drop; rm -rf db/migrations/*; yarn db:migrate:create init; yarn db:migrate:run"
  }
}

```

```
yarn db:reset:init
```

## connection

```ts:db/db.ts
import 'reflect-metadata';
import { getConnection, createConnection, Connection } from 'typeorm';
import { User, UserProfile } from '@db/entities';

const host = process.env.DATABASE_HOST || '';
const port = Number(process.env.DATABASE_PORT) || 3306;
const username = process.env.DATABASE_USERNAME || '';
const password = process.env.DATABASE_PASSWORD || '';
const database = process.env.DATABASE_NAME || '';

let connectionReadyPromise: Promise<Connection> | null = null;

export const 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
      const connection = await createConnection({
        type: 'mysql',
        host,
        port,
        username,
        password,
        database,
        entities: [User, UserProfile],
        synchronize: false,
        logging: true
      });

      return connection;
    })();
  }

  return connectionReadyPromise;
};
```

## 使い方

```ts
import { prepareConnection } from '@db/db';
import { User } from '@db/entities';

(async () => {
  const db = await prepareConnection();
  const user = await db.getRepository(User).find();
  console.log(user);
})();
```

### 参考

- https://programmagick.com/blogs/node_typeorm/#typeorm%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB-
- https://dev.to/unframework/getting-typeorm-to-work-with-next-js-and-typescript-1len