- 新卒ブログとは?
- 2024年4月に新卒入社したLIGメンバーが、日々の学びや気づきを綴るブログです。彼らがふだんどんなことを学んでいるのか、気軽にのぞいてみてください。
テクニカルディレクターの山岡です。今回はHonoのフィジビリティチェックも兼ねて、APIの構築に挑戦したいと思います。久々に新しいフレームワークを触るので、少しワクワクしています。
Honoとは
HonoとはAPI構築用のWebフレームワークで、Node.jsやDeno、Cloudflare WorkersなどのさまざまなJavaScript実行環境で動作します。
軽量かつ高速なAPIを構築できることが魅力の一つで、シンプルな記述で高性能なAPIを構築することが可能です。
TypeScriptに対応していること、複数のバックエンド環境で動作可能なこと、軽量なミドルウェアが充実していることなど、次世代のバックエンド構築を担うのではないかと(新卒ながら)思わせるような、魅力的なオプションが多く含まれています。
使ってみた
今回は家計簿アプリの開発を想定して、Honoのフィジビリティチェックをおこないました。観点はズバリ、「どれだけ簡単にAPIの実装ができるかどうか」です。
利用したライブラリ及び開発ツール
- Hono
- PostgreSQL
- Postman
今回の主役。APIの主軸を担ってもらいます。
DBの構築と操作のために利用。Honoとの繋ぎ込みはdotenv、pgを利用しました。
APIテスト用ツール。簡単な設定でAPIの検証ができるため重宝しています。
環境構築
Honoの導入
Honoの実行環境はnpmで構築しました。TypeScriptで開発を進めたため、各ライブラリの型定義などは別途インストールしています。
npm init -y npm install hono pg dotenv npm install -D typescript @types/pg @types/node npx tsc --init
ファイル構成
簡単なCRUD処理であれば app.ts内に記述が可能ですが、今後の拡張性も考えて、エンドポイント、型定義、処理本体をsrcフォルダ内で管理しました。
DBへのアクセスはapp.ts -> src/routes/~.ts -> src.controllers/~.ts -> database の順に振り分けられます。
/backend/ │── /dist/ # コンパイル後の出力先 │── /node_modules/ # 依存パッケージ │── /src/ # ソースコード │ ├── /controllers/ # 処理本体 │ ├── /model/ # データベースのスキーマ定義 │ ├── /routes/ # エンドポイントの定義 ├── app.ts # メインのエントリーポイント ├── db.ts # データベースの接続設定 │── package.json # プロジェクトの依存関係やスクリプトを定義 │── tsconfig.json # TypeScript のコンパイル設定 │── .env # 環境変数ファイル
実装
app.ts
app.tsには、エンドポイントの管理やheader情報の管理など、共通ロジックを記述します。app (const app = new Hono())はAPI構築に便利なメソッドを複数持っていますので、主要なものを一覧化しました。
メソッド | 説明 | 例 |
---|---|---|
app.use() | ミドルウェアの登録 | app.use(‘*’, logger()) |
app.get() 他 | HTTPメソッドの処理 | app.get(‘/hello’, (c) => c.text(‘Hello!’)) |
app.route() | ルーターを登録 | app.route(‘/hello’, apiRouter) |
app.notFound() | 404ハンドラを設定 | app.notFound((c) => c.text(‘Not Found’, 404)) |
app.fetch() | Honoアプリケーションのfetch化 | serve({ fetch: app.fetch, port:4000 }) |
実際に上記のメソッドを利用して作成した app.ts が以下となります。
import { Hono } from 'hono'; import type { Env } from 'hono'; import { serve } from '@hono/node-server'; //ミドルウェアのインポート import { logger } from 'hono/logger'; // routesディレクトリから関数をインポート import amountCategoryRouter from './routes/amountCategory'; type AppEnv = Env; const app = new Hono(); // ミドルウェアの適用 app.use('*', logger()); app.use('*', async (c, next) => { await next(); c.header('Cache-Control', 'no-store'); c.header('Content-Security-Policy', "default-src 'self'"); }); // ヘルスチェック app.get('/', (c) => c.text('Hello, Hono with TypeScript!だよ')); // ルータ app.route('/category', amountCategoryRouter); // 指定されたエンドポイントがなかった場合 app.notFound((c) => c.text('お探しのページは見つかりませんでした', 404)); // サーバー起動 serve({ fetch: app.fetch, // Honoアプリケーションを設定 port: 4000, // ポート番号を指定 });
ミドルウェアを利用するだけで、一通りの共通ロジックを組めるので楽ちんですね。
routes
次にエンドポイントの定義をroutesディレクトリ内に記述します。
routes/amountCategory.ts
import { Hono } from 'hono'; // controllerから処理関数を呼び出す import * as amountCategoryControllers from '../controllers/amountCategory'; const amountCategoryRouter = new Hono(); //メソッドおよびPassに応じてエンドポイントを管理 amountCategoryRouter.get('/list', amountCategoryControllers.getAmountCategory); amountCategoryRouter.post('/create', amountCategoryControllers.postAmountCategory) export default amountCategoryRouter;
models
次にmodelsでテーブルスキーマを記述します。controllersの型整合性を保証する上で重要な部分となるので、テーブル構造を確認しつつ記載しましょう。
sequelize等のライブラリを導入している場合には、ORMモデルの定義もここでおこないます。
models/amount_category.d.ts
export interface AmountCategory { id: number; // ID: 自動インクリメント category_name: string; // 支払いカテゴリ delete_flag: boolean; // 論理削除フラグ created_at: Date; // 作成日 updated_at: Date; // 更新日 }
controllers
最後に、処理本体をcontrollersに記述します。
controllers内の処理では、リクエスト情報をContextで受け取って処理をしています。Contextはリクエストやレスポンスを扱う重要なオブジェクトです。
一部、レスポンス情報(ヘッダーなど)ですでに登場しましたが、こちらも主要なメソッドを一覧化しました。
メソッド | 説明 | 例 |
---|---|---|
c.req() | リクエスト情報を取得 | c.req.method, c.req.url |
c.req.header() | リクエストヘッダーを取得 | c.req.header(‘User-Agent’) |
c.req.query() | クエリパラメータを取得 | c.req.query(‘id’) |
c.req.param() | パスパラメータを取得 | c.req.param(‘userId’) |
c.req.json() | リクエストボディをJsonで取得 | await c.req.json() |
c.header | レスポンスヘッダーを設定 | c.header(‘X-Custom’, ‘Hello’) |
c.status | HTTPステータスを設定 | c.status(200) |
c.json() | Jsonレスポンスを送信 | return c.json({message : ‘OK’}) |
上記の記述を使って一覧の取得、登録処理を記述したものがこちらとなります。
contorollers/amountCategory.ts
import { Context } from 'hono'; //DBへの接続 import pool from '../db'; import { AmountCategory } from '../model/amount_category'; //一覧の取得処理 export const getAmountCategory = async(c:Context) => { try { //SELECT文で一括取得 const result = await pool.query( 'SELECT * FROM amount_categories WHERE amount_categories.delete_flag = FALSE' ); // return c.json({ success : true, data: result.rows, // 実際のデータ }, 200); }catch(err){ console.error(err); return c.json({ success : false, error: 'データ取得に失敗しました。ダメですよ' }, 500); } } //新規登録処理 export const postAmountCategory = async(c:Context) => { //リクエストボディをJson形式で取得 const body = await c.req.json(); const { category_name } = body; try { //Insert const result = await pool.query ( `INSERT INTO amount_categories (category_name) VALUES ($1) RETURNING *`, [code] ); //処理結果を返す return c.json({ success : true, data: result.rows, // 実際のデータ }, 200); }catch(err){ console.error(err); return c.json({ success : false, error: 'データ取得に失敗しました。ダメですよ' }, 500); } }
補足:db.tsにはDBへの接続処理を記載しています
import { Pool } from 'pg'; import dotenv from 'dotenv'; dotenv.config(); const pool = new Pool({ host: process.env.DB_HOST, port: Number(process.env.DB_PORT), user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, }); export default pool;
テストしてみた
ここからは作成したAPIをPostmanでテストをしてみます。バックエンド環境はNode.jsを利用しました。
Postmanの設定
Postmanではエンドポイントとメソッド、パラメータを自由に設定してAPIのテストが可能です。試しに一覧を取得するAPIを実行してみます。
サーバ port は4000で起動したので、エンドポイントはhttp://localhost:4000/category/listとなります。結果は……
実際にAPIの実行を確かめることができました。
次に、一覧にデータを新規登録する処理をテストしてみます。結果は……
登録処理も簡単に実装をすることができました。
最後に
今回はHonoのフィジビリティチェックをかねてAPI実装をしました。
TypeScriptで実装ができて、APIを構築する上でのミドルウェアも一通り揃っていて、レスポンスも早くて……。コンテナ化をすることで、デプロイも比較的簡単におこなうことができますし、API方式のバックエンドを構築する上で十分に選択肢になりえる性能だと思います。
基本的なセキュリティもミドルウェアのみで対応が可能なため、本番構築も容易かと。使ってみたい……。
いつか複雑な処理も実装できるように、もっともっと技術をつけないとですね。それではご拝読いただき、ありがとうございました。
- 日本・フィリピン・ベトナムでの活躍チャンス
- 最先端技術と多言語環境での成長
- 有識者による月1回の勉強会
現在、海外拠点(フィリピン、ベトナム)に関われるエンジニアを募集しています。日本だけじゃなく世界で活躍することに興味のある方は、以下よりぜひご応募ください!