드디어, 괴물이 아닌 Bun을 위한 객체 지향 프레임워크

발행: (2026년 1월 2일 오전 05:06 GMT+9)
11 min read
원문: Dev.to

Source: Dev.to

Bun은 터무니없는 성능 수치로 문을 박살냈다. 모두가 Bun.serve()를 시험해 보려고 달려들었고, 첫 번째 파도의 프레임워크들(Elysia, Hono)은 기능적이고 미니멀한 “베어‑메탈” 스타일에 집중했다.

그들은 정말 훌륭하지만, 오해하지 말아 주세요. 나는 뭔가 빠진 느낌이 들었다.

.NET, Java 같은 생태계 혹은 Node의 NestJS와 같은 환경에서 온 우리는 Dependency Injection (DI), Decorators, Controllers를 클래스 단위로 조직하는 특정 패턴에 익숙해 있다. 나는 5초가 걸리는 무거운 프레임워크 없이도 그런 견고한 아키텍처가 필요했다. Bun의 순수한 속도와 함께 NestJS의 구조가 절실했다.

그때 Carno.js를 발견하고 사용하기 시작했다.

Carno.js란?

Carno.js“성능 우선 프레임워크” 로, Bun에 네이티브하게 동작하도록 정의됩니다.

바퀴를 새로 만들려는 것이 아니라 정리하려는 것입니다. Carno.js가 제공하는 기능은 다음과 같습니다:

  • 컨트롤러 & 데코레이터 (@Get, @Post, @Body, …) – 이미 익숙한 방식.
  • 의존성 주입 – 네이티브하고 가벼운 구현.
  • 커스텀 ORM맞춤형 SQL Builder를 갖춘 경량 ORM (Knex나 외부 쿼리 빌더 없이 순수하고 최적화된 성능).
  • 진정한 모듈성 – 코어가 작습니다. 실제로 필요할 때만 @carno.js/orm 또는 @carno.js/queue를 설치하세요.

Bun의 순수 성능을 포기하지 않으면서 확장 가능하고 체계적인 API를 구축하고자 하는 개발자를 위한 프레임워크입니다.

설치 및 요구 사항

  1. Bun 설치 (v1.0 이상).

  2. 폴더를 만들고 프로젝트를 초기화합니다:

    mkdir my-carno-project
    cd my-carno-project
    bun init
  3. 프레임워크 코어를 설치합니다:

    bun add @carno.js/core
  4. 데코레이터 활성화 – Carno는 클래식 데코레이터 문법을 사용하므로 tsconfig.json을 업데이트합니다:

    {
      "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
        // ...other options
      }
    }

처음부터 프로젝트 만들기 (단계별)

1. 폴더 구조

간단히 시작하려면 레이아웃을 최소화하세요:

src/
├── controllers/
│   └── user.controller.ts
├── services/
│   └── user.service.ts
└── index.ts

2. 서비스 만들기 (의존성 주입)

Carno는 내장 DI 컨테이너를 제공하므로 외부 라이브러리가 필요 없습니다.

// src/services/user.service.ts
import { Service } from '@carno.js/core';

@Service()
export class UserService {
  private users = [
    { id: 1, name: 'Myron' },
    { id: 2, name: 'Bun User' }
  ];

  findAll() {
    return this users;
  }

  create(name: string) {
    const newUser = { id: this.users.length + 1, name };
    this.users.push(newUser);
    return newUser;
  }
}

3. 컨트롤러 만들기

NestJS나 Spring을 사용해 본 경험이 있다면 익숙하게 느껴질 것입니다. 생성자에서 UserService가 주입되는 부분을 확인하세요.

// src/controllers/user.controller.ts
import { Controller, Get, Post, Body } from '@carno.js/core';
import { UserService } from '../services/user.service';

@Controller('/users')
export class UserController {
  constructor(private userService: UserService) {}

  @Get()
  getUsers() {
    return this.userService.findAll();
  }

  @Post()
  createUser(@Body('name') name: string) {
    // 기본 검증
    if (!name) {
      throw new Error('Name is required');
    }
    return this.userService.create(name);
  }
}

4. 진입점

index.ts에서 모든 것을 연결합니다. Carno에 사용할 컨트롤러와 제공자(서비스)를 알려줘야 합니다.

// src/index.ts
import { Carno } from '@carno.js/core';
import { UserController } from './controllers/user.controller';
import { UserService } from './services/user.service';

const app = new Carno({
  providers: [
    UserService,     // 서비스 등록
    UserController   // 컨트롤러 등록
  ]
});

const port = 3000; // 기본 포트 3000 또는 전달한 포트
app.listen(port);

5. 실행하기

bun run src/index.ts

완료되었습니다. 복잡한 빌드 단계도 없고, 느린 트랜스파일링도 없습니다. 바로 실행됩니다.

전체 예시: 모듈성

Carno는 중첩 라우트(서브‑컨트롤러)를 지원합니다. API가 성장해도 거대한 라우트 파일이 필요하지 않습니다.

