2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElysiaJS でお手軽バックエンド実装のススメ

Last updated at Posted at 2024-11-27

概要

ElysiaJS を使って、3 秒で API サーバを実装する方法を紹介します。

以下は ElysiaJS 公式ドキュメントの Cheat Sheet の引用です。

import { Elysia } from 'elysia'

new Elysia()
    .get('/', () => 'Hello World')
    .listen(3000)

これだけで http://localhost:3000GET / のエンドポイントができました。
簡単ですね。

Cheat Sheet 笑えるくらいシンプルです。
この記事は読まなくても良いのでこれ読みましょう。

ElysiaJS とは

ElysiaJS は初心者でも簡単に使用できるよう設計された Bun フレームワークです。
TypeScript の型安全性と Bun の高速性を備えており、明瞭な実装を行うことが出来ます。

新進気鋭な Javascript Runtime である Bun のフレームワークということで、新しめなライブラリです。
まだ情報は少ないですが、その分モダンな開発体験を味わえるでしょう。

実際のユースケース

Discord の Webhook で Google カレンダーの予定を通知します。
FFXIV の新しい高難度コンテンツが実装されたからです。

  • bun@1.1.37
  • elysia@1.1.25
import { Elysia } from "elysia";

const app = new Elysia()
  .use(swagger({ path: "/docs" }))
  .get("/auth", getAuth)
  .get("/oauth2callback", (context) => getOauth2callback(context))
  .get("/today", getToday)
  .post("/today", postToday)
  .listen(process.env.API_PORT ?? 3000);

これでエンドポイントが 4 つできました。
ついでに Scalar UI による OpenAPI ドキュメントも生成されています。

.use(swagger({ path: "/docs" }))

ここです。

path を指定しなかった場合は /swagger に、
この例では /docs にアクセスすることで、ドキュメントを確認することが出来ます。

image.png


それぞれのエンドポイントで関数呼び出しを行っています。
(全部を中に書いたら見辛いので)

実際の関数はこのようになっています。

import { google } from "googleapis";

const oauth2Client = new google.auth.OAuth2({
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  redirectUri: process.env.GOOGLE_REDIRECT_URI,
});
const scopes = ["https://www.googleapis.com/auth/calendar.readonly"];

function getAuth() {
  const authUrl = oauth2Client.generateAuthUrl({
    access_type: "offline",
    scope: scopes,
  });

  if (oauth2Client.credentials.access_token) {
    const expired = oauth2Client.credentials.expiry_date
      ? new Date(oauth2Client.credentials.expiry_date)
      : null;
    return {
      message: "Already authenticated!",
      expired: expired?.toLocaleString(),
      refreshTokenExists: !!refreshToken,
      authUrl,
    };
  } else {
    return {
      message: "Authorize this app by visiting this URL",
      authUrl,
    };
  }
}

GET /auth で実行される getAuth() では、
Google API の認証状態の確認と認証 URL の発行を行っています。

認証 URL にアクセスすると、Google アカウントからアプリに対するアクセス権を付与する画面に遷移します。
サードパーティのアプリで何かとログインを求められるあの画面です。

注目してほしいのはこの return 部分。

return {
  message: "Already authenticated!",
  expired: expired?.toLocaleString(),
  refreshTokenExists: !!refreshToken,
  authUrl,
};

適当なオブジェクトを投げ返すだけで、Elysia がいい感じの JSON に変換して Response してくれます。ありがとう。

もうひとつ見てみましょう。

import { Elysia, type Context } from "elysia";

async function getOauth2callback(context: Context) {
  if (!context.query.code) {
    return context.error(400, "No code provided");
  }

  const { tokens } = await oauth2Client.getToken(context.query.code);

  if (tokens.refresh_token) {
    refreshToken = tokens.refresh_token;
    saveRefreshToken(tokens.refresh_token);
  }
  oauth2Client.setCredentials({ ...tokens, refresh_token: refreshToken });

  const expired = tokens.expiry_date ? new Date(tokens.expiry_date) : null;
  return {
    message: "Authenticated!",
    expired: expired?.toLocaleString(),
    refreshTokenExists: !!refreshToken,
  };
}

GET /oauth2callback で呼び出される getOauth2callback() では、
Google API の AccessToken の取得を行っています。

上述の GET /auth で取得した認証 URL で認証をすると、
上でちゃっかり設定していた redirectUricode のクエリパラメータ付きで飛ばされます。

その飛び先がこちらの GET /oauth2callback となっており、受け取った code から AccessToken の取得を行っているというわけですが、大変めんどくさかったので詳細は割愛します。

見てほしいのはここ。

async function getOauth2callback(context: Context) {
  if (!context.query.code) {
    return context.error(400, "No code provided");
  }

  const { tokens } = await oauth2Client.getToken(context.query.code);

code が渡されなかった場合はエラー、渡された場合はトークンの取得に進んでいます。

このように context からリクエストを取得したり、レスポンスをカスタマイズして投げ返すことが出来ます。
この辺は Hono なども同様かと思いますので、とっつきやすさもあると思います。


GET /authGET /oauth2callback で認証したところで準備は完了。
あとは GET /today で Google カレンダーから今日の予定を取得、
POST /today で Discord の Webhook にメッセージを送りつけるだけです。

ところで、予定のリマインドをするのに、まさか手動でなんてやりませんよね。
パーティーメンバーへのリマインドのし忘れが多々あったため、なんとかして自動化したいと考えたのが今回のアプリです。


今回は毎日 18 時にリマインドを行います。
このような定時実行には cron がよく利用されると思いますが、なんとこれ、一緒に出来ちゃいます。しかも型安全で。
そう、Elysia ならね。

import { cron } from "@elysiajs/cron";
import { treaty } from "@elysiajs/eden";

const api = treaty<typeof app>(process.env.API_URL ?? "");

new Elysia().use(
  cron({
    name: "reminder-event",
    pattern: "0 18 * * *",
    timezone: "Asia/Tokyo",
    async run() {
      console.log(new Date(), "Cron job started");
      await api.today.post();
      console.log(new Date(), "Cron job finished");
    },
  })
);

Eden は Elysia インスタンスへ簡単に接続できるクライアントです。
TypeScript の型推論を利用しており、型安全に Request/Response を利用できます。

以下のように、簡単に POST /today を呼び出す事ができます。

const api = treaty<typeof app>(process.env.API_URL ?? "");
await api.today.post();

Cron Plugin については言わずもがな、
指定した pattern のタイミングで、run() の処理を実行してくれます。

所感

行き当たりばったりで上述の機能を急造したため、まだまだ理解しきれていない部分も多いですが、
とにかくわかりやすく使いやすい、学習コストの低いフレームワークだなと感じました。

WebSocket や UnitTest も作りやすいよう用意されているようなので、機会があればまた触ってみたいと思います。

ということで、最近気になっている ElysiaJS のオススメでした。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?