Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9164

TypeORM環境をCLIで構築

$
0
0

背景

前回、sequelizeを利用したコードのtypescript化を試みて、modelの呼び出し元はtypescriptっぽく書くようにできたけど、モデル自体はtypescript化できずに、中途半端な感じで終わってしまった。

https://qiita.com/yusuke-ka/items/244d3dfafb578fd84b1a

sequelizeは、元々typescriptをサポートしていなかったため、typescriptと少し相性が悪いとの記事もいくつか見つけた。

そこで今回は最近伸びてきているTypeORMを試してみようと思う。
こちらは元々typescriptが前提となっているORMなので、typescript化で苦労することはなさそう。

googleトレンドで見ると、sequelizeに追いつく勢いで伸びてきている。
(むしろtypescriptに限定すれば、既にTypeORMの方が人気がある気がする)

image.png

TypeORMのCLIを使った環境構築

DBは以前インストールしたpostgresql(windows)を利用する。
https://qiita.com/yusuke-ka/items/448843020c0406363ba5#%E6%BA%96%E5%82%99

pgadmin4でデータベースインスタンスだけ作っておく。

image.png

データベース名("typeorm"とした)を入力して作成。

ここからは、コード エディタ(VS Code)上での作業。
まずはベースとなるnode環境を作る。

> mkdir typeorm
> cd typeorm
> yarn init

検証なので、とりあえず全部デフォルト設定。

続いて、TypeORMのインストール。
また、DBはpostgresqlを使うのでpg(node-postgres)もインストール。

> yarn add typeorm
> yarn add pg

TypeORMのCLIがyarnで簡単に使えるようにpackage.jsonに以下を追加しておく。

package.json
{..."scripts":{"typeorm-cli":"typeorm"},...}

TypeORMのCLIを使って、express、postgresqlのTypeORM環境を一気に構築。

> yarn typeorm-cli init --express --database postgres

自動で以下のようなフォルダ/ファイルが生成される。

|- typeorm/
 |- src/
  |- controller/
    |- UserController.ts
  |- entry/
    |- User.ts
  |- migration/
  |- index.ts
  |- routes.ts 
 |- ormconfig.json
 |- tsconfig.json

設定ファイル(ormconfig.json)で接続先DBに合わせて設定を変更。

ormconfig.json
{"type":"postgres","host":"localhost","port":5432,"username":"postgres","password":"postgres","database":"typeorm","synchronize":true,"logging":false,"entities":["src/entity/**/*.ts"],"migrations":["src/migration/**/*.ts"],"subscribers":["src/subscriber/**/*.ts"],"cli":{"entitiesDir":"src/entity","migrationsDir":"src/migration","subscribersDir":"src/subscriber"}}

今回は以下だけ変更。

ormconfig.json
..."username":"postgres","password":"postgres","database":"typeorm",...

これでTypeORMの環境構築は完了。簡単ですね。

TypeORMのCLIで自動構築された環境の確認

CLIで作成すると、サンプルとして"User"というモデルを操作するコードが自動で入っている。

自動生成されたモデル(src/entry/User.ts)は以下のようになっている。

User.ts
import{Entity,PrimaryGeneratedColumn,Column}from"typeorm";@Entity()exportclassUser{@PrimaryGeneratedColumn()id:number;@Column()firstName:string;@Column()lastName:string;@Column()age:number;}

モデルを定義するときは、これを真似して<モデル名>.tsをentryフォルダ以下に配置すればよい模様。

続いて、コントローラー(src/controller/UserController.ts)は以下のようになっていた。

UserController.ts
import{getRepository}from"typeorm";import{NextFunction,Request,Response}from"express";import{User}from"../entity/User";exportclassUserController{privateuserRepository=getRepository(User);asyncall(request:Request,response:Response,next:NextFunction){returnthis.userRepository.find();}asyncone(request:Request,response:Response,next:NextFunction){returnthis.userRepository.findOne(request.params.id);}asyncsave(request:Request,response:Response,next:NextFunction){returnthis.userRepository.save(request.body);}asyncremove(request:Request,response:Response,next:NextFunction){letuserToRemove=awaitthis.userRepository.findOne(request.params.id);awaitthis.userRepository.remove(userToRemove);}}

ここではモデルに対する操作を書く感じですね。
全件取得、1件取得、保存(作成/更新)、削除の機能がサンプルとして実装されている。

getRepository(Hoge)で取得されるRepositoryをTypeORMが提供してくれているので、このRepositoryのメソッド(findやsaveなど)を呼び出して、DBにアクセスしているっぽい。

