diff --git a/apps/web/content/study/clab-26-1/in-person/week11.mdx b/apps/web/content/study/clab-26-1/in-person/week11.mdx index 026fade..dd6a63a 100644 --- a/apps/web/content/study/clab-26-1/in-person/week11.mdx +++ b/apps/web/content/study/clab-26-1/in-person/week11.mdx @@ -136,8 +136,8 @@ branch 작업 → PR 생성 → preview 배포 → 리뷰 → main 머지 → pr 이번 학기에는 HTML/CSS/JS/React의 기본 사용법을 비대면에서 배우고, 대면에서는 - 브라우저 렌더링, AI 시대 개발 방식, 프로젝트 구조, API 계약, 웹 보안, 팀 협업, - 배포와 운영까지 큰 그림을 봤습니다. + 브라우저 렌더링, AI 시대 개발 방식, 프로젝트 구조, API 계약, Next.js와 웹 + 렌더링, 팀 협업, 배포와 운영까지 큰 그림을 봤습니다. diff --git a/apps/web/content/study/clab-26-1/in-person/week6-1.mdx b/apps/web/content/study/clab-26-1/in-person/week6-1.mdx index 898870e..f099739 100644 --- a/apps/web/content/study/clab-26-1/in-person/week6-1.mdx +++ b/apps/web/content/study/clab-26-1/in-person/week6-1.mdx @@ -52,7 +52,7 @@ description: 4~6주차 공백 이후 진행하는 첫 번째 대면입니다. HT - 7주차: 프론트엔드 프로젝트가 복잡해지는 이유 — 구조와 책임 분리 - 8주차: API와 통신 — 프론트엔드와 백엔드의 계약 -- 9주차: 웹 보안 — 브라우저에 있는 코드는 믿으면 안 된다 +- 9주차: Next.js 렌더링 진화와 웹 퍼포먼스 - 10주차: 팀 협업 — Git, PR, 컨벤션, 리뷰 문화 - 11주차: 배포와 운영 — localhost 밖의 세계 diff --git a/apps/web/content/study/clab-26-1/in-person/week8.mdx b/apps/web/content/study/clab-26-1/in-person/week8.mdx index b023c63..ba04f47 100644 --- a/apps/web/content/study/clab-26-1/in-person/week8.mdx +++ b/apps/web/content/study/clab-26-1/in-person/week8.mdx @@ -8,9 +8,10 @@ tags: - HTTP - REST - CORS + - 프록시 - 백엔드 협업 - 데이터 계약 -description: 비동기와 async/await를 배운 뒤, 대면에서는 API를 단순 호출 방법이 아니라 프론트엔드와 백엔드 사이의 계약으로 바라봅니다. HTTP, REST, CORS, 에러 응답, API 문서와 협업 방식을 다룹니다. +description: 비동기와 async/await를 배운 뒤, 대면에서는 API를 단순 호출 방법이 아니라 프론트엔드와 백엔드 사이의 계약으로 바라봅니다. HTTP, REST, CORS, 프록시, API 클라이언트 구조, TanStack Query와 파일 배치까지 연결합니다. --- 1. 이번 주 사전학습과 오늘 대면의 연결 @@ -18,8 +19,14 @@ description: 비동기와 async/await를 배운 뒤, 대면에서는 API를 단 이번 주 비대면에서는 클로저, Promise, async/await를 배웠습니다. 이 문법들이 실제 웹에서 가장 많이 쓰이는 곳은 서버와 통신할 때입니다. - 오늘은 `fetch` 문법을 다시 외우는 시간이 아니라, API를 팀 사이의 계약으로 보는 - 감각을 잡아보겠습니다. + 버튼을 눌렀을 때 서버에 요청을 보내고, 응답을 기다리고, 성공과 실패에 따라 + 화면을 바꾸는 모든 과정이 비동기 위에 있습니다. + + + + 오늘은 `fetch` 문법을 다시 외우는 시간이 아닙니다. API를{" "} + 프론트엔드와 백엔드 사이의 계약으로 바라보고, 그 계약을 + 프로젝트 안에서 어디에 배치해야 유지보수하기 쉬운지까지 연결해보겠습니다. --- @@ -43,6 +50,12 @@ GET /api/studies?page=1&size=20 올지, 빈 목록이면 어떤 응답이 올지 정해야 합니다. + + 그래서 API를 잘 다룬다는 것은 단순히 요청을 보낼 수 있다는 뜻이 아닙니다. + 화면 요구사항을 요청과 응답의 언어로 바꾸고, 백엔드와 합의한 내용을 코드에 + 안정적으로 반영할 수 있다는 뜻입니다. + + --- 3. 좋은 API 협업에서 꼭 맞춰야 하는 것들 @@ -121,41 +134,571 @@ GET /api/studies?page=1&size=20 --- -4. CORS — 브라우저가 끼어드는 보안 규칙 +4. CORS — 서버 에러처럼 보이는 브라우저 규칙 + + + CORS는 처음 보면 백엔드 에러처럼 보이지만, 사실 브라우저의 보안 정책과 + 관련이 있습니다. 브라우저는 기본적으로 같은 출처의 리소스만 자유롭게 읽을 수 + 있게 하는 SOP를 가지고 있고, CORS는 다른 출처의 리소스를 안전하게 공유하기 + 위한 예외 규칙입니다. + + + + 여기서 출처(origin)는 보통 프로토콜, 호스트, 포트의 조합으로 + 봅니다. + + +```text +http://localhost:5173 +http://localhost:8080 +``` + + + 두 주소는 둘 다 localhost지만 포트가 다릅니다. 브라우저 입장에서는 다른 + 출처입니다. 그래서 프론트 개발 서버에서 백엔드 개발 서버로 요청을 보낼 때 + CORS 에러를 자주 만나게 됩니다. + - CORS는 처음 보면 백엔드 에러처럼 보이지만, 사실 브라우저의 보안 정책입니다. - 다른 출처의 API를 마음대로 호출하지 못하게 막는 규칙입니다. + CORS의 핵심은 요청을 막는 주체가 서버가 아니라 브라우저라는 점입니다. 서버는 + 응답을 보냈더라도, 브라우저가 응답 헤더를 보고 "이 출처에 공개해도 된다"는 + 허가를 확인하지 못하면 JavaScript 코드에 응답을 넘기지 않습니다. - 프론트엔드 개발자가 CORS를 알아야 하는 이유는, 이 문제가 생겼을 때 "프론트에서 - 코드 조금 바꾸면 되겠지"가 아니라 서버의 응답 헤더와 배포 환경을 함께 봐야 - 하기 때문입니다. + 더 자세한 흐름은{" "} + + CORS란 무엇인가? (그만 괴롭혀..) + + 글의 Origin, SOP, Preflight 설명을 함께 보면 좋습니다. --- -5. API 문서는 협업 도구다 +5. CORS는 실제로 어떻게 확인되는가 + +5-1. 기본 요청 흐름 - Swagger, Postman, Notion 문서 같은 API 문서는 단순 참고 자료가 아닙니다. - 프론트와 백엔드가 같은 그림을 보고 있는지 확인하는 협업 도구입니다. + 브라우저는 다른 출처로 요청을 보낼 때 요청 헤더에 Origin을 담습니다. 서버는 + 응답 헤더에 어떤 출처를 허용할지 적어 보냅니다. 브라우저는 이 둘을 비교합니다. -좋은 API 문서에는 적어도 다음이 있어야 합니다. +```http +Origin: http://localhost:5173 +Access-Control-Allow-Origin: http://localhost:5173 +``` -- 요청 URL과 메서드 -- 요청 파라미터와 body -- 성공 응답 예시 -- 에러 응답 예시 -- 인증 필요 여부 -- 빈 데이터 예시 -- 필드 설명과 가능한 enum 값 + + 값이 맞으면 브라우저는 응답을 JavaScript에 넘깁니다. 맞지 않으면 네트워크 + 요청 자체가 성공했더라도 프론트엔드 코드에서는 응답을 사용할 수 없습니다. + + +5-2. Preflight 요청 + + + 실제 요청 전에 브라우저가 OPTIONS 요청을 먼저 보내는 경우가 있습니다. 이것을 + Preflight라고 부릅니다. "이 메서드와 이 헤더로 요청해도 되는지"를 미리 물어보는 + 절차입니다. + + +```http +OPTIONS /api/studies +Origin: http://localhost:5173 +Access-Control-Request-Method: POST +Access-Control-Request-Headers: content-type, authorization +``` + + + 서버가 허용하는 origin, method, headers를 응답하면 브라우저가 그제야 실제 + 요청을 보냅니다. 우리가 흔히 쓰는 `Content-Type: application/json`이나 + `Authorization` 헤더는 Preflight를 유발하는 경우가 많습니다. + + +5-3. 인증 정보가 포함된 요청 + + + 쿠키나 인증 헤더처럼 자격 증명이 포함되는 요청은 더 엄격합니다. 프론트에서는 + `credentials: "include"` 또는 axios의 `withCredentials: true` 같은 설정이 + 필요하고, 서버도 `Access-Control-Allow-Credentials: true`를 내려줘야 합니다. + 이때 `Access-Control-Allow-Origin: *`처럼 모든 출처를 허용하는 방식은 사용할 + 수 없습니다. + --- -6. 프론트엔드가 백엔드에게 잘 질문하는 법 +6. CORS 해결 방법 — 서버 설정과 프록시 + +6-1. 정석은 서버가 허용할 출처를 명시하는 것 + + + 가장 정석적인 해결은 백엔드가 실제로 허용할 프론트엔드 출처를 응답 헤더에 + 명시하는 것입니다. + + +```http +Access-Control-Allow-Origin: https://www.example.com +Access-Control-Allow-Methods: GET, POST, PATCH, DELETE +Access-Control-Allow-Headers: Content-Type, Authorization +Access-Control-Allow-Credentials: true +``` + + + 운영 환경에서는 무작정 모든 origin을 열기보다, 실제 서비스 도메인과 개발 + 도메인을 구분해서 관리해야 합니다. CORS는 귀찮은 에러가 아니라 "누가 이 응답을 + 읽을 수 있는가"를 정하는 보안 경계입니다. + + +6-2. 프론트 개발 서버 프록시 + + + 개발 환경에서는 프론트 개발 서버가 백엔드로 요청을 대신 전달하는 프록시를 + 자주 씁니다. 브라우저는 같은 출처인 프론트 개발 서버에 요청한다고 느끼고, + 프론트 개발 서버가 뒤에서 백엔드 서버로 요청을 넘깁니다. + + +```ts +// vite.config.ts +export default defineConfig({ + server: { + proxy: { + "/api": { + target: "http://localhost:8080", + changeOrigin: true, + }, + }, + }, +}); +``` + +```ts +// 브라우저에서는 같은 출처로 요청하는 것처럼 보인다. +await fetch("/api/studies"); +``` + + + 원리는 단순합니다. CORS는 브라우저가 다른 출처의 응답을 JavaScript에 넘길지 + 판단하는 규칙입니다. 그런데 브라우저가 보는 요청 주소가 `/api/studies`처럼 + 현재 프론트 서버와 같은 출처라면, 브라우저 입장에서는 교차 출처 요청이 + 아닙니다. 실제 백엔드 호출은 브라우저 밖에 있는 개발 서버가 대신 수행합니다. + + +프록시 방식의 장점은 다음과 같습니다. + +- 개발 환경에서 CORS 설정 때문에 막히지 않고 빠르게 화면 개발을 진행할 수 있습니다. +- 프론트 코드의 API base URL을 `/api`처럼 단순하게 유지할 수 있습니다. +- 로컬 개발 환경과 운영 환경의 URL 차이를 설정으로 흡수할 수 있습니다. + +단점도 분명합니다. + +- 개발 서버 프록시는 보통 로컬 개발용입니다. 운영 CORS 정책을 대신 설계해주지는 않습니다. +- 프록시 설정이 운영 배포 구조와 다르면 로컬에서는 되는데 운영에서는 깨질 수 있습니다. +- 인증 쿠키, 도메인, SameSite, HTTPS 조건이 얽히면 프록시만으로 문제를 해결할 수 없습니다. + + + 따라서 프록시는 "CORS를 없애는 마법"이 아니라{" "} + 브라우저가 보는 요청 경로를 같은 출처처럼 만들어주는 개발/배포 구조 + 입니다. 운영에서는 Nginx, Next.js Route Handler, BFF, API Gateway 같은 계층이 + 비슷한 역할을 맡을 수 있습니다. + + +--- + +7. HTTP에서 현대 API 연동까지 + + + API 요청 도구를 고를 때 바로 axios, fetch, TanStack Query부터 비교하면 흐름이 + 잘 보이지 않습니다. 먼저 웹 통신이 어떤 문제를 해결하며 발전해왔는지 봐야 + 합니다. + + +7-1. HTTP — 요청과 응답의 기본 약속 + + + HTTP는 클라이언트가 요청하면 서버가 응답하는 규약입니다. 중요한 특징은 + 무상태성입니다. 서버는 기본적으로 이전 요청을 기억하지 + 않습니다. 그래서 로그인 이후의 요청에는 "내가 누구인지"를 알려주는 토큰이나 + 쿠키 같은 인증 정보가 필요합니다. + + +```http +GET /api/users/123 HTTP/1.1 +Host: example.com +Authorization: Bearer access-token +``` + + + API 협업에서 메서드도 약속입니다. 보통 GET은 조회, POST는 생성, PUT/PATCH는 + 수정, DELETE는 삭제에 사용합니다. 이 의미가 맞아야 프론트엔드와 백엔드가 같은 + 그림을 보고 개발할 수 있습니다. + + +7-2. AJAX — 페이지 전체가 아니라 데이터만 바꾸기 + + + 예전 웹은 버튼을 누르거나 링크를 클릭하면 페이지 전체를 다시 받아왔습니다. + AJAX가 등장하면서 페이지를 새로고침하지 않고 필요한 데이터만 받아와 화면의 + 일부만 바꾸는 방식이 가능해졌습니다. + + +```text +AJAX 이전: 요청 → 새 HTML 전체 다운로드 → 페이지 전체 교체 +AJAX 이후: 요청 → JSON 데이터 다운로드 → 필요한 UI만 갱신 +``` + + + 이 변화 덕분에 Gmail, Google Maps처럼 페이지 전환 없이 부드럽게 동작하는 웹 + 서비스가 가능해졌습니다. 대신 비동기 요청이 많아지면서 콜백 지옥, 에러 처리, + 공통 요청 설정 같은 새로운 문제가 생겼습니다. + + +7-3. 콜백에서 Promise, async/await으로 + + + 비동기 요청은 응답이 언제 올지 알 수 없습니다. 처음에는 콜백 함수로 응답을 + 처리했지만, 요청이 이어질수록 코드가 깊게 중첩됐습니다. + + +```js +getUser(userId, function (user) { + getOrders(user.id, function (orders) { + getOrderDetail(orders[0].id, function (orderDetail) { + // 계속 중첩됨 + }); + }); +}); +``` + + + Promise와 async/await은 이 문제를 해결했습니다. 서버 통신 코드를 동기 코드처럼 + 읽을 수 있게 만들었고, 에러 처리도 `try/catch`로 모을 수 있게 됐습니다. + + +```ts +async function loadUserOrderInfo(userId: number) { + try { + const user = await getUser(userId); + const orders = await getOrders(user.id); + const orderDetail = await getOrderDetail(orders[0].id); + return orderDetail; + } catch (error) { + console.error("요청 실패", error); + } +} +``` + +7-4. Fetch API — 브라우저 내장 HTTP 클라이언트 + + + fetch는 Promise 기반의 브라우저 내장 HTTP 클라이언트입니다. 별도 라이브러리 없이 + 사용할 수 있고, XMLHttpRequest보다 문법이 훨씬 직관적입니다. + + +```ts +const response = await fetch("/api/users"); +const data = await response.json(); +``` + +하지만 fetch에도 실무에서 불편한 지점이 있습니다. + +- 404, 500 같은 HTTP 에러가 자동으로 `catch`로 가지 않습니다. +- JSON 변환을 매번 `response.json()`으로 직접 해야 합니다. +- timeout, 공통 헤더, 인터셉터 같은 기능을 직접 만들어야 합니다. +- 요청 취소는 `AbortController`를 알아야 합니다. + +```ts +const response = await fetch("/api/users"); + +if (!response.ok) { + throw new Error("HTTP error"); +} + +const data = await response.json(); +``` + +7-5. Axios — 실무 편의 기능을 모은 HTTP 클라이언트 + + + axios는 fetch의 부족한 부분을 보완하기 위해 많이 사용됩니다. 자동 JSON 변환, + timeout, 에러 처리, 인스턴스, 인터셉터 같은 기능을 제공합니다. 브라우저와 + Node.js에서 비슷한 방식으로 쓸 수 있다는 점도 장점입니다. + + +```ts +const response = await axios.get("/api/users"); +const data = response.data; +``` + + + fetch와 달리 axios는 2xx가 아닌 HTTP 상태 코드를 에러로 처리합니다. 그래서 + `try/catch` 안에서 성공과 실패를 나누기 쉽습니다. + + +```ts +try { + const response = await axios.get("/api/users"); + console.log(response.data); +} catch (error) { + if (axios.isAxiosError(error) && error.response) { + console.log(error.response.status); + console.log(error.response.data); + } +} +``` + +7-6. 인터셉터 — 요청과 응답을 가로채는 공통 처리 + + + 인터셉터는 모든 요청이나 모든 응답 앞뒤에 공통 로직을 끼워 넣는 기능입니다. + 실무에서는 토큰 자동 추가, 401 에러 처리, 공통 에러 변환에 많이 사용합니다. + + +```ts +const apiClient = axios.create({ + baseURL: "/api", + timeout: 10000, + withCredentials: true, +}); + +apiClient.interceptors.request.use((config) => { + // 학습을 위한 단순 예시입니다. + // localStorage에 토큰을 저장하면 XSS 공격에 노출될 수 있습니다. + const token = localStorage.getItem("accessToken"); + + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + + return config; +}); +``` + + + 인터셉터를 쓰면 모든 API 함수에서 토큰을 반복해서 꺼내 붙이지 않아도 됩니다. + 공통 로직은 한 곳에 모이고, 개별 API 함수는 "무슨 요청을 보낼지"에 집중할 수 + 있습니다. + + + + 다만 위 코드는 인터셉터 개념을 설명하기 위한 단순 예시입니다. 실무에서는 + access token을 localStorage에 오래 보관하는 방식을 기본값으로 두면 안 됩니다. + localStorage는 JavaScript로 읽을 수 있기 때문에 XSS가 발생했을 때 토큰이 탈취될 + 수 있습니다. 보안이 중요한 서비스라면 서버가 설정하는 HttpOnly, + Secure, SameSite 쿠키 기반 인증이나 그에 준하는 + 안전한 저장 전략을 함께 검토해야 합니다. + + +7-7. 토큰 인증 — HTTP의 무상태성을 보완하기 + + + HTTP는 이전 요청을 기억하지 않기 때문에, 로그인 이후 요청마다 사용자를 증명할 + 정보가 필요합니다. 이때 access token과 refresh token을 함께 쓰는 구조가 자주 + 등장합니다. + + +```text +로그인 성공 + → Access Token 발급: 짧은 수명, API 요청에 사용 + → Refresh Token 발급: 긴 수명, Access Token 재발급에 사용 +``` + + + 백엔드와 맞춰야 할 약속은 구체적이어야 합니다. 토큰을 어느 헤더에 넣는지, + `Bearer`를 붙이는지, 만료되면 어떤 상태 코드가 오는지, refresh API는 어떤 응답을 + 주는지 확인해야 합니다. + + +```http +Authorization: Bearer {accessToken} +``` + + + 401 응답이 왔을 때 인터셉터에서 토큰을 갱신하고 원래 요청을 다시 보내는 패턴도 + 자주 사용됩니다. 다만 동시에 여러 요청이 401을 받으면 refresh 요청이 중복될 수 + 있으므로, 실무에서는 중복 갱신을 막는 설계도 필요합니다. + + + + Refresh Token은 특히 더 조심해야 합니다. 브라우저 JavaScript가 직접 읽는 저장소에 + 두기보다, 서버가 내려주는 HttpOnly + Secure 쿠키로 보관하는 방식을 많이 + 사용합니다. 이 경우 프론트엔드는 토큰 문자열을 직접 다루지 않고, 브라우저가 + 쿠키를 요청에 함께 보내도록 둡니다. 대신 쿠키 기반 인증에서는 SameSite 설정, + CSRF 대응, CORS credentials 설정을 백엔드와 함께 맞춰야 합니다. + + +7-8. TanStack Query — HTTP 통신이 아니라 서버 상태 관리 + + + 어느 순간부터 프론트엔드 개발자들은 HTTP 요청 자체보다 서버 데이터 상태 관리에 + 더 많은 시간을 쓰고 있다는 걸 알게 됐습니다. 로딩, 에러, 캐싱, 중복 요청 제거, + 백그라운드 갱신, 낙관적 업데이트 같은 문제입니다. + + +```tsx +const [users, setUsers] = useState([]); +const [loading, setLoading] = useState(false); +const [error, setError] = useState(null); + +useEffect(() => { + setLoading(true); + axios + .get("/api/users") + .then((response) => setUsers(response.data)) + .catch((error) => setError(error)) + .finally(() => setLoading(false)); +}, []); +``` + + + TanStack Query는 이런 반복을 줄여주는 서버 상태 관리 도구입니다. axios가 서버에 + 요청을 보내고 응답을 받아오는 역할이라면, TanStack Query는 받아온 데이터를 + 캐시에 저장하고, 언제 새로 가져올지, 어떤 컴포넌트에 전달할지를 관리합니다. + + +```tsx +const { data, isLoading, error } = useQuery({ + queryKey: ["users"], + queryFn: () => apiClient.get("/users").then((res) => res.data), + staleTime: 5 * 60 * 1000, +}); +``` + + + 핵심은 역할 구분입니다. axios는 택배 기사처럼 데이터를 가져오고, + TanStack Query는 창고 관리자처럼 그 데이터를 보관하고 갱신합니다. + + +7-9. Query와 Mutation + + + TanStack Query에서는 데이터를 읽는 작업과 쓰는 작업을 구분합니다. Query는 서버 + 데이터를 읽는 작업이고, Mutation은 생성, 수정, 삭제처럼 서버 데이터를 바꾸는 + 작업입니다. + + +| 구분 | Query | Mutation | +| --- | --- | --- | +| 용도 | 데이터 읽기 | 데이터 생성/수정/삭제 | +| 주로 쓰는 HTTP | GET | POST, PUT, PATCH, DELETE | +| 실행 시점 | 컴포넌트 마운트 시 자동 | `mutate` 호출 시 수동 | +| 캐싱 | 자동 캐싱 | 직접 무효화/갱신 필요 | + +```tsx +const queryClient = useQueryClient(); + +const createUser = useMutation({ + mutationFn: (newUser) => apiClient.post("/users", newUser), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["users"] }); + }, +}); +``` + + + 새 사용자를 만든 뒤 사용자 목록 query를 무효화하면, TanStack Query가 목록을 다시 + 가져옵니다. 직접 `setUsers`로 상태를 여기저기 수정하지 않아도 서버 데이터와 + 화면을 다시 맞출 수 있습니다. + + +--- + +8. 7주차 파일 구조와 API 파일 배치 연결하기 + + + 지난 대면에서 UI, 상태, 서버 데이터, 비즈니스 규칙을 나눠서 봤습니다. API + 관련 파일도 같은 기준으로 배치하면 됩니다. 핵심은 "서버와 통신하는 코드"와 + "화면을 그리는 코드"를 섞지 않는 것입니다. + + +작은 프로젝트에서는 아래처럼 시작할 수 있습니다. + +```text +src/ + api/ + client.ts + studies.ts + pages/ + StudyListPage.tsx + components/ + StudyCard.tsx +``` + + + `client.ts`에는 base URL, credentials, 공통 헤더, 에러 변환 같은 HTTP 공통 + 정책을 둡니다. `studies.ts`에는 스터디 도메인의 API 함수들을 둡니다. 화면은 + `getStudies()` 같은 함수를 호출할 뿐, URL 문자열과 HTTP 세부 설정을 직접 알지 + 않는 편이 좋습니다. + + +```ts +// api/studies.ts +export async function getStudies(params: GetStudiesParams) { + const response = await api.get("studies", { searchParams: params }).json(); + return parseStudiesResponse(response); +} +``` + +프로젝트가 커지면 기능 단위로 가까이 두는 방식도 좋습니다. + +```text +src/ + features/ + studies/ + api/ + studyApi.ts + studyQueries.ts + model/ + studyTypes.ts + studyPolicy.ts + ui/ + StudyCard.tsx + StudyList.tsx + pages/ + StudyListPage.tsx +``` + + + 이 구조에서는 스터디 기능과 관련된 API, query key, 타입, 정책, UI가 한 기능 + 안에 모입니다. 대신 공통 HTTP 클라이언트는 여전히 바깥에 둡니다. + + +```text +src/ + shared/ + api/ + httpClient.ts + apiError.ts + features/ + studies/ + api/ + studyApi.ts + studyQueries.ts +``` + + + `studyApi.ts`는 실제 HTTP 요청 함수, `studyQueries.ts`는 TanStack Query의 query + key와 query option을 담는 식으로 나누면 역할이 선명해집니다. + + +```ts +// features/studies/api/studyQueries.ts +export const studyQueries = { + all: ["studies"] as const, + list: (params: GetStudiesParams) => ({ + queryKey: [...studyQueries.all, "list", params] as const, + queryFn: () => studyApi.getStudies(params), + }), +}; +``` + + + 이렇게 해두면 화면 컴포넌트는 "스터디 목록을 가져온다"는 의도만 드러내고, + HTTP 세부사항과 query key 규칙은 API 계층에 남습니다. + + +--- + +9. 프론트엔드가 백엔드에게 잘 질문하는 법 "API 언제 돼요?"보다 좋은 질문은 구체적입니다. @@ -170,12 +713,36 @@ GET /api/studies?page=1&size=20 개발자는 화면 요구사항을 API 계약 언어로 바꿔 말할 수 있어야 합니다. +실제로는 이런 질문들도 자주 필요합니다. + +- 이 API는 로그인하지 않은 사용자도 호출할 수 있나요? +- 실패할 때 code 값은 어떤 목록 중 하나인가요? +- 빈 목록은 200과 빈 배열인가요, 아니면 404인가요? +- 페이지 번호는 0부터 시작하나요, 1부터 시작하나요? +- CORS 허용 origin에 로컬 개발 주소와 배포 주소가 모두 들어가 있나요? + +--- + +10. 오늘 정리 + + + API는 데이터를 가져오는 기술이면서 동시에 팀 사이의 약속입니다. CORS는 그 + 약속이 브라우저 보안 규칙과 만나는 지점이고, HTTP 클라이언트와 TanStack Query는 + API 코드를 프로젝트 구조 안에 안정적으로 배치하기 위한 도구입니다. + + + + 7주차에서 배운 책임 분리를 떠올리면 오늘 내용도 같은 흐름입니다. UI는 UI답게, + API 함수는 API답게, 서버 상태 관리는 서버 상태 관리답게 두어야 프로젝트가 + 커져도 변경 범위를 좁힐 수 있습니다. + + --- -7. 다음 주 안내 +11. 다음 주 안내 - 다음 대면에서는 웹 보안을 다룹니다. 특히 브라우저에 있는 코드는 사용자가 볼 수 - 있고 조작할 수 있다는 전제에서, 프론트엔드가 어디까지 믿어도 되는지 - 이야기합니다. + 다음 대면에서는 Next.js와 웹 렌더링을 다룹니다. React로 화면을 + 만드는 것에서 한 걸음 더 나아가, 브라우저 렌더링, 서버 렌더링, 정적 생성, + 하이드레이션이 왜 등장했는지 큰 흐름을 봅니다. diff --git a/apps/web/content/study/clab-26-1/week9.mdx b/apps/web/content/study/clab-26-1/week9.mdx index cf1f0d2..9c0e3a7 100644 --- a/apps/web/content/study/clab-26-1/week9.mdx +++ b/apps/web/content/study/clab-26-1/week9.mdx @@ -34,11 +34,11 @@ description: 다음 주부터 React를 시작합니다. React 코드에서 매 - 이번 주 대면에서는 웹 보안을 다룹니다. 비대면에서 배우는 옵셔널 체이닝, - try/catch, 모듈, API 응답 처리 문법은 단순히 React를 편하게 읽기 위한 - 도구이기도 하지만, "외부에서 들어온 값을 어디까지 믿을 수 있는가"를 판단하는 - 기초이기도 합니다. 특히 API 응답은 항상 예상한 형태로 온다고 믿지 말고, - 안전하게 접근하고 실패를 처리하는 습관을 같이 가져가면 좋습니다. + 이번 주 대면에서는 Next.js 렌더링 진화와 웹 퍼포먼스를 다룹니다. 비대면에서 + 배우는 map/filter, 구조 분해, 스프레드, 모듈 문법은 React 컴포넌트를 읽기 위한 + 기초이면서, Next.js에서 서버 컴포넌트와 클라이언트 컴포넌트를 나누고 데이터를 + 화면에 연결할 때도 계속 등장합니다. 문법을 외우기보다, 이 문법들이 데이터를 + UI로 바꾸는 과정에서 어떤 역할을 하는지 같이 보면 좋습니다. ---