2026년 08월 19일

NestJS에서 Claude API를 호출하는 방법

NestJS는 TypeScript 기반의 구조화된 백엔드 프레임워크로, 모듈-서비스-컨트롤러 패턴 덕분에 외부 API 연동을 깔끔하게 관리할 수 있습니다. 이 글에서는 Anthropic의 Claude API를 NestJS 프로젝트에 통합하는 방법을 환경 설정부터 실제 호출 코드까지 순서대로 설명합니다.

사전 준비

Claude API를 사용하려면 Anthropic 계정과 API 키가 필요합니다. Anthropic 콘솔에서 계정을 만들고 API 키를 발급받으세요.

NestJS 프로젝트가 이미 있다면 다음 패키지만 추가로 설치하면 됩니다.

npm install @anthropic-ai/sdk
npm install @nestjs/config

@anthropic-ai/sdk는 Anthropic이 공식으로 제공하는 Node.js/TypeScript SDK이고, @nestjs/config는 환경 변수를 NestJS 방식으로 관리할 때 사용합니다.

환경 변수 설정

API 키는 코드에 직접 넣지 않고 환경 변수로 관리하는 것이 기본입니다. 프로젝트 루트에 .env 파일을 만들고 아래와 같이 작성합니다.

ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxxxxxx

그다음 AppModule에서 ConfigModule을 전역으로 등록합니다.

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
  ],
})
export class AppModule {}

isGlobal: true로 설정하면 다른 모듈에서 별도의 import 없이 ConfigService를 주입받을 수 있어 편리합니다.

Claude 서비스 모듈 만들기

NestJS의 장점은 기능별로 모듈을 분리해 관리할 수 있다는 점입니다. Claude API 호출 로직을 전담하는 모듈을 별도로 만들어 두면 재사용성과 유지보수성이 높아집니다.

모듈 생성

nest generate module claude
nest generate service claude

NestJS CLI를 사용하면 기본 파일 구조가 자동으로 만들어집니다.

ClaudeService 구현

claude.service.ts 파일을 아래와 같이 작성합니다.

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Anthropic from '@anthropic-ai/sdk';

@Injectable()
export class ClaudeService {
  private readonly client: Anthropic;

  constructor(private readonly configService: ConfigService) {
    this.client = new Anthropic({
      apiKey: this.configService.get<string>('ANTHROPIC_API_KEY'),
    });
  }

  async sendMessage(prompt: string): Promise<string> {
    const message = await this.client.messages.create({
      model: 'claude-opus-4-5',
      max_tokens: 1024,
      messages: [
        {
          role: 'user',
          content: prompt,
        },
      ],
    });

    const block = message.content[0];
    if (block.type === 'text') {
      return block.text;
    }

    return '';
  }
}

생성자에서 ConfigService를 통해 API 키를 읽어 Anthropic 클라이언트를 초기화합니다. sendMessage 메서드는 사용자 프롬프트를 받아 Claude에 전송하고 텍스트 응답을 반환합니다.

message.content는 배열 형태이며 각 항목이 type 필드를 가집니다. 텍스트 응답의 경우 type이 'text'이므로 해당 블록에서 .text 값을 꺼내면 됩니다.

ClaudeModule 설정

import { Module } from '@nestjs/common';
import { ClaudeService } from './claude.service';

@Module({
  providers: [ClaudeService],
  exports: [ClaudeService],
})
export class ClaudeModule {}

exports에 ClaudeService를 추가해야 다른 모듈에서 이 서비스를 주입받아 사용할 수 있습니다.

컨트롤러에서 사용하기

실제 HTTP 엔드포인트를 통해 Claude API를 호출하는 예시를 만들어 봅니다.

DTO 정의

export class ChatRequestDto {
  prompt: string;
}

간단하게 prompt 필드 하나만 받는 DTO입니다. 실제 프로젝트에서는 class-validator로 유효성 검사를 추가하는 것이 좋습니다.

컨트롤러 작성

import { Body, Controller, Post } from '@nestjs/common';
import { ClaudeService } from './claude.service';
import { ChatRequestDto } from './dto/chat-request.dto';

@Controller('chat')
export class ChatController {
  constructor(private readonly claudeService: ClaudeService) {}

