웹개발 API 설계는 응답 규칙부터 통일해야 실패하지 않는다
화면에서는 분명히 저장 완료 메시지가 떴는데 새로고침하자 데이터가 사라지고, 같은 오류가 어떤 API에서는 400으로, 다른 API에서는 200으로 돌아옵니다. 이런 문제는 코딩 실력보다 API 응답 규칙의 부재에서 시작되는 경우가 많습니다. 프론트엔드와 백엔드가 서로 다른 추측을 하기 시작하면 작은 기능 하나도 수정 범위가 눈덩이처럼 커집니다.
API는 데이터를 주고받는 통로이면서 팀 사이의 약속입니다. 코딩과 프로그래밍의 기본 개념을 짚고 싶다면 코딩 용어 설명과 프로그래밍의 개념을 함께 참고하면 좋습니다. 중요한 것은 문법을 많이 아는 것보다 예측 가능한 규칙을 코드에 반영하는 일입니다.
성공과 실패를 HTTP 200 하나로 감추지 마세요
실패 사례: 응답 본문을 읽어야만 오류를 알 수 있었습니다
한 쇼핑몰 프로젝트에서는 주문 재고가 부족해도 서버가 항상 200 OK를 반환했습니다. 응답 본문의 success: false를 확인해야 실패를 알 수 있었지만, 새로 투입된 개발자는 상태 코드만 보고 결제 완료 화면으로 이동시켰습니다. 사용자는 주문이 접수됐다고 믿었고 운영팀은 존재하지 않는 주문을 찾느라 시간을 썼습니다.
HTTP 상태 코드는 요청 처리 결과의 첫 번째 신호여야 합니다. 정상 조회와 생성은 2xx, 잘못된 입력은 400, 인증 정보가 없거나 유효하지 않으면 401, 권한이 부족하면 403, 자원이 없으면 404처럼 의미를 구분해야 합니다. 모든 상황을 무조건 한 코드에 담으면 캐시, 모니터링, 프론트엔드 예외 처리까지 동시에 부정확해집니다.
- 성공 여부를 본문의 불리언 값 하나에만 맡기지 않습니다.
- 서버 내부 오류를 사용자 입력 오류인 400으로 위장하지 않습니다.
- 404와 빈 검색 결과를 같은 상황으로 처리하지 않습니다.
- 상태 코드를 정했다면 모든 엔드포인트에서 같은 기준을 적용합니다.
상태 코드는 장식이 아닙니다. 클라이언트가 응답 본문을 열기 전에 실행 경로를 결정하게 해 주는 가장 빠른 계약입니다.
엔드포인트마다 다른 JSON 모양을 만들지 마세요
실패 사례: 같은 사용자 데이터에 세 개의 이름이 붙었습니다
회원 조회 API는 userName, 주문 API는 username, 관리자 API는 user_name을 반환하는 상황을 생각해 보세요. 각각만 보면 사소하지만 프론트엔드는 화면마다 변환 함수를 추가하게 됩니다. 이후 필드 하나를 변경할 때 타입, 테스트, 캐시 키, 분석 이벤트를 모두 찾아 고쳐야 하는 숨은 비용이 생깁니다.
응답의 최상위 구조도 흔히 무너지는 지점입니다. 어떤 API는 데이터를 바로 반환하고 다른 API는 data 안에 넣으며, 목록 정보는 items와 results를 번갈아 사용하면 공통 요청 모듈을 만들기 어렵습니다. 필드 표기법과 성공·실패 봉투 구조를 먼저 고정하면 반복 코딩을 줄이고 오류를 더 빨리 발견할 수 있습니다.
- 필드명은 camelCase 또는 snake_case 중 하나만 선택합니다.
- 목록 응답은 items, page, pageSize, total처럼 공통 구조를 정합니다.
- 단건 응답에서 불필요한 중첩 깊이를 계속 늘리지 않습니다.
- null, 빈 문자열, 빈 배열의 의미를 필드별로 문서화합니다.
- 날짜와 시간은 시간대 정보가 포함된 일관된 문자열 형식을 사용합니다.
예를 들어 데이터가 없는 값을 API마다 null, 빈 객체, 필드 생략으로 다르게 표현하면 클라이언트는 세 가지 방어 코드를 작성해야 합니다. 처음부터 “값을 모르면 null, 목록이 비면 빈 배열, 계약에 없는 필드는 보내지 않음”처럼 기준을 합의해 두면 변경 영향이 눈에 보이게 줄어듭니다.
오류 메시지를 사람용 문장으로만 보내지 마세요
실패 사례: 문구 수정이 앱 기능을 망가뜨렸습니다
로그인 API가 “비밀번호가 올바르지 않습니다”라는 문장을 반환하고, 앱이 그 문장을 직접 비교해 비밀번호 재설정 버튼을 노출했다고 가정해 보겠습니다. 운영팀 요청으로 문구를 “계정 정보를 확인해 주세요”로 바꾸는 순간 버튼이 사라집니다. 번역을 추가하거나 띄어쓰기만 고쳐도 같은 장애가 발생합니다.
기계가 판단할 값과 사람이 읽을 값은 분리해야 합니다. 고정된 오류 코드는 클라이언트의 분기 처리에 사용하고, message는 화면 표시나 개발자 이해를 돕는 용도로 사용합니다. 입력 오류라면 어느 필드가 어떤 규칙을 위반했는지 details에 구조화해 전달해야 사용자가 한 번에 수정할 수 있습니다.
- code: EMAIL_ALREADY_EXISTS처럼 변경이 드문 식별자를 제공합니다.
- message: 사용자가 이해할 수 있는 짧은 설명을 제공합니다.
- details: 필드명, 검증 규칙, 허용 범위 등 해결 단서를 담습니다.
- requestId: 로그에서 같은 요청을 추적할 수 있는 값을 제공합니다.
다만 내부 SQL 문장, 서버 경로, 스택 추적, 인증 토큰을 오류 응답에 그대로 노출해서는 안 됩니다. 개발 환경에서는 상세 로그를 서버에 남기고 운영 응답에는 필요한 정보만 전달하세요. 사용자는 해결 방법을 얻고, 개발자는 requestId로 내부 기록을 찾는 구조가 안전하고 실용적입니다.
클라이언트 입력을 믿고 데이터 모델에 바로 넣지 마세요
실패 사례: 예상하지 못한 필드까지 저장됐습니다
회원 정보 수정 요청 전체를 데이터베이스 모델에 그대로 전달하는 코드는 짧고 편해 보입니다. 하지만 공격자가 role이나 point 같은 필드를 요청에 추가했을 때 서버가 이를 걸러내지 못하면 권한과 자산 정보가 바뀔 수 있습니다. 화면에서 해당 입력란을 숨겼다는 사실은 서버 보안과 아무 관계가 없습니다.
서버는 허용한 필드만 명시적으로 선택하고 타입, 길이, 범위, 형식을 검증해야 합니다. 이메일 형식 검증만 하고 문자열 길이를 제한하지 않거나, 숫자 타입만 확인하고 음수를 허용하는 것도 흔한 실패입니다. URL, 배열, 중첩 객체처럼 구조가 복잡한 값은 깊이와 항목 수까지 제한해야 과도한 처리 비용을 예방할 수 있습니다.
- 요청 DTO나 스키마에 정의되지 않은 필드는 거부하거나 제거합니다.
- 페이지 크기, 문자열 길이, 배열 항목 수에 상한을 둡니다.
- 가격과 권한처럼 중요한 값은 클라이언트가 아닌 서버에서 결정합니다.
- 검증과 데이터 정규화를 구분하고 원본 의미를 임의로 바꾸지 않습니다.
- 파일 업로드는 확장자뿐 아니라 크기와 실제 콘텐츠 유형도 확인합니다.
프론트엔드 검증은 사용자 편의를 위한 것이고, 백엔드 검증은 시스템 신뢰성을 위한 것입니다. 둘 중 하나로 다른 하나를 대신할 수 없습니다.
검증 오류를 한 건씩만 반환하면 사용자는 제출과 수정을 반복해야 합니다. 서로 독립적인 필드 오류는 가능한 범위에서 함께 알려 주되, 계정 존재 여부처럼 악용될 수 있는 정보는 노출 수준을 별도로 판단해야 합니다. 자세한 프로그래밍 관련 개념을 살펴보면서 입력, 처리, 출력의 경계를 구분해 보는 것도 설계에 도움이 됩니다.
페이지네이션과 재시도 규칙을 나중으로 미루지 마세요
실패 사례: 데이터가 늘자 첫 화면이 멈췄습니다
개발 초기에는 게시글이 30개뿐이라 전체 목록 반환이 빨라 보입니다. 서비스가 성장해 데이터가 수만 건이 되면 응답 크기와 데이터베이스 조회 시간이 함께 늘고, 모바일 환경에서는 화면이 뜨기 전부터 사용자가 이탈합니다. 그제야 페이지네이션을 추가하면 기존 앱과 응답 형식이 달라져 호환성 문제까지 생깁니다.
목록 API는 처음부터 페이지 크기 상한과 정렬 기준을 가져야 합니다. 단순 관리 화면에는 페이지 번호 방식이 이해하기 쉽지만, 새 데이터가 계속 추가되는 피드에는 커서 방식이 중복과 누락을 줄이는 데 유리합니다. 어느 방식을 선택하든 정렬 기준이 고정되지 않으면 다음 요청의 결과가 흔들릴 수 있다는 점을 기억해야 합니다.
- 기본 pageSize와 허용 가능한 최대값을 문서에 적습니다.
- 동일한 정렬 값이 있을 때 사용할 보조 정렬 키를 지정합니다.
- 커서는 클라이언트가 내부 값을 추측하거나 수정하지 않게 설계합니다.
- 타임아웃과 5xx 오류만 제한적으로 재시도하고 4xx는 무작정 반복하지 않습니다.
- 재시도 간격에는 점진적 지연과 무작위 편차를 적용해 요청 집중을 줄입니다.
특히 결제나 주문 생성처럼 상태를 바꾸는 요청은 재시도가 중복 생성을 일으킬 수 있습니다. 클라이언트가 고유한 멱등성 키를 보내고 서버가 처리 결과를 기억하도록 설계하면 네트워크 단절 뒤 같은 요청이 반복돼도 결과를 통제할 수 있습니다. “버튼을 한 번만 누르게 만들기”는 보조 장치일 뿐 서버의 중복 방지 규칙을 대신하지 못합니다.
지금 한 엔드포인트의 실패 계약을 직접 적어보세요
30분 안에 끝내는 API 계약 점검
거대한 문서화 프로젝트부터 시작할 필요는 없습니다. 지금 가장 자주 수정되는 로그인, 주문 생성, 게시글 작성 API 중 하나를 고르세요. 요청 예시 하나와 성공 응답 하나, 그리고 실제로 발생할 수 있는 실패 응답 세 개를 나란히 적으면 숨어 있던 불일치가 빠르게 드러납니다.
그다음 프론트엔드 개발자 관점에서 각 응답만 보고 다음 행동을 결정할 수 있는지 확인합니다. 다시 로그인해야 하는지, 입력란을 강조해야 하는지, 잠시 뒤 재시도해야 하는지가 모호하다면 상태 코드나 오류 코드가 부족한 것입니다. 코드를 작성하기 전에 실패 경로를 설명할 수 있어야 구현과 테스트가 같은 목표를 바라보게 됩니다.
- 대상 엔드포인트 하나를 선택하고 담당자 이름을 적습니다.
- 허용 필드, 필수 필드, 길이와 범위를 한 줄씩 기록합니다.
- 성공 상태 코드와 응답 JSON 예시를 작성합니다.
- 검증 실패, 인증 실패, 서버 실패의 코드와 JSON을 각각 작성합니다.
- requestId, 로그 위치, 재시도 가능 여부를 명시합니다.
- 작성한 예시를 자동화 테스트의 기대값으로 옮깁니다.
당장 할 행동은 간단합니다. 프로젝트에서 가장 최근에 수정한 API 하나를 열고 200 외에 발생 가능한 상태 코드가 테스트에 존재하는지 확인하세요. 없다면 오늘 검증 실패 테스트 한 건을 추가하고, 고정된 오류 코드와 requestId가 반환되도록 계약을 작성해 보세요. 이 작은 변경이 다음 장애에서 원인을 찾는 시간을 크게 줄이는 출발점이 됩니다.

- 이전글코딩 강의 결제 전 무엇을 확인해야 후회하지 않을까? 26.08.14
- 다음글웹개발 속도를 높이는 브라우저 개발자 도구 숨은 활용법 26.08.12
등록된 댓글이 없습니다.
