현록

HTTP GET과 POST 차이

웹 개발을 시작하면 GETPOST를 아주 자주 만난다.
목록을 불러올 때는 GET, 회원가입을 할 때는 POST를 쓰는 식이다.

처음에는 “데이터를 URL에 담으면 GET, body에 담으면 POST” 정도로 이해하기 쉽다.
이 설명도 출발점으로는 괜찮다.
하지만 실제 기준은 조금 더 넓다.

핵심은 요청의 의도다.
GET은 리소스를 조회하기 위한 메서드다.
POST는 서버가 요청 본문을 처리해서 어떤 동작을 수행하게 하는 메서드다.

HTTP method의 역할

HTTP 요청에는 method가 있다.
method는 서버에게 이 요청을 어떤 의도로 처리하면 되는지 알려주는 값이다.

GET /posts HTTP/1.1
Host: example.com
POST /posts HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "title": "hello"
}

두 요청은 같은 /posts 경로를 사용할 수도 있다.
하지만 method가 다르므로 의미가 다르다.

GET /posts는 보통 글 목록을 조회한다.
POST /posts는 보통 새 글을 생성한다.

URL만 보고 판단하는 것이 아니라 method와 URL을 함께 봐야 한다.

GET의 기준

GET은 지정한 리소스의 표현을 요청하는 메서드다.
쉽게 말하면 서버에서 데이터를 가져올 때 쓴다.

GET /products HTTP/1.1
Host: example.com

검색 조건이나 페이지 번호처럼 조회 조건이 필요하면 query string을 붙인다.

GET /products?keyword=keyboard&page=2 HTTP/1.1
Host: example.com

브라우저 주소창에서 직접 열 수 있는 URL도 대부분 GET 요청이다.
링크를 클릭하거나 이미지를 불러오는 일도 보통 GET으로 처리된다.

그래서 GET 요청은 URL에 요청 정보가 드러난다.
검색어, 페이지 번호, 정렬 기준처럼 공유해도 되는 조회 조건에 잘 맞는다.

/products?keyword=keyboard&sort=price

반대로 비밀번호나 토큰처럼 노출되면 안 되는 값은 query string에 넣지 않는 편이 좋다.
URL은 브라우저 기록, 서버 로그, 분석 도구, 공유된 링크에 남을 수 있기 때문이다.

POST의 기준

POST는 데이터를 서버로 보내고, 서버가 그 데이터를 처리하게 할 때 쓴다.
대표적으로 회원가입, 로그인, 게시글 작성, 주문 생성, 파일 업로드 같은 작업이다.

POST /signup HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "secret"
}

POST의 데이터는 보통 request body에 담긴다.
body의 형식은 Content-Type 헤더로 알려준다.

Content-Type: application/json

HTML form에서는 application/x-www-form-urlencodedmultipart/form-data도 자주 사용한다.
파일 업로드를 다룰 때는 보통 multipart/form-data가 쓰인다.

<form method="post" action="/profile" enctype="multipart/form-data">
  <input type="file" name="avatar" />
  <button type="submit">저장</button>
</form>

POST는 서버 상태를 바꾸는 작업에 자주 사용된다.
새 리소스를 만들거나, 처리 작업을 시작하거나, 폼 제출 결과를 서버에 반영하는 식이다.

query string과 body

GET은 보통 query string으로 조건을 표현한다.

GET /search?q=css&page=1 HTTP/1.1
Host: example.com

POST는 보통 body에 데이터를 담는다.

POST /search HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "q": "css",
  "page": 1
}

둘 중 무엇을 쓸지는 데이터가 어디에 담기는지만으로 결정하지 않는다.
검색처럼 조회 조건을 담는 요청이라면 GET이 자연스럽다.
링크 공유, 새로고침, 북마크, 캐시와 잘 맞기 때문이다.

반대로 주문 생성처럼 서버 상태를 바꾸는 요청이라면 POST가 자연스럽다.
같은 요청이 다시 전송됐을 때 같은 주문이 중복 생성될 수 있기 때문이다.

즉, query string은 조회 조건에 잘 맞고 body는 처리할 데이터에 잘 맞는다.
하지만 더 중요한 기준은 요청이 읽기인지 변경인지다.

