Typormを使ってリレーションを組む

## 目標

- userテーブルとtodoテーブルをつなげる
- どのユーザーがどんなタスクを持っているかを表示させる
- ユーザーにつき複数のタスクがつくようにする

## 目的

- typeormのリレーションの組み方を学ぶ
- 1対多の紐付け方を学ぶ

## 手順

- 準備物・環境設定はこちら
[Typeormの設定のメモ](https://qiita.com/cedrictarou/items/9da496fca66b5c3ac7d8)
[typescriptでdoenvを使うときのメモ](https://qiita.com/cedrictarou/items/c191a5c370a55a1e62de)
[Typescriptとexpress.jsの環境設定](https://qiita.com/cedrictarou/items/24f9dbc1391193bae24b)

- ルートを整える

`/users` に対応するルートを別ファイルにする

### routesフォルダーを作りそこにusers.tsを作る

```tsx
// users.tsを以下のようにする
import { Request, Response } from 'express'

import { Router } from "express";
import { User } from '../entity/User'

const router = Router();

// ユーザー詳細
router.get("/:id", async (req: Request, res: Response) => {
  const results = await User.findOneOrFail(req.params.id);
  return res.json(results);
});
// 全ユーザーを表示
router.get("/", async (req: Request, res: Response) => {
  const results = await User.find();
  return res.json(results);
});
// ユーザーを登録
router.post("/", async (req: Request, res: Response) => {
  const user = await User.create(req.body);
  const results = await User.save(user);
  return res.json(results);
});
// ユーザー情報を更新
router.put("/:id", async (req: Request, res: Response) => {
  // User.findOne()を使用するとエラーになるのでFindOneOrFailを使用
  const user = await User.findOneOrFail(req.params.id);
  User.merge(user, req.body);
  const results = await User.save(user);
  return res.json(results);
});
// ユーザーを削除
router.delete("/:id", async (req: Request, res: Response) => {
  await User.delete(req.params.id);
  const message = "The user is deleted!!"
  return res.json(message);
});

export default router;
```

```tsx
//index.txを以下のように変更

import express, { Request, Response, NextFunction } from 'express'
import todoRoutes from './routes/todos'
import userRoutes from './routes/users'
import "reflect-metadata";
import { createConnection } from "typeorm";
// create typeorm connection
createConnection()
  .then(() => {

    // create and setup express app
    const app = express()
    app.use(express.json());

    // register routes
    app.use('/todos', todoRoutes)
    app.use('/users', userRoutes)

    app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
      res.status(500).json({ message: err.message })
    })

    // start express server
    app.listen(3000, () => console.log('Server up at http://localhost:3000'))
  }).catch(error => console.log(error));
```

### todoテーブルを作る

entityフォルダ内にTodo.tsをつくる

```tsx
// entity/Todo.ts
import { BaseEntity, Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm";

@Entity()
export class Todo extends BaseEntity {
  @PrimaryGeneratedColumn()
  readonly id?: number;

  @Column({ type: 'varchar' })
  title: string;

  @Column("text")
  description: string;

  @Column()
  isDone: boolean = false;

  @CreateDateColumn()
  readonly createdAt?: Date;

  @UpdateDateColumn()
  readonly updatedAt?: Date;

  // コンストラクタで初期化
  constructor(title: string, description: string) {
    super();
    this.title = title;
    this.description = description;
  }
}

```

次にUser.tsを共通するところがあるのでまとめていきます。

### entity内でModel.tsを作る

```tsx
// Model.ts
import { BaseEntity, Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm";

@Entity()
export default abstract class Model extends BaseEntity {
  @PrimaryGeneratedColumn()
  readonly id?: number;

  @CreateDateColumn()
  readonly createdAt?: Date;

  @UpdateDateColumn()
  readonly updatedAt?: Date;

  // コンストラクタで初期化
  constructor(model?: Partial<any>) {
    super();
    Object.assign(this, model)
  }
}
```

### User.tsとTodo.tsを変更する

```tsx
// User.ts
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
import Model from "./Model";

@Entity()
export class User extends Model {
  @PrimaryGeneratedColumn()
  readonly id?: number;

  @Column({ type: 'varchar' })
  name: string;

  @Column({ type: 'varchar' })
  email: string;

  // コンストラクタで初期化
  constructor(name: string, email: string) {
    super();
    this.name = name;
    this.email = email;
  }
}
```

```tsx
// Todo.ts
import { Entity, Column } from "typeorm";
import Model from "./Model"

@Entity()
export class Todo extends Model {
  @Column({ type: 'varchar' })
  title: string;

  @Column("text")
  description: string;

  @Column()
  isDone: boolean = false;

  // コンストラクタで初期化
  constructor(title: string, description: string) {
    super();
    this.title = title;
    this.description = description;
  }
}
```

###todos.tsの処理を整えてCRUDができるようにする

```tsx
// todos.tsを以下のように変更する
import { Request, Response } from 'express'

import { Router } from "express";
import { Todo } from '../entity/Todo'

const router = Router();

// ユーザー詳細
router.get("/:id", async (req: Request, res: Response) => {
  const results = await Todo.findOneOrFail(req.params.id);
  return res.json(results);
});
// 全ユーザーを表示
router.get("/", async (req: Request, res: Response) => {
  const results = await Todo.find();
  return res.json(results);
});
// ユーザーを登録
router.post("/", async (req: Request, res: Response) => {
  const todo = await Todo.create(req.body);
  const results = await Todo.save(todo);
  return res.json(results);
});
// ユーザー情報を更新
router.put("/:id", async (req: Request, res: Response) => {
  // Todo.findOne()を使用するとエラーになるのでFindOneOrFailを使用
  const todo = await Todo.findOneOrFail(req.params.id);
  Todo.merge(todo, req.body);
  const results = await Todo.save(todo);
  return res.json(results);
});
// ユーザーを削除
router.delete("/:id", async (req: Request, res: Response) => {
  await Todo.delete(req.params.id);
  const message = "The todo is deleted!!"
  return res.json(message);
});

export default router;
```

###リレーションを組む

userIdカラムをtodoテーブルの加えるためにTodo.tsに次のコードを加える。
Many to oneの考え方です。

```tsx
//Todo.ts
import { Entity, Column, ManyToOne, JoinColumn } from "typeorm";
import Model from "./Model"
import { User } from "./User" // 追加

@Entity()
export class Todo extends Model {
  @Column({ type: 'varchar' })
  title: string;

  @Column("text")
  description: string;

  @Column()
  isDone: boolean = false;
//----- 追加
  @Column()
  userId!: number;
  @ManyToOne(() => User, user => user.todos)
  user!: User;
//-----
  //　userIdの初期化を追加
  constructor(title: string, description: string, userId: number) {
    super();
    this.title = title;
    this.description = description;
    this.userId = userId;
  }
}
```

User.tsにもTodo.tsと関係があることをコードを追加して表す。
こちらはone to manyの関係

```tsx
import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from "typeorm";
import Model from "./Model";
import { Todo } from "./Todo";

@Entity()
export class User extends Model {
  @PrimaryGeneratedColumn()
  readonly id?: number;

  @Column({ type: 'varchar' })
  name: string;

  @Column({ type: 'varchar' })
  email: string;
//----- 追加
  @OneToMany(() => Todo, todo => todo.user)
  todos?: Todo[];
//----- 
  // コンストラクタで初期化
  constructor(name: string, email: string) {
    super();
    this.name = name;
    this.email = email;
  }
}
```

## 参考

[TypeORM - Amazing ORM for TypeScript and JavaScript (ES7, ES6, ES5). Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.](https://typeorm.io/#/many-to-one-one-to-many-relations)

[TypeORM Tutorial (Seamless Typescript ORM)](https://www.youtube.com/watch?v=Paz0gnODPE0&t=2509s)