1on1システム「takibi」の技術について語ってみた!

こんにちは、新卒3年目の石川です。 この記事は、1on1システム「takibi」シリーズ記事の第3回です。
前回の記事「1on1システム 「takibi」を作りました!」では、takibiを開発するにあたっての工夫した点と苦労した点を紹介しました。 今回は、takibiで使用した技術について色々語りたいと思います!

使用した技術

メンバーにとって未経験のライブラリやフレームワークを採用しました。
実務で使用している技術で社内システムを開発するより、未経験の技術を使用することで楽しさと学びがある開発にしたかったからです!
使用した技術は主に下記の通りです。

  • フロントエンド
    • React
    • TailwindCSS
  • バックエンド
    • Express
    • Prisma(ORM)

フロントエンドにReactを採用した理由は、Vue.jsを実務で使っているためよく比較されるReactに触れてみたかったのが大きな理由です。 バックエンドにExpressを採用した理由は、バックエンドもフロントエンドと同じ言語=TypeScriptで構築できることが学習コストの削減という点で魅力的だったので採用しました。
Golangなども候補としてあがりましたが、全体的に学習コストが上がるため今回は断念しました。
いつかGolangで開発したいな〜。

所感

紹介した技術を使ってみての所感を共有したいと思います。

フロントエンド

React

Reactを使ってみて感じたことの中で、印象的だったVue.jsとの違い

  • 文法
  • データフロー

の2つを簡単に紹介したいと思います。
まず、ReactはJavaScriptに近い文法で記述できるため、JavaScriptに慣れている開発者にとっては親しみやすいと思います。逆を言えば、ReactはJavaScriptの知識が浅いと難しく、「Reactは難しい」と言われがちな理由はここにあるのかな〜と思いました。また、Vue.jsと異なり、Reactはコンポーネントの記述に気をつけないと、見通しの悪いコードになりやすいです。Vue.jsはdata、method、mountedなどコードを記述する場所が決まっていて、あまり意識しなくてもコードが整理されやすいです。
一方で、Reactは好きな場所に好きな処理を記述できるため、コードがごちゃごちゃしてしまう可能性があります。

次に、データフローという視点で見ると、Reactの方が整理しやすいと思います。 Reactではデータは基本的に親コンポーネントから子コンポーネントへと伝わっていくだけですが、Vueでは親から子だけでなく子から親へもデータを受け渡すことができます。便利な反面、データの流れを意識して開発することを求められるので、複雑になりがちです。そのためReactの単方向のデータフローは、大きなメリットの一つであると言えます。

TailwindCSS

バックエンドエンジニアなのでCSSが苦手(?)なのですが(言い訳)、そんな自分が使ってみた所感としては、

  • クラス名を見ただけで、どんなスタイルが当てられているかを理解しやすい
  • BootStrapと違ってデザインは予め用意されていないものの、柔軟性がある

の2点がTailwindCSSの良い点だと思いました。
クラスを組み合わせてスタイリングするので、クラス名が冗長になりがちなところに最初は抵抗がありましたが、慣れてくるとあまり気にならなくなります。
簡単にスタイリングできるので、TailwindCSSからCSSを学ぶという順序も個人的にはアリかなと思います!

バックエンド

Express

Expressを採用した理由として、「バックエンドもフロントエンドと同じ言語=TypeScriptで構築できる」点を挙げました。
では、実際「同じ言語で構築してみてどうだったか」ですが、個人的に感じた点は

  • awaitを書くことが手間である
  • バックエンドにTypeScriptを用いても、開発体験が必ず向上するわけではない

の2つです。
まず、「awaitを書くことが手間である」についてです。これはExpressというより、バックエンドをNode.jsで開発する場合に共通して言えます。DBの操作を思い浮かべてみてください。 よくある例として、「ユーザーを作成して、そのユーザーIDを外部キーとして別のテーブルに保存する」というのがあると思います。この場合、ユーザーを作成する時は後続の処理を考えるとawaitせざるを得ません。 このように基本的にはawaitを書くので、書き忘れるとバグになる可能性もあります。全く大きな欠点ではないのですが、面倒だなと感じる部分です。

