PHPer+Nestjs+TypeScriptでサーバーサイド

PHPとTypeScript+Nest.jsと言う新しい組み合わせにチャレンジshてみます。

NestJS入門にあたって探したもの

MVCで書けることは前述の記事を読んでわかったのですが、長年Symfonyに甘やかされているので、単にMVCなだけだと既につらいです。
最低限で

  • DI
  • ORM
  • テンプレートエンジン…できればTwigと同じ文法で書きたい

がほしいなと思いました。

DI

NestJSのドキュメントで一発解決しました。

https://embed.zenn.studio/card#zenn-embedded__657311005614f

普段、「フレームワークに依存しないで生のクラスでロジックを書きたい」流派に属しているので( https://speakerdeck.com/77web/sofalsekodo-huremuwakufalsewai-demodong-kimasuka )ドメインロジックのクラスに @Injectable を書かないといけないのはぐぬぬ…という感じですが、まぁどうしても許せなければFactoryパターンを多用すればいいかと思い、これで解決としました。
@Injectable はAngularと同じような使い方でAngularに慣れていると使いやすいですね。

ORM

ActiveRecordでもギリギリ許容範囲ではあるものの、できればDoctrineと同じDataMapperがよかったので TypeScript ORM DataMapper TypeScript Hibernate 等でググりました。
比較的すぐ見つかりました。
MikroORM https://mikro-orm.io/

もう公式サイトのトップに載ってるコード例だけで、なんとなく親近感が湧いてしまいました。(Doctrineユーザーにはわかると思う、この気持ちw)

const user = em.find(User, 1);
user.name = 'update!';
await em.flush();

テンプレートエンジン

こちらも TypeScript twig TypeScript jinja 等とググって見つけました。
nunjucks https://mozilla.github.io/nunjucks/

サイトトップのテンプレート例が完全に違和感なかったのと、extendsとblockが使えるというので、これに決めました。

コード

まだ何もまともなロジックがない段階ですが↓に置きました。

https://embed.zenn.studio/card#zenn-embedded__c169a051edbd2

NestJSのデフォルトのテンプレートエンジンがnunjucksじゃなくてHandlebarsなので、main.tsで設定をごにょごにょ頑張ったりとか。

https://embed.zenn.studio/card#zenn-embedded__f13e58aa49778

MikroORMを使えるようにググって見つけたモジュールを設置してみたりとか。

import { Module } from '@nestjs/common';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { User } from '../../entity/user';
import * as ormConfig from '../../../config/mikro-orm.config';

@Module({
  imports: [
    MikroOrmModule.forRoot(ormConfig.default),
    MikroOrmModule.forFeature({
      entities: [User],
    }),
  ],
  exports: [MikroOrmModule],
})
export class OrmModule {}

Symfonyでやるのと同じようにDB接続情報を.envで指定できるように試行錯誤とか。

import { Logger } from '@nestjs/common';
import { Options } from '@mikro-orm/core';
import { User } from '../src/entity/user';
import * as dotenv from 'dotenv';

dotenv.config();

const logger = new Logger('MikroORM');
const config = {
  entities: [User],
  dbName: process.env.DATABASE_NAME,
  user: process.env.DATABASE_USER,
  password: process.env.DATABASE_PASSWORD,
  host: process.env.DATABASE_HOST,
  type: 'mysql',
  port: 3306,
  debug: true,
  logger: logger.log.bind(logger),
} as Options;

export default config;

DBを使った機能テスト書いてみたりとか。

import { Test, TestingModule } from '@nestjs/testing';
import { HomeController } from './home.controller';
import { OrmModule } from '../module/orm/orm.module';

describe('HomeController', () => {
  let homeController: HomeController;

  beforeEach(async () => {
    const app: TestingModule = await Test.createTestingModule({
      imports: [OrmModule],
      controllers: [HomeController],
      providers: [],
    }).compile();

    homeController = app.get<HomeController>(HomeController);
  });

  describe('root', () => {
    it('should return "Hello World!"', async () => {
      expect(await homeController.index()).toEqual({ hello: 'Hello Hiromi' });
    });
  });
});