ジョブカン事業部 アドベントカレンダーの8日目です!
札幌オフィスでジョブカンの開発をしている@knotです![]()
私は普段Ruby on RailsやReactなどを触る機会が多いです。
Ruby on RailsとReact(TypeScript)の境界面の取り扱いについて、難しさを感じています。
最近ではフロントエンドとバックエンドを両方TypeScriptで書くことが流行ってきているような印象を持っています。そんな中、Elysiaというフレームワークを見つけて面白そうと思ったため、本記事ではRuby on Rails + React(TypeScript)開発の視点も少し混ぜながらElysiaのフルスタック TypeScript開発(狭義)の世界を覗いてみたいと思います。
Elysiaとは
ElysiaとはBun向けに最適化されたバックエンドのTypeScriptフレームワークです。主に下記のような特徴があります。
- Bunに最適化されているため高速
- BunネイティブだがNode.jsやDenoでも動く
- End-to-Endの型安全
- シンプルで記述量が少ない
- OpenAPIをはじめとしたプラグインが簡単に使える
Hello World
公式のサンプルコードそのままですが、このような雰囲気で書くことができます。
import { Elysia } from 'elysia'
new Elysia()
.get('/', 'Hello Elysia')
.get('/user/:id', ({ params: { id }}) => id)
.post('/form', ({ body }) => body)
.listen(3000)
近いものにHonoやExpressがありますが、これらに比べてElysiaの書き方で特徴的なのはすべてをメソッドチェーン1本でつなげる書き方をするという点だと思います。これは型安全性のために重要なことであるようです。(メソッドチェーンにしなくても動くようですが、型推論は行われません)1
Ruby on Railsのclassベースとは大きく書き方が違いますが、Elysiaのライフサイクルの流れなどが意識しやすくなって良さそうと感じました。

Elysiaの良いなと感じたところ
Elysiaの良いなと思ったところをかい摘んで紹介します。
チュートリアル
Elysiaは公式でチュートリアルが用意されており、実際にコードを書きながら基本を学ぶことができます。
- ブラウザ上のエディタで進めることができるため、環境構築が不要
- 各セクションには練習問題が用意されており、答え合わせをしてくれる
- 困ったらAIに質問できる
チュートリアルを進めるためのハードルがかなり低くなっていると感じます。挫折なく最後まで進めることができました。

