1182 단어
6 분
서버 배포를 쌀먹하기 w. Hono
2026-03-07
태그 없음

돈벌기#

돈이 없다. 이것은 학생의 숙명이다. 프리티어를 떠도는 노마드의 삶이다. 그런데 코딩을 하면 배포할 일이 생긴다. 당연한 거 아닌가. 배포를 그럼 git clone하라고 할 수도 없지 않나.

FaaS/서버리스#

서버리스의 장점은 그것이다. 24/7이 필요하긴 한데, 굳이 막 항상 사람이 들어오는 건 아닌 사이트 많지 않나? 그런 상황이 많다. 서버리스는 그 상황에 딱 맞는다. 트래픽 없다? 서버 내릴게. 서버 내려? 돈 안뺄게.

Azure Functions#

Azure Functions는 마이크로소프트의 서버리스 플랫폼이다.

장점:

  • 언어 독립적이다, 안 되면 커스텀 핸들러로 어떻게든 된다
  • SLA가 있다(무료티어 포함)
  • 무료 티어도 있고 학생 크레딧을 쓸 수 있다

단점:

  • 하드캡이 없다. 카드 넣어야 한다
  • 콜드 스타트가 좀 걸린다
  • 저장공간은 또 다른 서비스 써야 한다
  • 꽤 고립적이다 Hono 지원

Cloudflare Workers#

Cloudflare Workers는 JS를 지원하는 FaaS이다.

장점:

  • 빠른 콜드 스타트(0ms!!!!)
  • 무료티어 넘어가면 청구 없이 터진다(Paid 플랜 업그레이드하지 않으면)
  • CI/CD를 CF에서 제공한다(=Github Action 요금 낼 필요 없다, 워크플로우 짤 필요 없다)
  • 분당 1000회 무료 티어
  • 다른 WinterTC 계열 서비스 대비 더 넉넉하다

단점:

  • JS와 WASM 컴파일을 지원하는 일부 언어만 가능하다
  • Github Action은 어짜피 퍼블릭레포 무제한 무료라 굳이…?
  • 런타임은 Node.JS와 거리가 멀다, DOM 없다
  • CPU 타임이 제한적이다(무료플랜 10ms/콜)

Deno Deploy#

Deno Deploy는 Cloudflare Workers와 유사한 FaaS 호스팅 서비스다.

장점:

  • Cloudflare Workers 대비 넉넉한 CPU 타임(15h/월)
  • 빠른 콜드 스타트
  • 셋업하기 쉽다(import { Hono } from "jsr:hono", TypeScript OOTB 지원 등)
  • 제일 JS스럽고, 배워야 할 자체 개념이 적다

단점:

  • JS/TS만 지원한다
  • 카드 등록이 필요하다(업그레이드하기 전까진 billing X)
  • Cloudflare Workers 대비 물리적 인프라가 떨어진다
  • 다들 Cloudflare 계정은 있지만 Deno Deploy는 없는 경우도 있다

Hono#

“신”

Hono는 각종 FaaS 프로바이더를 위한 프레임워크다. 지원하는 실행 환경은…

  • Node.js
  • Deno(Deno Deploy 포함)
  • Bun
  • WebAssembly
  • Service Worker

이러하다. 또 호스팅은 이 플랫폼들을 지원한다.

  • Cloudflare Workers, Cloudflare Pages
  • Fastly Compute
  • Vercel, Netlify
  • Next.js
  • AWS Lambda, Lambda@Edge
  • Azure Functions
  • Google Cloud Run
  • Supabase Functions
  • Ali Function Compute

이 무슨 개얼탱없는 라인업이란 말인가. 당장 써야지. 원래 FaaS 서비스들은 꽤 벤더 종속적이다. 벤더에서 제공한 API를 쓰게 되는데, 이러면 다른 곳으로 마이그레이션하기 상당히 어렵다.

// Cloudflare Workers, ES Modules 문법
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const name = url.searchParams.get('name');
if (name) {
return new Response(`Hello, ${name}! Welcome to Cloudflare Workers.`, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
} else {
return new Response('Hello World! This is Cloudflare Workers.', {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
}
},
};
// Azure Functions
export async function serve(context, req) {
context.log('HTTP trigger function processed a request.');
const name = req.query.name || (req.body && req.body.name);
if (name) {
context.res = {
status: 200,
body: `Hello, ${name}! Welcome to Azure Functions.`
};
} else {
context.res = {
status: 200,
body: 'Hello World! This is Azure Functions.'
};
}
};
export default serve;

네이티브 API를 쓰면 벌써 코드가 다르다. CF Workers는 함수를 객체에 담아 export default하고, 그 함수는 Response 객체를 리턴한다. 하지면 Azure Functions는 함수가 context.res를 수정한다.

그런데 그때, Hono가 등장한다…

import { Hono } from 'hono';
type Env = {
Bindings: Record<string, unknown>;
};
const app = new Hono<Env>();
// POST 요청 처리 예제
app.post('/api/hello', async (c) => {
const body = await c.req.json<{ name?: string }>();
const name = body.name || 'World';
return c.json({
message: `Hello, ${name}!`,
method: 'POST',
framework: 'Hono',
});
});
export default app;

이거, 어디선가 본 패턴이지 않은가?

import express, { Request, Response } from 'express';
const app = express();
app.use(express.json());
app.post('/api/hello', (req: Request, res: Response) => {
const body = req.body as { name?: string };
const name = body.name || 'World';
res.json({
message: `Hello, ${name}!`,
method: 'POST',
framework: 'Express',
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
export default app;

Express.js와 매우 유사하다. 선언적이고, 자유분방하다. 게다가 타입스크립트를 염두하고 만들어졌기 때문에,

//...
const app=new Hono()
.post("/post", ...)
.get("/get", ...);
export default app;

이와 같은 방식으로 메서드 체인을 걸면, app의 타입 자체에 라우트 정보가 작성된다. 이는 RPC나 테스팅 시 매우 유용하다.

개인 사용을 위해 Hono용 템플릿을 작성해두었다.