---
id: wiki-2026-0508-typescript-api-development
title: TypeScript API Development
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [TS Backend API, TypeScript REST API]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, api, backend, hono, trpc, zod]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: typescript
  framework: hono
---

# TypeScript API Development

## 매 한 줄
> **"매 end-to-end type-safe API 의 boundary 의 zero-cost"**. 2026 의 TS API stack 의 Hono + Zod + Drizzle + tRPC (또는 OpenAPI) 의 combo 의 client 의 server 의 schema 의 share, 매 runtime validate + compile-time infer 의 unify. 매 Express + JSDoc 의 era 의 끝, 매 edge runtime (Bun, Deno, Cloudflare Workers) 의 first-class.

## 매 핵심

### 매 stack
- **Runtime**: Bun · Node 22+ · Deno 2 · Cloudflare Workers.
- **Framework**: Hono (universal) · Elysia (Bun) · Fastify (Node) · tRPC (RPC over HTTP).
- **Validation**: Zod 3.x · Valibot · ArkType.
- **DB**: Drizzle (SQL) · Prisma 6 · Kysely.
- **Auth**: Lucia · better-auth · clerk.
- **Tracing**: OpenTelemetry · Sentry.

### 매 type-safety boundary
- **Input**: Zod schema 의 parse — runtime validate + `infer` 의 type derive.
- **DB**: Drizzle / Prisma 의 query 의 typed result.
- **Output**: response schema 의 explicit serialize.
- **Client**: tRPC 또는 hono/client 의 server type 의 import — codegen X.

### 매 응용
1. Edge API on Cloudflare Workers (Hono).
2. Internal RPC (tRPC) for monorepo SPA.
3. Public REST API with OpenAPI spec.
4. Background job worker with typed queue.

## 💻 패턴

### Hono + Zod typed route
```typescript
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const app = new Hono();

const createUser = z.object({
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
});

app.post('/users', zValidator('json', createUser), async (c) => {
  const data = c.req.valid('json'); // 매 typed
  const user = await db.insert(users).values(data).returning();
  return c.json(user[0], 201);
});

export type AppType = typeof app;
```

### Hono RPC client
```typescript
import { hc } from 'hono/client';
import type { AppType } from './server';

const client = hc<AppType>('https://api.example.com');
const res = await client.users.$post({
  json: { email: 'a@b.com', age: 30 }, // 매 typed
});
```

### tRPC procedure
```typescript
import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();

export const appRouter = t.router({
  user: t.router({
    byId: t.procedure
      .input(z.object({ id: z.string().uuid() }))
      .query(async ({ input }) => {
        return db.query.users.findFirst({ where: eq(users.id, input.id) });
      }),
    create: t.procedure
      .input(z.object({ email: z.string().email() }))
      .mutation(async ({ input }) => {
        return db.insert(users).values(input).returning();
      }),
  }),
});

export type AppRouter = typeof appRouter;
```

### Drizzle typed query
```typescript
import { drizzle } from 'drizzle-orm/postgres-js';
import { pgTable, uuid, text, integer } from 'drizzle-orm/pg-core';
import { eq } from 'drizzle-orm';

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull().unique(),
  age: integer('age').notNull(),
});

const db = drizzle(connectionString, { schema: { users } });

const u = await db.select().from(users).where(eq(users.email, 'a@b.com'));
// 매 u: { id: string; email: string; age: number }[]
```

### OpenAPI 의 hono/zod-openapi
```typescript
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';

const route = createRoute({
  method: 'get',
  path: '/users/{id}',
  request: { params: z.object({ id: z.string().uuid() }) },
  responses: {
    200: {
      content: { 'application/json': { schema: z.object({ id: z.string(), email: z.string() }) } },
      description: 'User',
    },
  },
});

const app = new OpenAPIHono();
app.openapi(route, async (c) => {
  const { id } = c.req.valid('param');
  const user = await getUser(id);
  return c.json(user);
});
app.doc('/spec.json', { openapi: '3.1.0', info: { title: 'API', version: '1.0' } });
```

### Error handling 의 typed
```typescript
import { HTTPException } from 'hono/http-exception';

app.onError((err, c) => {
  if (err instanceof HTTPException) return err.getResponse();
  console.error(err);
  return c.json({ error: 'Internal' }, 500);
});

// throw inside handler
throw new HTTPException(404, { message: 'User not found' });
```

### Auth middleware
```typescript
import { jwt } from 'hono/jwt';

app.use('/admin/*', jwt({ secret: process.env.JWT_SECRET! }));

app.get('/admin/me', (c) => {
  const payload = c.get('jwtPayload'); // 매 typed via module augmentation
  return c.json(payload);
});
```

### Streaming response
```typescript
import { streamSSE } from 'hono/streaming';

app.get('/events', (c) =>
  streamSSE(c, async (stream) => {
    for await (const event of source) {
      await stream.writeSSE({ data: JSON.stringify(event), event: 'tick' });
    }
  })
);
```

### Test 의 typed
```typescript
import { testClient } from 'hono/testing';
const client = testClient(app);
const res = await client.users.$post({ json: { email: 'a@b.com', age: 30 } });
expect(res.status).toBe(201);
```

## 매 결정 기준
| 상황 | Approach |
|---|---|
| Internal monorepo SPA backend | tRPC |
| Public API + 3rd party consumer | Hono + zod-openapi |
| Edge / Workers / Bun | Hono |
| Node-only legacy | Fastify + zod |
| GraphQL preferred | Pothos + GraphQL Yoga |
| Microservice RPC | tRPC 또는 gRPC |

**기본값**: 매 새 TS API 의 Hono + Zod + Drizzle, 매 internal client 의 case 의 tRPC.

## 🔗 Graph
- 부모: [[TypeScript]]
- 변형: [[Hono]] · [[Fastify]] · [[Elysia]]
- Adjacent: [[Zod]] · [[OpenTelemetry]]

## 🤖 LLM 활용
**언제**: 매 새 API 의 design, 매 client/server type 의 share 의 want.
**언제 X**: 매 polyglot consumer 의 strict OpenAPI 의 require — tRPC 의 X 의 zod-openapi 의 prefer.

## ❌ 안티패턴
- **Express + raw body parse**: 매 type-unsafe + middleware sprawl — 매 Hono 의 migrate.
- **`any` 의 request body**: 매 boundary 의 hole — 매 Zod 의 always.
- **Manual codegen 의 client SDK**: 매 hono/client 또는 tRPC 의 zero-codegen 의 prefer.
- **DB query 의 raw string**: 매 SQL injection + type loss — 매 Drizzle / Kysely.
- **Error response 의 inconsistent shape**: 매 client 의 narrow 의 X — 매 Problem Details (RFC 9457) 의 standard.

## 🧪 검증 / 중복
- Verified (Hono v4 docs, tRPC v11 docs, Drizzle docs 2026).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Hono+Zod+Drizzle stack, tRPC, OpenAPI |