バリデーション/型定義
Elysiaのバリデーションはt(TypeBox)を使うことで実装できます。
import { Elysia, t } from 'elysia'
new Elysia()
.post('/', ({ body: { name, id } }) => `Hello ${name}(id: ${id})`, {
body: t.Object({
name: t.String(),
id: t.Number()
})
})
.listen(3000)
バリデーションスキーマの定義がそのまま型の定義ということになります。よって({ body: { name, id } }) => Hello ${name}(id: ${id})のnameやidは、しっかりそれぞれstring型とnumber型として扱われます。
また、バリデーションに引っかかるパラメータが送られてきた場合は、422 Unprocessable Contentをデフォルトで返してくれます。
ここ1箇所にバリデーションを書いておくだけで入力チェックや型定義などの諸々が完了するため、Ruby on Rails + Reactでやってきていたstrong parameters、Modelのバリデーション、フロントの型定義などがひとまとまりにできます。
パラメータを変えたいときは、最初にバリデーションを修正すれば楽に作業が終了するためかなり嬉しいです。
ZodやValibotも使える
Elysiaが標準で提供しているのはTypeBoxですが、ZodやValibot・ArkTypeなどの他のバリデーションライブラリを使用することも可能です。好きなライブラリが使えます。
import { Elysia } from 'elysia'
import { z } from 'zod'
const app = new Elysia()
.patch('/user/:id', ({ params, body }) => ({
params,
body
}),
{
params: z.object({
id: z.number()
}),
body: z.object({
name: z.string()
})
})
paramsが分離されている
Ruby on Railsではクエリパラメータとパスパラメータとリクエストボディが全てparamsに入ってきます。どのパラメータがどこから入ってきているのか、わからなくなることがあります...。
Elysiaでは
- クエリパラメータ:
query - パスパラメータ:
params - リクエストボディ:
body
として扱われるため、明確に区別されます。
もちろんそれぞれバリデーションもかかります。
import { Elysia, t } from "elysia"
const app = new Elysia()
.post("/user/:id", ({ body: { name }, query: { lang }, params: { id } }) => `Hello ${name}(id: ${id}, lang: ${lang})`, {
query: t.Object({
lang: t.Number(),
}),
params: t.Object({
id: t.Number(),
}),
body: t.Object({
name: t.String(),
}),
})
.listen(3000)
OpenAPIドキュメントが簡単に生成できる
プラグインを入れるだけでOpenAPIドキュメントが自動的に生成されます。(これを見るとFastAPIの機能を思い出します)
import { Elysia } from 'elysia'
+ import { openapi } from '@elysiajs/openapi'
new Elysia()
+ .use(openapi())
デフォルトでは実行時のスキーマを使ってOpenAPIを生成しますが、型からも生成することが可能です。(実行時のスキーマと共存し、実行時のスキーマが優先されるようです)
.use(
openapi({
+ references: fromTypes()
})
)
自動生成であるため情報が古くなることも無ければ、いざ別の言語やフレームワークやに乗り換える事になったときにもAPIドキュメントはそのまま使えるため、良い機能だと思います。また、OpenAPIの他にもOpenTelemetryやJWTなどの公式プラグインが他にも複数用意されています。
End-to-Endの型安全(Eden treaty)
個人的にはフルスタックTypeScript開発をするにあたっての目玉機能だと思っています
Edenを使うとtRPCっぽいことができます。
サーバの型をexportして...
import { Elysia, t } from "elysia"
const app = new Elysia()
.get("/", () => "Hello!")
.post("/ping", ({ body: { msg }}) => msg, {
body: t.Object({
msg: t.String()
})
})
export type App = typeof app
クライアントの方で型をimportしてtreatyのジェネリクスにわたすと、APIクライアントとして使えるようになります。
import { treaty } from "@elysiajs/eden"
import type { App } from "./server"
// APIクライアントになる
const client = treaty<App>("localhost:3000")
const { data, error } = await client.get()
await client.ping.post({
msg: "pong"
})
Ruby on Rails + ReactのときにやっていたAPIリクエスト・レスポンスの定義やAPIを何で叩くかなどの考慮が必要なくなります。嬉しい。
honoのRPCでもほとんど同じことができそうです。リクエストをするまでの書き味はほとんど一緒のようですが、honoのRPCはResponse Objectが返ってきます。
対して、Edenは戻り値が自動でパースされてdataとerrorが返されます2。ここで得られるdataにはちゃんと型がつきます。ただし、null許容値となっているため、null許容をしたくない場合はerrorの存在を検証する必要があります。
他のフレームワーク等との比較
公式で以下の4つとの比較が用意されています。
パフォーマンスや提供される機能、構文などが比較されているため、何を採用するか悩む場合にひとつの指標として活用できそうです。
まとめ
Elysiaを軽く触ってみて、良かったなと思ったところを紹介しました。紹介したもの以外にもElysiaには色々な機能が用意されています。
今までバックエンドはRuby on RailsやPython(FastAPI)しか触ったことがなく、バックエンドとフロントエンドをまとめてTypeScriptで書いたことがなかったのですが、結構体験が良さそうだと思いました。
難しいと感じていたバックエンドとフロントエンドの境界面はほとんど気にする必要がなくなり、型定義で色々なところと繋がっていくのはTypeScript/Elysiaならではの強みなのだと思います。
Elysiaは、特に中・小規模で開発スピードを気にする場合の個人開発での選択肢として不足の無いものと思ったので、Elysiaでちゃんとした個人開発をやってみたいです。
気になった方はぜひチュートリアルから始めてみてください!
さいごに
DONUTSでは新卒中途問わず積極的に採用活動を行っています。
新卒やインターンのエンジニアも募集しています!
-
Response Objectも返されるため、こちらを使用することも可能です ↩