次に「バックエンドにTypeScriptを用いても、開発体験が必ず向上するわけではない」についてです。大きな理由としては、TypeScriptにデメリットがあるというわけではなく、「フロントエンドと同じ言語だからといって、バックエンドの開発が簡単になるわけではない」からです。そのため、フロントエンドとバックエンドが同じ言語であることの恩恵をあまり感じませんでした。また、自分がバックエンドの言語(PHPなど)を扱うのに慣れているので、「TypeScriptじゃなくてもいいや」と正直思いました(もちろん、要件によります)。 ただ、さまざまな事情により「バックエンドのリソースが足りない!」みたいな状況を考えると、フロントエンドをTypeScriptでバリバリ書いている人がバックエンドでも開発できることはすごくいいなと思います。

Prisma(ORM)

ORMであるPrismaは直感的で、とても操作がしやすい印象です。
例えば

import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function main() {
  const user = await prisma.user.create({
    data: {
      name: 'Bob',
      email: 'bob@prisma.io',
      posts: {
        create: {
          title: 'Hello World',
        },
      },
    },
  })
  console.log(user)
}

というコードですが、「userをcreateして、posts?あ、postsもcreateしているのか!」とならないでしょうか。
何かしらのORMを触ったことがある人なら、すぐに理解できると思います。
一方で、「どんなクエリが発行されているのか」イメージしづらいという事実もあります。

async function fetchUsersWithComments() {
  const usersWithPosts = await prisma.user.findMany({
    include: {
      posts: true 
   }
  })
}

このコードはどんなクエリを発行するでしょうか。 発行されるクエリは

SELECT 
  `testdb`.`users`.`user_id`, 
  `testdb`.`users`.`email`, 
  `testdb`.`users`.`name`, 
  `testdb`.`users`.`created_at` 
FROM 
  `testdb`.`users` 
WHERE 
  1 = 1 

SELECT 
  `testdb`.`posts`.`post_id`, 
  `testdb`.`posts`.`title`, 
  `testdb`.`posts`.`content`, 
  `testdb`.`posts`.`published`, 
  `testdb`.`posts`.`author_id` 
FROM 
  `testdb`.`posts` 
WHERE 
  `testdb`.`posts`.`user_id` IN (?)

となります。where inで持ってきてます。

JOINはどうやるのか

INNER JOINを用いて1クエリで取得したい場合は、どうすればいいでしょうか。
INNER JOINできるメソッドがある(queryRaw以外で)と考えてしまったのですが、Prismaにはありません。「嘘でしょ?」とツッコみたくなりますよね。
そのためINNER JOINと同様の結果を得るには、先ほどと同様に

async function fetchUsersWithComments() {
  const usersWithPosts = await prisma.user.findMany({
    include: {
      posts: true 
   }
  })
}

とするしかないそうです。モヤモヤしますね...
LEFT JOINの場合も見てみます。

usersテーブル

フィールド名 概要 Not Null
user_id ユーザID
name 名前

commentsテーブル

フィールド名 概要 Not Null
comment_id コメントID
content コメント内容
user_id ユーザID

上記のテーブル設計の時、「commentsテーブルにusersテーブルをLEFT JOINしたい」とします。
この時、INNER JOINと同様にLEFT JOINが可能なメソッドはPrismaにはないので、同じ結果を得ようとすると

async function fetchUsersWithComments() {
  const usersWithPosts = await prisma.comment.findMany({
    include: {
      users: true 
   }
  })
}

とするしかありません。発行されるクエリは

SELECT 
  `testdb`.`comments`.`comment_id`, 
  `testdb`.`comments`.`content`, 
  `testdb`.`comments`.`user_id` 
FROM 
  `testdb`.`comments` 
WHERE 
  1 = 1 

SELECT 
  `testdb`.`users`.`user_id`, 
  `testdb`.`users`.`email`, 
  `testdb`.`users`.`name`, 
  `testdb`.`users`.`created_at` 
FROM 
  `testdb`.`users` 
WHERE 
  `testdb`.`users`.`user_id` IN (ユーザID)

です。
このようにPrismaには直感的に操作が扱いやすい一方で、JOINに関する操作が得意ではありません。
JOINを用いた複雑なクエリを発行できるように、これからの発展に期待しています!

おわりに

いかがだったでしょうか。
技術について深掘りすることなく、ふんわりと語ってみました。
さまざまな技術に触れてみることで、いろんな発見ができ、開発がとても楽しかったです!
新しいことを学ぶのはやっぱり楽しいですね!

Webサイト・システムの
お悩みがある方は
お気軽にご相談ください

お問い合わせ 03-6380-6022(平日09:30~18:30)

出張またはWeb会議にて、貴社Webサイトの改善すべき点や
ご相談事項に無料で回答いたします。

無料相談・サイト診断 を詳しく見る

多くのお客様が気になる情報をまとめました、
こちらもご覧ください。