  @Post()
  async chat(@Body() dto: ChatRequestDto): Promise<{ reply: string }> {
    const reply = await this.claudeService.sendMessage(dto.prompt);
    return { reply };
  }
}

POST /chat 요청으로 { "prompt": "안녕하세요" } 같은 JSON을 보내면 Claude의 응답을 { "reply": "..." } 형태로 돌려줍니다.

이 컨트롤러를 사용할 모듈에 ClaudeModule을 import하는 것을 잊지 마세요.

@Module({
  imports: [ClaudeModule],
  controllers: [ChatController],
})
export class ChatModule {}

시스템 프롬프트 추가하기

Claude API는 system 파라미터를 지원해 Claude의 역할이나 행동 방식을 사전에 정의할 수 있습니다. 서비스 메서드를 확장해 시스템 프롬프트를 선택적으로 받도록 수정하면 활용 범위가 넓어집니다.

async sendMessage(
  prompt: string,
  systemPrompt?: string,
): Promise<string> {
  const message = await this.client.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 1024,
    system: systemPrompt,
    messages: [
      {
        role: 'user',
        content: prompt,
      },
    ],
  });

  const block = message.content[0];
  if (block.type === 'text') {
    return block.text;
  }

  return '';
}

시스템 프롬프트를 활용하면 특정 도메인에 맞게 Claude의 응답 스타일이나 역할을 고정할 수 있어 서비스 품질을 높이는 데 유용합니다.

대화 히스토리 관리

단순한 단발성 질문이 아닌 멀티턴 대화를 구현하려면 이전 대화 내역을 messages 배열에 함께 전달해야 합니다. Claude API는 상태를 저장하지 않기 때문에 클라이언트 또는 서버 쪽에서 직접 관리해야 합니다.

import { MessageParam } from '@anthropic-ai/sdk/resources';

async chat(history: MessageParam[], newMessage: string): Promise<string> {
  const messages: MessageParam[] = [
    ...history,
    { role: 'user', content: newMessage },
  ];

  const response = await this.client.messages.create({
    model: 'claude-opus-4-5',
    max_tokens: 1024,
    messages,
  });

  const block = response.content[0];
  if (block.type === 'text') {
    return block.text;
  }

  return '';
}

실제 애플리케이션에서는 세션 ID를 기준으로 Redis나 데이터베이스에 대화 히스토리를 저장하고, 요청마다 불러와서 전달하는 방식으로 구현하는 경우가 많습니다.

에러 처리

API 호출 중 네트워크 오류나 API 한도 초과 같은 예외가 발생할 수 있습니다. NestJS에서는 예외를 HttpException으로 변환해 적절한 HTTP 상태 코드와 함께 클라이언트에 전달하는 것이 일반적입니다.

import {
  Injectable,
  InternalServerErrorException,
} from '@nestjs/common';
import Anthropic from '@anthropic-ai/sdk';

// sendMessage 메서드 내부
try {
  const message = await this.client.messages.create({ ... });
  // ...
} catch (error) {
  if (error instanceof Anthropic.APIError) {
    throw new InternalServerErrorException(
      `Claude API 오류: ${error.message}`,
    );
  }
  throw error;
}

Anthropic.APIError를 확인하면 SDK에서 발생한 API 관련 오류를 구분해서 처리할 수 있습니다. 필요에 따라 error.status를 확인해 429(요청 한도 초과)나 401(인증 오류) 등의 상황을 세분화해 대응할 수도 있습니다.

정리

NestJS에서 Claude API를 연동하는 핵심 흐름을 요약하면 다음과 같습니다.

  1. @anthropic-ai/sdk 설치 및 ConfigModule로 환경 변수 관리
  2. ClaudeService에서 Anthropic 클라이언트 초기화 및 메서드 구현
  3. ClaudeModule로 모듈화하고 필요한 곳에 import
  4. 컨트롤러에서 서비스를 주입받아 엔드포인트 연결
  5. 시스템 프롬프트, 멀티턴, 에러 처리를 필요에 따라 추가

이 구조를 바탕으로 챗봇, 문서 요약, 코드 리뷰 보조 등 다양한 AI 기능을 NestJS 애플리케이션에 빠르게 통합할 수 있습니다.