실습 프로젝트 준비하기
이 장에서는 가장 작은 OpenAI API 호출을 만든 뒤, 실서비스에 가까운 서버 코드로 조금씩 다듬어 본다. 먼저 빈 폴더에서 Node.js 프로젝트를 만든다.
npm init -y
npm install openai dotenvOpenAI 공식 JavaScript/TypeScript SDK는 npm install openai로 설치하고, ESM 방식의 import OpenAI from 'openai'; 사용을 기본 예제로 안내한다1. 따라서 package.json에는 다음 설정을 추가한다.
{
"type": "module",
"scripts": {
"start": "node src/index.js"
}
}API Key는 코드에 직접 쓰지 않는다. 프로젝트 루트에 .env 파일을 만들고 다음처럼 저장한다.
OPENAI_API_KEY=sk-...그리고 .gitignore에 .env를 추가한다. 공식 quickstart 예제도 OPENAI_API_KEY를 환경변수로 설정한 뒤 Node.js 스크립트를 실행하는 흐름을 사용한다2.
AI 생성 이미지
가장 작은 API 호출 만들기
src/index.js를 만들고 다음 코드를 작성한다.
import 'dotenv/config';
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function main() {
const response = await client.responses.create({
model: 'gpt-4.1-mini',
input: [
{
role: 'system',
content: '너는 짧고 실용적인 답변을 하는 AI 글쓰기 도우미다.',
},
{
role: 'user',
content: 'AI API 개발을 처음 배우는 사람에게 줄 조언 3가지를 써 줘.',
},
],
max_output_tokens: 300,
});
console.log(response.output_text);
}
main();실행은 간단하다.
npm start여기서 핵심은 세 가지다. client는 OpenAI API를 호출하는 객체다. model은 사용할 모델이다. input은 모델에게 보낼 지시와 사용자 요청이다. 응답에서 최종 텍스트는 response.output_text로 꺼내 콘솔에 출력한다.
async/await로 흐름 분리하기
AI API 호출은 네트워크 요청이다. 즉시 결과가 돌아오지 않는다. 그래서 async/await를 사용한다. await는 현재 함수 안에서 응답을 기다리지만, Node.js 서버 전체를 멈추는 방식은 아니다. 이 구조 덕분에 나중에 Express, Fastify, Next.js Route Handler 안으로 쉽게 옮길 수 있다.
실제 서비스에서는 호출 함수를 따로 분리하는 편이 좋다.
async function generateAdvice(topic) {
const response = await client.responses.create({
model: 'gpt-4.1-mini',
input: `주제: ${topic}n초보자가 이해하기 쉽게 조언 3가지를 작성해 줘.`,
max_output_tokens: 300,
});
return response.output_text;
}이제 서버 라우트에서는 generateAdvice(userInput)처럼 호출하고, 결과를 JSON 응답으로 보내면 된다.
에러를 사용자 메시지와 로그로 나누기
API 호출은 실패할 수 있다. 키가 틀릴 수도 있고, 요청 형식이 잘못됐을 수도 있고, 사용량 제한에 걸릴 수도 있다. OpenAI SDK는 실패 응답과 연결 오류를 APIError 계열로 던지며, 상태 코드에 따라 인증 오류, 권한 오류, 요청 제한, 서버 오류 등을 구분할 수 있다1.
function toUserMessage(error) {
if (error instanceof OpenAI.APIError) {
if (error.status === 401) return 'API 인증에 실패했습니다.';
if (error.status === 429) return '요청이 많습니다. 잠시 후 다시 시도해 주세요.';
if (error.status >= 500) return 'AI 서버 응답이 불안정합니다. 잠시 후 다시 시도해 주세요.';
return '요청을 처리할 수 없습니다. 입력값을 확인해 주세요.';
}
return '알 수 없는 오류가 발생했습니다.';
}
try {
const text = await generateAdvice('OpenAI API');
console.log(text);
} catch (error) {
console.error({
name: error.name,
status: error.status,
requestId: error.request_id,
message: error.message,
});
console.log(toUserMessage(error));
}사용자에게는 짧고 안전한 메시지만 보여 준다. 반대로 개발자 로그에는 status, name, request_id, message처럼 문제 추적에 필요한 정보를 남긴다. 단, API Key나 사용자 개인정보는 로그에 남기지 않는다.
재시도와 타임아웃 설정하기
재시도는 편리하지만 조심해야 한다. OpenAI SDK는 기본적으로 연결 오류, 408, 409, 429, 500번대 오류에 대해 짧은 지수 백오프로 재시도하며, 기본 재시도 횟수는 2회다. maxRetries와 timeout으로 전역 또는 요청별 설정도 가능하다1.
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
maxRetries: 2,
timeout: 20_000,
});
const response = await client.responses.create(
{
model: 'gpt-4.1-mini',
input: '짧은 제품 소개 문구를 작성해 줘.',
},
{
maxRetries: 3,
timeout: 10_000,
}
);직접 재시도 로직을 만들 때는 "반복해도 안전한 작업"에만 적용해야 한다. 예를 들어 단순 텍스트 생성은 다시 시도해도 괜찮다. 하지만 결제, 이메일 발송, DB 저장까지 한 함수 안에 묶여 있다면 전체 함수를 반복하면 안 된다. 모델 호출만 재시도하고, 저장은 성공한 결과에 대해 한 번만 실행해야 한다.
이제 우리는 단순한 콘솔 예제를 만들었다. 하지만 구조는 이미 서버 코드에 가깝다. 환경변수로 키를 읽고, 비동기 함수로 모델을 호출하고, 에러를 구분하고, 재시도와 타임아웃을 설정했다. 다음 단계는 이 함수를 웹 화면과 연결하는 것이다.
참고
1Official JavaScript / TypeScript library for the OpenAI API
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.