Hono용 CLI 어댑터 구축

발행: (2026년 1월 3일 오전 10:24 GMT+9)
5 min read
원문: Dev.to

Source: Dev.to

Overview

hono-cli-adapter를 사용하면 Hono 앱을 CLI에서 직접 호출할 수 있습니다.
비즈니스 로직은 Hono에 그대로 두어 Postman이나 Insomnia로 디버깅하고, 동일한 코드를 CLI로도 배포하며, stdout에 직접 쓰는 것을 피할 수 있습니다—CLI가 모든 출력을 제어합니다.
보너스: 엔트리포인트만 교체하면 간단한 MCP 서버 지원도 가능합니다.

CLI 도구를 디버깅하는 것은 번거롭습니다: 실행 → 인자 수정 → 다시 실행—요청 히스토리가 없고, 쉽게 검토할 수 없습니다.
hono-cli-adapter를 사용하면 CLI 로직이 HTTP 엔드포인트 뒤에 숨어 있지만 실제 HTTP 서버가 필요하지 않습니다. 라이브러리가 CLI 인자를 HTTP 요청으로 변환하고 app.fetch()를 직접 호출합니다.

Why Hono?

  • Web‑tool friendly – 개발 중에 Postman/Insomnia를 사용할 수 있습니다. 요청을 저장하고, 응답을 검사하며, 빠르게 반복합니다.
  • MCP ready – 엔트리포인트만 교체하면 로컬 및 원격 MCP 서버를 모두 지원합니다. 동일한 로직, 다른 전송 방식.
  • Testable – Hono 앱이 진리의 원천이며 독립적으로 테스트할 수 있습니다.

Installation

npm install hono-cli-adapter

Basic Usage

CLI 스크립트를 생성합니다 (예: my-cli.js):

#!/usr/bin/env node
import { cli } from 'hono-cli-adapter';
import { app } from './app.js';

await cli(app);

실행 가능하도록 만듭니다:

chmod +x my-cli.js

Running commands

# Call /hello/:name route
node my-cli.js hello Taro
# → "Hello, Taro!"

# List available routes
node my-cli.js --list

# Show help
node my-cli.js --help

CLI 인자는 URL 경로와 쿼리 파라미터가 된 뒤 app.fetch()에 전달됩니다.

Design Constraints

1. Thin CLI, fat Hono

모든 비즈니스 로직은 Hono에 존재하고, CLI는 플래그와 출력만 처리합니다. 이렇게 하면 CLI와 HTTP 간 동작이 일관되고, Hono 앱을 독립적으로 테스트할 수 있습니다.

2. No side effects

라이브러리는 stdout에 절대 쓰지 않습니다. 출력 포맷은 사용자가 직접 결정합니다:

const { code, lines } = await runCli(app, process);
for (const l of lines) console.log(l); // 또는 JSON.stringify, 파이프 등
process.exit(code);

3. POST‑only

CLI 명령은 동작을 트리거하므로 기본적으로 POST를 사용합니다. 필요에 따라 GET 지원을 나중에 추가할 수 있습니다.

Environment Variable Precedence

세 단계, 마지막 것이 우선합니다:

  1. process.env (기본)
  2. options.env (어댑터 설정)
  3. --env 플래그 (최고 우선순위)
await cli(app, process, { env: { API_URL: 'https://dev.example.com' } });

node my-cli.js do-thing --env API_KEY=secret-123

Passing JSON Body

-- 뒤에 오는 토큰은 JSON 본문이 됩니다:

node my-cli.js create-user -- name=Taro email=taro@example.com
# Sends: { "name": "Taro", "email": "taro@example.com" }

Transforming Requests per Command

await adaptAndFetch(app, process.argv.slice(2), {
  beforeFetch: {
    upload: async (req, argv) => {
      if (argv.file) {
        const buf = await fs.readFile(argv.file);
        const headers = new Headers(req.headers);
        headers.set('content-type', 'application/octet-stream');
        return new Request(req, { body: buf, headers });
      }
    }
  }
});

Enriching --help with OpenAPI

await runCli(app, process, { openapi: myOpenApiSpec });

생성된 도움말에는 파라미터 타입, 필수/선택 플래그, 설명이 표시됩니다. hono-openapi와 잘 어울립니다.

Implementation Notes

  • listPostRoutes는 Hono 내부 라우터 구조를 검사합니다. 주요 Hono 업데이트 시 깨질 수 있으니, 프로덕션에서는 자체 라우트 목록을 유지하는 것을 고려하세요.
  • ESM only – CommonJS 지원이 없습니다. Node 18 이상이 필요합니다.

Conclusion

Hono + CLI는 더 많은 주목을 받아야 할 패턴입니다. 개발 중에 웹 도구를 활용하고, 손쉬운 MCP 지원을 받으며, 로직을 중복하지 않고 테스트 가능한 코어를 얻을 수 있습니다.

Check it out on GitHub:

Back to Blog

관련 글

더 보기 »

V8 커버리지 제한 사항 및 우회 방법

V8 Native Coverage – Blind Spots & Work‑arounds for React JSX V8 네이티브 커버리지는 강력합니다: 어떤 번들러와도 작동하고, 오버헤드가 최소이며, 데이터를 수집할 수 있습니다.