safe의 의미

HTTP에서 safe method는 클라이언트가 서버 상태 변경을 의도하지 않는 메서드다.
GET은 safe method에 속한다.

여기서 safe는 “절대 아무 일도 일어나지 않는다”는 뜻이 아니다.
서버가 접근 로그를 남기거나 통계를 기록할 수는 있다.
중요한 것은 클라이언트가 리소스 변경을 요청하지 않는다는 점이다.

그래서 아래처럼 삭제를 GET으로 만들면 의미가 어긋난다.

GET /posts/1/delete HTTP/1.1
Host: example.com

링크 미리보기, 검색 엔진 크롤러, 브라우저 prefetch 같은 동작이 GET을 자동으로 호출할 수 있다.
GET 요청이 삭제나 결제를 수행한다면 의도하지 않은 변경이 생길 수 있다.

삭제는 보통 DELETE, 생성은 보통 POST, 수정은 보통 PUT이나 PATCH 같은 메서드를 고려한다.
이 글에서는 GETPOST를 중심으로 보지만, safe의 기준은 API 설계에서 꽤 중요하다.

idempotent의 의미

idempotent는 같은 요청을 여러 번 보내도 서버에 의도한 효과가 한 번 보낸 것과 같다는 뜻이다.
GET은 safe method이므로 idempotent로 본다.

GET /posts/1 HTTP/1.1
Host: example.com

같은 글을 여러 번 조회한다고 해서 글이 여러 개 생기지는 않는다.
응답 내용은 시간이 지나며 달라질 수 있지만, 조회 요청 자체가 서버 상태 변경을 의도하지 않는다.

POST는 일반적으로 idempotent가 보장되지 않는다.

POST /orders HTTP/1.1
Host: example.com
Content-Type: application/json

{
  "productId": 10,
  "quantity": 1
}

이 주문 생성 요청을 여러 번 보내면 주문이 여러 개 만들어질 수 있다.
그래서 결제나 주문 같은 작업에서는 중복 제출 방지, idempotency key, 서버 측 중복 처리 같은 장치가 필요할 수 있다.

이 차이는 네트워크 재시도에서도 중요하다.
GET은 실패했을 때 다시 시도하기 비교적 쉽지만, POST는 같은 작업이 중복 실행될 수 있는지 생각해야 한다.

캐시와 북마크

GET은 캐시와 잘 맞는다.
같은 URL이 같은 리소스 표현을 가리킨다면 브라우저나 CDN이 응답을 재사용할 수 있다.

GET /posts/1 HTTP/1.1
Host: example.com

URL에 조회 조건이 드러나므로 링크 공유와 북마크도 쉽다.

https://example.com/search?q=javascript&page=2

검색 결과 페이지나 상품 목록 페이지에는 이 특성이 유용하다.
사용자가 새로고침을 해도 같은 조건으로 다시 조회할 수 있고, 링크를 다른 사람에게 보내기도 쉽다.

POST 응답도 조건에 따라 캐시될 수는 있지만, 일반적인 웹 개발에서는 GET보다 캐시 대상으로 다루기 어렵다.
요청 본문이 URL에 드러나지 않고, 요청 자체가 서버 작업을 의미하는 경우가 많기 때문이다.

조회 화면을 만들 때는 먼저 GET으로 표현할 수 있는지 본다.
그 편이 브라우저와 HTTP의 기본 동작에 잘 맞는다.

fetch 사용 예

브라우저에서 fetch를 사용할 때 GET은 기본 method다.

const response = await fetch('/api/posts?tag=css')
const posts = await response.json()

명시적으로 적어도 된다.

const response = await fetch('/api/posts?tag=css', {
  method: 'GET',
})

POST는 method와 body, Content-Type을 함께 지정하는 경우가 많다.

const response = await fetch('/api/posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    title: 'GET과 POST 차이',
  }),
})

응답 처리와 HTTP 에러 처리는 method와 별개로 챙겨야 한다.
fetch404500 응답을 받았다고 해서 자동으로 catch로 보내지 않는다.
이 부분이 헷갈리면 fetch와 axios 차이를 같이 보면 좋다.