// Example of a versioned API controller
import { Controller } from '@carno.js/core';
import { UserController } from './controllers/user.controller';
import { ProductController } from './controllers/product.controller';

@Controller({
  path: '/api/v1',
  children: [UserController, ProductController]
})
export class ApiV1Controller {}

이렇게 하면 API 버전 관리가 손쉽게 이루어집니다.

장점 및 트레이드오프

✅ 장점

  • DX (개발자 경험): 클래스와 데코레이터를 작성하는 것이 대규모 팀에게 매우 가독성이 좋습니다.
  • 성능: 내부적으로 Bun.serve를 사용합니다—빠릅니다. 매우 빠릅니다.
  • 통합 에코시스템: @carno.js/orm@carno.js/queue가 동일한 패턴을 따르므로, 서로 다른 라이브러리를 수동으로 연결할 필요가 없습니다.

⚠️ 트레이드오프

  • 성숙도: Express나 NestJS보다 최신입니다. 레거시 프로젝트에서 의존하던 희귀 플러그인이 없을 수도 있습니다.
  • Bun 전용: 회사 정책이 Node.js를 강제한다면, Carno는 (아직) 옵션이 아닙니다.
  • 커뮤니티 규모: 현재 Hono나 Elysia보다 작아 가끔 소스 코드를 직접 읽어야 할 수도 있습니다(솔직히 꽤 깔끔합니다).

Carno 실용 성능 팁

  • Use Singleton: 기본적으로 서비스는 싱글톤입니다. REQUEST 스코프(ProviderScope.REQUEST)는 꼭 필요할 때만 사용하세요(예: 요청당 테넌트 데이터). 클래스 인스턴스화는 CPU 비용이 많이 듭니다.
  • JSON Serialization: Carno는 객체를 자동으로 직렬화합니다. 컨트롤러에서는 순환 메서드가 있는 복잡한 클래스 대신 단순 객체(POJO)를 반환하세요.
  • Cache: Carno는 캐시 연동이 잘 되어 있습니다. 엔드포인트가 무거운 경우 캐시를 활용하세요.
  • Watch Your Logs: 프로덕션에서는 비동기 로거를 사용하세요(Carno는 내부적으로 pino를 사용합니다; 메인 스레드를 차단하지 않도록 설정하세요).
  • Avoid Heavy Middleware: Carno의 미들웨어 모델은 유연하지만, 각 미들웨어가 요청에 “홉”을 추가합니다. 가볍게 유지하세요.

빠른 비교

프레임워크스타일언제 사용하나요?
Carno.js객체지향, 데코레이터, 구조화된NestJS/Java/.NET을 좋아하지만 Bun의 속도와 맞춤형 경량 ORM을 원한다면.
Elysia / Hono함수형, 최소주의초경량 마이크로서비스, 엣지 함수, 혹은 함수형 프로그래밍을 선호한다면.
NestJS객체지향, 의견이 강한, 대규모대규모 기업 생태계가 필요하고 Node.js에서 실행한다면.
Express레거시구 프로젝트. 새로운 프로젝트는 피하세요 (느리고 구식 개발 경험).

빠른 FAQ

1. SQL 데이터베이스와 함께 사용할 수 있나요?
네! @carno.js/orm이 권장되는 방법입니다. 이 생태계를 위해 특별히 구축되었으며 오래된 쿼리 빌더의 무게를 지니고 있지 않습니다.

2. Validation을 지원하나요?
네, class-validatorclass-transformer를 기본적으로 사용합니다. @IsString()와 같은 데코레이터로 DTO를 생성합니다.

3. Testing과는 어떻게 작동하나요?
Bun은 기본 테스트 러너(bun test)를 제공합니다. Carno가 의존성 주입을 사용하기 때문에 컨트롤러 단위 테스트에서 서비스를 “mock”하는 것이 매우 간단합니다.

4. Node 라이브러리와 호환되나요?
대부분 호환됩니다. 이는 Bun의 호환성 덕분입니다. 잘 알려지지 않은 네이티브 Node API에 의존하지 않는 라이브러리를 선호하세요.

5. Serverless 환경에서도 사용할 수 있나요?
가능하지만, Carno는 DI와 구조가 효과를 발휘하는 컨테이너/장기 실행 프로세스에서 더 큰 장점을 가집니다.

결론

Carno.js는 중요한 격차를 메웁니다: 느려짐 없는 견고한 구조. 느린 Node API를 Bun으로 마이그레이션할 변명을 찾고 있었지만 코드 조직이 무너질까 두려웠다면, Carno를 한 번 시도해 보세요.

유용한 링크

팁이 마음에 드셨나요? Bun에서 OO 프레임워크를 사용해 본 적이 있나요? 댓글로 알려 주세요!

Back to Blog

관련 글

더 보기 »

2015년처럼 API를 작성하지 마세요

우리는 2025년에 살고 있으며, 많은 코드베이스가 여전히 API를 단순히 “JSON을 반환하는 엔드포인트”로만 취급합니다. API 설계가 기본 CRUD 라우트를 넘어 발전하지 않았다면, 당신은 sacr…