目標
- userテーブルとtodoテーブルをつなげる
- どのユーザーがどんなタスクを持っているかを表示させる
- ユーザーにつき複数のタスクがつくようにする
目的
- typeormのリレーションの組み方を学ぶ
- 1対多の紐付け方を学ぶ
手順
-
準備物・環境設定はこちら
Typeormの設定のメモ
typescriptでdoenvを使うときのメモ
Typescriptとexpress.jsの環境設定 -
ルートを整える
/users
に対応するルートを別ファイルにする
routesフォルダーを作りそこにusers.tsを作る
// 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;
//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をつくる
// 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を作る
// 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を変更する
// 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;
}
}
// 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ができるようにする
// 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の考え方です。
//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の関係
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;
}
}