form 사용 예

HTML form은 method 속성으로 GET이나 POST를 지정할 수 있다.
검색 폼은 GET이 잘 맞는다.

<form method="get" action="/search">
  <input name="q" />
  <button type="submit">검색</button>
</form>

이 폼을 제출하면 브라우저는 입력값을 query string으로 만든다.

/search?q=css

회원가입 폼은 POST가 잘 맞는다.

<form method="post" action="/signup">
  <input name="email" type="email" />
  <input name="password" type="password" />
  <button type="submit">가입</button>
</form>

이런 값은 URL에 남기기보다 request body로 보내는 편이 낫다.
다만 POST를 쓴다고 해서 자동으로 안전해지는 것은 아니다.
민감한 값을 다루려면 HTTPS, 서버 검증, 쿠키 설정, CSRF 방어 같은 보안 기준도 함께 봐야 한다.

POST와 보안의 오해

POST는 데이터가 URL에 보이지 않는다는 장점이 있다.
하지만 그것만으로 보안이 해결되지는 않는다.

브라우저 개발자 도구의 Network 탭에서는 POST body도 확인할 수 있다.
네트워크 구간이 암호화되지 않았다면 요청 내용이 노출될 수 있다.

민감한 데이터를 보낼 때 중요한 기준은 GET이냐 POST냐보다 HTTPS 사용 여부와 서버의 처리 방식이다.
비밀번호를 query string에 넣지 않는 것은 기본이고, 전송 자체도 HTTPS로 보호해야 한다.

또 로그인이나 결제처럼 상태가 바뀌는 요청은 CSRF 같은 공격도 고려해야 한다.
method 선택은 첫 단추일 뿐이고, 보안 설계 전체를 대신하지 않는다.

선택 기준

데이터를 조회하고 URL로 공유할 수 있어야 한다면 GET을 쓴다.
검색, 목록 조회, 상세 조회, 필터링, 페이지 이동이 여기에 가깝다.

서버 상태를 만들거나 바꾸는 작업이라면 POST를 쓴다.
회원가입, 로그인, 글 작성, 댓글 등록, 주문 생성, 파일 업로드가 여기에 가깝다.

같은 요청이 여러 번 전송될 수 있는 상황도 생각한다.
GET은 재시도와 캐시에 잘 맞는다.
POST는 중복 실행이 문제가 될 수 있으므로 서버에서 중복 처리를 고려해야 한다.

정리하면 기준은 단순하다.
읽기는 GET이다.
처리와 생성은 POST다.
URL에 남아도 되는 조회 조건은 query string에 두고, 서버가 처리해야 하는 데이터는 body에 둔다.

참고 자료