次は、index.ts。expressを使用する感じで自動生成されている。

index.ts
import"reflect-metadata";import{createConnection}from"typeorm";import*asexpressfrom"express";import*asbodyParserfrom"body-parser";import{Request,Response}from"express";import{Routes}from"./routes";import{User}from"./entity/User";createConnection().then(asyncconnection=>{// create express appconstapp=express();app.use(bodyParser.json());// register express routes from defined application routesRoutes.forEach(route=>{(appasany)[route.method](route.route,(req:Request,res:Response,next:Function)=>{constresult=(new(route.controllerasany))[route.action](req,res,next);if(resultinstanceofPromise){result.then(result=>result!==null&&result!==undefined?res.send(result):undefined);}elseif(result!==null&&result!==undefined){res.json(result);}});});// setup express app here// ...// start express serverapp.listen(3000);// insert new users for testawaitconnection.manager.save(connection.manager.create(User,{firstName:"Timber",lastName:"Saw",age:27}));awaitconnection.manager.save(connection.manager.create(User,{firstName:"Phantom",lastName:"Assassin",age:24}));console.log("Express server has started on port 3000. Open http://localhost:3000/users to see results");}).catch(error=>console.log(error));

routes.tsに定義されているRouteをforEachで回しているようなので、新しいAPIを追加するときには、このコード(index.ts)は変更せずに、routes.tsに定義を追加すれば良さそう。

最後のほうにある
// insert new users for test
以下はサーバー起動時にテストデータを入れているコードのようなので、実際に利用する際には消しておいた方がよさそう。

というか必要ないので、さっそく消しておく。

index.ts
import"reflect-metadata";import{createConnection}from"typeorm";import*asexpressfrom"express";import*asbodyParserfrom"body-parser";import{Request,Response}from"express";import{Routes}from"./routes";createConnection().then(asyncconnection=>{// create express appconstapp=express();app.use(bodyParser.json());// register express routes from defined application routesRoutes.forEach(route=>{(appasany)[route.method](route.route,(req:Request,res:Response,next:Function)=>{constresult=(new(route.controllerasany))[route.action](req,res,next);if(resultinstanceofPromise){result.then(result=>result!==null&&result!==undefined?res.send(result):undefined);}elseif(result!==null&&result!==undefined){res.json(result);}});});// setup express app here// ...// start express serverapp.listen(3000);console.log("Express server has started on port 3000. ");}).catch(error=>console.log(error));

routes.tsを見てみる。これも自動生成されている。

routes.ts
import{UserController}from"./controller/UserController";exportconstRoutes=[{method:"get",route:"/users",controller:UserController,action:"all"},{method:"get",route:"/users/:id",controller:UserController,action:"one"},{method:"post",route:"/users",controller:UserController,action:"save"},{method:"delete",route:"/users/:id",controller:UserController,action:"remove"}];

サンプルとして、全件取得、1件取得、追加(更新)、削除の4つのAPIが定義されている模様。

最後に設定系のファイルを見てみる。

typeORMの設定ファイル(ormconfig.json)は、先ほど見たので、package.jsonから。

package.json
{"name":"typeorm","version":"1.0.0","main":"index.js","license":"MIT","scripts":{"typeorm-cli":"typeorm","start":"ts-node src/index.ts"},"dependencies":{"typeorm":"0.2.25","reflect-metadata":"^0.1.10","pg":"^7.3.0","express":"^4.15.4","body-parser":"^1.18.1"},"devDependencies":{"ts-node":"3.3.0","@types/node":"^8.0.29","typescript":"3.3.3333"}}

scriptsstartが追加されている。これでサーバーを実行する模様。
あとは、必要な依存が自動的に追加されている。

tsconfig.jsonも確認。

tsconfig.json
{"compilerOptions":{"lib":["es5","es6"],"target":"es5","module":"commonjs","moduleResolution":"node","outDir":"./build","emitDecoratorMetadata":true,"experimentalDecorators":true,"sourceMap":true}}

typeORMのREADMEに書いてある設定が自動的に入っているようです。
https://github.com/typeorm/typeorm#installation

TypeORMのサンプルコードを実行してみる。

TypeORMのコードを実行してみようと思う。

まずは、モデルをDBに反映してみる。

migration:generateを実行すると、DBの内容とモデルを比較してマイグレーションファイルを作成してくれるようなので、これを実行してみる。

> yarn typeorm-cli migration:generate -n UserMigration

エラーが発生した。

yarn run v1.22.4
$ typeorm migration:generate -n UserMigration
Error during migration generation:
D:\study\orm\typeorm\src\entity\User.ts:1
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
^^^^^^

SyntaxError: Cannot use import statement outside a module
...

本家サイトのここを参考にしてやってみる。
https://github.com/typeorm/typeorm/blob/master/docs/using-cli.md#installing-cli

package.jsonのscriptsを以下のように変更(typeormのscriptを追加)。
(ts-nodeは自動でインストールされているはずだけど、もし入っていなければ、インストールする必要があるかも)

   ...
   "scripts": {
      "typeorm-cli": "typeorm",
      "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
      "start": "ts-node src/index.ts"
   },
   ...

再度実行してみる。
(今度は、yarn typeorm-cliではなく、yarn typeorm)

> yarn typeorm migration:generate -n UserMigration

今度は成功。

yarn run v1.22.4
$ node --require ts-node/register ./node_modules/typeorm/cli.js migration:generate -n UserMigration
Migration D:\study\orm\typeorm/src/migration/1593226939171-UserMigration.ts has been generated successfully.
Done in 2.03s.

こんな感じでマイグレーションファイルが生成された。

1593226939171-UserMigration.ts
import{MigrationInterface,QueryRunner}from"typeorm";exportclassUserMigration1593226939171implementsMigrationInterface{name='UserMigration1593226939171'publicasyncup(queryRunner:QueryRunner):Promise<void>{awaitqueryRunner.query(`CREATE TABLE "user" ("id" SERIAL NOT NULL, "firstName" character varying NOT NULL, "lastName" character varying NOT NULL, "age" integer NOT NULL, CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`);}publicasyncdown(queryRunner:QueryRunner):Promise<void>{awaitqueryRunner.query(`DROP TABLE "user"`);}}

DBに反映させてみる。

> yarn typeorm migration:run

userテーブルが作成された。

image.png

サーバーを起動。

> yarn start

今回もchromeの拡張ツール「Advanced REST client」で動作確認。
http://localhost:3000/usersに各種リクエストを送ってみる。

routes.tsに定義されているRouteを見てURLを指定。

まずは全件取得。

image.png

何も登録してないので、結果は空の配列。

つづいて、ユーザーを登録してみる。

image.png

application/jsonで指定したパラメータでユーザーが登録された。

DBにも登録されている。

image.png

同様にもう一人ユーザーを追加した後、再度全件取得してみる。

image.png

今度は配列が空じゃない状態で返ってきた。

パスにIDを指定して取得。

image.png

1件だけ返ってきた。

データ更新。
パラメータにID(integer)を指定してPOSTリクエストを送る。

image.png

データが更新された。

最後に、削除を試してみる。

image.png

DBからデータが消えているようだが、レスポンスが返ってこない。

index.tsでresultがundefinedの時にレスポンスを返していないのが原因かと思われる。

index.ts
...// register express routes from defined application routesRoutes.forEach(route=>{(appasany)[route.method](route.route,(req:Request,res:Response,next:Function)=>{constresult=(new(route.controllerasany))[route.action](req,res,next);if(resultinstanceofPromise){result.then(result=>result!==null&&result!==undefined?res.send(result):undefined);// ← ココ}elseif(result!==null&&result!==undefined){res.json(result);}});});...

試しに200を返すように書き換えてみる。

index.ts
...// register express routes from defined application routesRoutes.forEach(route=>{(appasany)[route.method](route.route,(req:Request,res:Response,next:Function)=>{constresult=(new(route.controllerasany))[route.action](req,res,next);if(resultinstanceofPromise){result.then(result=>result!==null&&result!==undefined?res.send(result):res.send(200));// ← ココ}elseif(result!==null&&result!==undefined){res.json(result);}});});...

他のIDを指定して削除を再実行。

image.png

今度はレスポンスが返ってきた。
修正が正しいかどうかは置いといて、原因は特定できた。

さいごに

今回はTypeORMの環境構築を試してみた。

いくつかハマりポイントはあったもののsequelizeをtypescript化するよりは遥かに楽だったし、モデルもコントローラーもすべてtypescriptで書けそうなので、sequelizeの時のように中途半端な感じにならないのも良さげ。
(もう少し時間をかけて調べれば、sequelizeもいい感じにtypescript化できる方法があるのかもしれないけど…)

sequelizeほど成熟していない感はあるものの、typescriptで書くことが前提ならTypeORMの方がやりやすい気がします。


Viewing all articles
Browse latest Browse all 9164

Trending Articles