저는 교육부 공공데이터 API를 직접 호출했다가, 브라우저에서 CORS와 인증 키 노출 문제를 동시에 맞닥뜨렸습니다. 그래서 Next.js 서버 라우트에 프록시를 만들어 키를 감추고 에러 처리를 한 곳에서 담당하게 했습니다.
.env에 두고, 요청에 serviceKey가 오지 않으면 서버에서 인코딩해 붙입니다.apiUrl을 쿼리로 지정하면 다른 엔드포인트도 재사용할 수 있도록 디코딩 후 전달합니다.요청에서 serviceKey와 apiUrl을 받아 처리합니다. 환경변수의 키는 URL 인코딩이 필요해서 encodeURIComponent로 감쌌습니다.
fetch로 업스트림 API를 호출하고, 응답 텍스트를 그대로 받아둡니다. 성공하면 { success: true, data: text } 형태로 JSON을 반환합니다.
import { NextRequest, NextResponse } from 'next/server';
const USER_AGENT =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)';
export async function GET(request: NextRequest) {
// 1. 쿼리스트링과 환경 변수를 조합해 서비스 키를 결정합니다.
const params = request.nextUrl.searchParams;
const rawServiceKey =
params.get('serviceKey') ?? process.env.UNIVERSITY_DATA_API_KEY ?? '';
const serviceKey = encodeURIComponent(rawServiceKey);
const rawApiUrl = params.get('apiUrl') ?? process.env.UNIVERSITY_DATA_API_URL;
if (!rawApiUrl) {
return NextResponse.json(
{ success: false, message: 'API URL이 설정되지 않았습니다.' },
{ status: 400 },
);
}
// 2. 서비스 키를 붙여 최종 요청 URL을 구성합니다.
params.set('serviceKey', serviceKey);
const url = `${decodeURIComponent(rawApiUrl)}?${params.toString()}`;
const response = await fetch(url, { headers: { 'User-Agent': USER_AGENT } });
const text = await response.text();
// 3. upstream에서 받은 상태를 그대로 반환하면서 body 일부를 로그에 남깁니다.
if (!response.ok) {
return NextResponse.json(
{ success: false, status: response.status, body: text.slice(0, 200) },
{ status: response.status },
);
}
return NextResponse.json({ success: true, data: text });
}
응답이 200이 아니면 { status, message, body }를 담아 같은 상태 코드로 클라이언트에 전달합니다. 네트워크 장애는 500으로 묶어 에러 메시지를 보여줍니다.
apiUrl을 다시 인코딩하면 400이 나와서, decodeURIComponent 후 사용했습니다.이제 클라이언트는 /api/university만 호출하면 되고, 서비스 키가 브라우저에 노출되지 않습니다. 다른 학사 통계 API도 같은 프록시로 쉽게 확장할 수 있었죠. 다음에는 응답을 XML→JSON으로 변환해 클라이언트 코드를 더 단순하게 만들 생각입니다.
여러분은 공공데이터 API를 어떻게 프록시하고 계신가요? 다른 보안 팁이 있다면 댓글로 공유해주세요.