관련 포스트
npm outdated와 npm update 차이 thumbnail
npm outdated와 npm update 차이
npm outdated와 npm update의 차이를 초보자 기준으로 정리합니다. Current, Wanted, Latest의 의미와 semver 범위 안에서 업데이트되는 방식, package-lock.json 변화까지 함께 봅니다.
npm install -g와 npx 차이 thumbnail
npm install -g와 npx 차이
npm install -g와 npx의 차이를 초보자 기준으로 정리합니다. 전역 설치, 로컬 설치, 일회성 실행, 프로젝트 scripts에 넣는 기준까지 함께 봅니다.
package.json에서 ^와 ~ 차이 thumbnail
package.json에서 ^와 ~ 차이
package.json 의존성 버전 앞에 붙는 ^와 ~의 차이를 초보자 기준으로 정리합니다. semantic versioning, 버전 범위, 0.x 예외, package-lock.json과의 관계까지 함께 봅니다.
npx와 npm exec 차이 thumbnail
npx와 npm exec 차이
npx와 npm exec가 어떤 명령어인지 초보자 기준으로 정리합니다. 로컬 패키지 실행, 원격 패키지 임시 실행, --package 옵션, -- 인자 전달 차이까지 함께 봅니다.
package.json scripts와 npm run 정리 thumbnail
package.json scripts와 npm run 정리
package.json의 scripts와 npm run 명령어를 초보자 기준으로 정리합니다. npm run dev, npm start, npm test, node_modules/.bin, -- 인자 전달 방식까지 함께 봅니다.
dependencies와 devDependencies 차이 thumbnail
dependencies와 devDependencies 차이
package.json의 dependencies와 devDependencies 차이를 초보자 기준으로 정리합니다. npm install과 npm install -D, 배포 환경 설치, package-lock.json과의 관계까지 함께 봅니다.
npm ci란? thumbnail
npm ci란?
npm ci는 CI, 테스트, 배포처럼 같은 의존성 트리를 반복해서 설치해야 하는 환경에 어울리는 clean install 명령어입니다. package-lock.json 조건, npm install과의 차이, .npmrc 플래그 주의점을 정리합니다.
.npmrc 파일이란? thumbnail
.npmrc 파일이란?
storybook을 사용해보려고 하다 마주한 이슈의 해결법을 알아보다가 등장한 .npmrc라는 파일에 대해 공부해보았다. .npmrc 파일이란? .npmrc 파일은 npm에 대한 config 파일이다. (npm에 대한 rc 파일) 프로젝트별 registry, install 옵션, 인증 토큰처럼 npm CLI가 읽는 설정을 관리할 때 사용한다.
스토리북이란? thumbnail
스토리북이란?
Storybook은 UI 컴포넌트를 독립적으로 개발하고, 문서화하고, 테스트하기 위한 프론트엔드 워크샵입니다. Storybook 10.4 기준 설치 흐름과 stories, 문서화, 테스트 활용 방식을 정리합니다.
TTV와 TTI란? thumbnail
TTV와 TTI란?
TTV는 Time to View이며, 사용자가 화면을 보는 시점을 의미한다. TTI는 Time to Interact이며, 사용자가 웹에서 특정 동작을 수행할 수 있는 시점을 의미한다.
Maria DB 외부 접속 설정하기 thumbnail
Maria DB 외부 접속 설정하기
안녕하세요. 오늘은 Maria DB 초기 세팅 시, 외부에서 접속이 안될 때 매뉴얼을 작성해보겠습니다. dotenv 패키지를 통해서 환경변수로 관리한다면, 별도의 추가 작업을 할 일이 없으실 겁니다.
package-lock.json 파일의 역할 thumbnail
package-lock.json 파일의 역할
안녕하세요. 오늘은 node 환경의 개발자라면 한번쯤 궁금했을만한 package-lock.json의 역할에 대해 알아보겠습니다. 우리는 node 환경에서 개발을 할 때 다양한 패키지들을 설치하여 활용하곤 합니다. 우리가 설치하는 패키지 또한 다른 npm 패키지를 활용하여 만든 패키지들이고 이들 또한 설치를 하게 됩니다. 이렇게 직간접적으로 설치된 패키지들은 대부분 호환성을 "^1.1.5"와 같이 표현하여, 범위로 지정해두고 있습니다.
Linux 환경 배포 자동화 체험해보기 thumbnail
Linux 환경 배포 자동화 체험해보기
안녕하세요. 요즘 포트폴리오를 만들면서 서버 상에 자주 반영할 일이 생겼는데, 매번 명령어들을 타이핑하는 것이 비효율적이라 생각이 들어 배포 자동화를 생각해보게 되었습니다. 현재 레벨에서는 단순히 명령어들만 단축시켜도 효율적이라 생각이 들어 간단한 쉘 스크립트만 작성하였습니다. 정말 간단하니 여러분도 도전해보시길 바랍니다.
협업 필수품. Prettier thumbnail
협업 필수품. Prettier
안녕하세요. 오늘은 Prettier이라는 도구에 대해 알려드리고자 합니다. 개발자는 각자의 코딩스타일이 존재합니다. 그러다보니 같은 프로젝트에서도 작성하는 소스마다 스타일이 제각기 다르기 일쑤입니다. 그럴 때 도입하면 좋은 것이 Prettier입니다. 프로젝트 root 폴더에 .prettierrc 라는 파일을 생성한 뒤, 위 예시와 같이 원하는 옵션을 JSON 형식으로 작성해주면 됩니다.