Server Side TypeScriptでdecoratorを使い倒す

サーバーサイドを TypeScript で書くなら decorator を活用するとシンプルに書ける。 decorator はむやみに使うと可読性・保守性が下がるけど、汎用性の高いケースに活用すればビジネスロジックに集中した快適コーディングができる、と思う。

decorator ベースの便利なライブラリを3つ紹介する。

これら3つを組み合わせたサーバーアプリの例を GitHub に置いた。

サンプルコード

GitHub リポジトリを見ていただくのが早いけど、記事にもコピペしておく。

データベースのモデル定義はこう。

import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'

@Entity()
export default class Post {

  @PrimaryGeneratedColumn()
  id: number

  @Column()
  title: string

  @Column('text')
  text: string

}

モデルを使用するレポジトリ(モデルとコントローラーの中間層)はこう。

import { Service } from 'typedi'
import { InjectRepository } from 'typeorm-typedi-extensions'
import { Repository } from 'typeorm'
import Post from '../model/Post'

@Service()
export class PostRepository {

  @InjectRepository(Post)
  private repository: Repository<Post>

  findAll () {
    return this.repository.find()
  }

  findOne (id: number) {
    return this.repository.findOneById(id)
  }

  save (post: Post) {
    return this.repository.save(post)
  }

  remove (id: number) {
    return this.repository.removeById(id)
  }

}

コントローラーはこう。

import { JsonController, Get, Post as HttpPost, Param, Delete, Body, OnUndefined } from 'routing-controllers'
import { Service, Inject } from 'typedi'
import { PostRepository } from '../repository/PostRepository'
import Post from '../model/Post'

@Service()
@JsonController()
export class PostController {

  @Inject()
  private postRepository: PostRepository

  @Get('/posts')
  all () {
    return this.postRepository.findAll()
  }

  @Get('/posts/:id')
  @OnUndefined(404)
  one (@Param('id') id: number) {
    return this.postRepository.findOne(id)
  }

  @HttpPost('/posts')
  post (@Body() post: Post) {
    return this.postRepository.save(post)
  }

  @Delete('/posts/:id')
  @OnUndefined(200)
  delete (@Param('id') id: number) {
    return this.postRepository.remove(id)
  }

}

エントリーポイントはこう。

import 'reflect-metadata'
import { createKoaServer, useContainer as routingUseContainer } from 'routing-controllers'
import { createConnection, useContainer as dbUseContainer } from 'typeorm'
import { Container } from 'typedi'
import { PostController } from './controllers/PostController'
import Post from './model/Post'

routingUseContainer(Container)
dbUseContainer(Container)

main().catch(console.error)

async function main () {
  await createConnection({
    type: 'sqlite',
    database: 'test.database',
    entities: [
      Post,
    ],
    synchronize: true,
    logging: true,
  })
  const app = createKoaServer({
    controllers: [
      PostController,
    ],
  })

  app.listen(3000)
  console.log('Server is up and running at port 3000')
}