Honoで手軽にAPIを構築してみた

Honoで手軽にAPIを構築してみた

Masaki Yamaoka

Masaki Yamaoka

新卒ブログとは?
2024年4月に新卒入社したLIGメンバーが、日々の学びや気づきを綴るブログです。彼らがふだんどんなことを学んでいるのか、気軽にのぞいてみてください。

テクニカルディレクターの山岡です。今回はHonoのフィジビリティチェックも兼ねて、APIの構築に挑戦したいと思います。久々に新しいフレームワークを触るので、少しワクワクしています。

Honoとは

HonoとはAPI構築用のWebフレームワークで、Node.jsやDeno、Cloudflare WorkersなどのさまざまなJavaScript実行環境で動作します。

軽量かつ高速なAPIを構築できることが魅力の一つで、シンプルな記述で高性能なAPIを構築することが可能です。

TypeScriptに対応していること、複数のバックエンド環境で動作可能なこと、軽量なミドルウェアが充実していることなど、次世代のバックエンド構築を担うのではないかと(新卒ながら)思わせるような、魅力的なオプションが多く含まれています。

使ってみた

今回は家計簿アプリの開発を想定して、Honoのフィジビリティチェックをおこないました。観点はズバリ、「どれだけ簡単にAPIの実装ができるかどうか」です。

利用したライブラリ及び開発ツール

  1. Hono
  2. 今回の主役。APIの主軸を担ってもらいます。

  3. PostgreSQL
  4. DBの構築と操作のために利用。Honoとの繋ぎ込みはdotenv、pgを利用しました。

  5. Postman
  6. APIテスト用ツール。簡単な設定でAPIの検証ができるため重宝しています。

環境構築

Honoの導入

Honoの実行環境はnpmで構築しました。TypeScriptで開発を進めたため、各ライブラリの型定義などは別途インストールしています。

bash

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 が以下となります。

typescript
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

typescript

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

typescript

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

typescript

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への接続処理を記載しています

typescript

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方式のバックエンドを構築する上で十分に選択肢になりえる性能だと思います。

基本的なセキュリティもミドルウェアのみで対応が可能なため、本番構築も容易かと。使ってみたい……。

いつか複雑な処理も実装できるように、もっともっと技術をつけないとですね。それではご拝読いただき、ありがとうございました。

新卒採用募集中!
LIGは、Webデザインからグローバルなシステム開発まで、幅広い分野のコンサルティングから制作、運用までおこなう企業です。

  • 日本・フィリピン・ベトナムでの活躍チャンス
  • 最先端技術と多言語環境での成長
  • 有識者による月1回の勉強会

現在、海外拠点(フィリピン、ベトナム)に関われるエンジニアを募集しています。日本だけじゃなく世界で活躍することに興味のある方は、以下よりぜひご応募ください!

新卒採用ページへ

この記事のシェア数

Masaki Yamaoka
Masaki Yamaoka Technology / Technical Director / 山岡 正樹

Technical Directorチームに所属し、国内大手企業のシステム開発ディレクション業務に従事。AWSを中心としたクラウドサービスの活用に関心があり、最適なソリューションを追求している。大学院で農学の修士課程を修了後、2024年にLIGに入社。

このメンバーの記事をもっと読む
10年以上の開発実績があるLIGが、最適な開発体制や見積もりをご提案します
相談する サービス詳細を見る