앱으로 직접 데이터를 가져오거나 저장하고 연결을 맺는 행위는 외부 환경에서는 절대 하면 안되는 일 중 하나이다.
코드를 통해 데이터베이스의 인증 정보를 노출시키는 행위이기 때문이다.
-> 보안 이슈 발생
따라서 리액트 앱 코드 내부에서 데이터베이스에 직접적으로 통신하는 대신, 백엔드 서버 또는 백엔드 API라고 불리는 서로 다른 URL로의 요청을 전송하는 서버와 통신하게 된다.
인증 정보는 백엔드 앱에 저장되어 있고, 백엔드 앱과의 통신은 보안에 관련된 세부 사항이 필요 없으므로 데이터베이스와 안전하게 통신을 주고 받을 수 있다.
API(Application Programming Interface) 종류 : REST API, GraphQL API
axios
: 어떤 자바스크립트 라이브러리를 사용하는가에 관계없이 HTTP 요청 전송을 할 수 있다.Fetch API
: 자바스크립트 내에서 HTTP 요청을 전송하는 내장 메커니즘이 있는데, 이를 Fetch API라고 한다. 브라우저 내장형이고 데이터를 불러오거나 전송할 수 있다. 이 API를 통해 HTTP 요청을 전송하고 응답을 처리할 수 있다.fetch('fetch할 url', { })
두 번째 인자는 선택사항으로, 헤더, 바디, HTTP 요청 메소드의 변경 등의 다양한 선택사항을 지정할 수 있는 자바스크립트 객체를 전달할 수 있다.
fetch()
는 프로미스
라는 객체를 반환하는데, 이 객체는 우리가 잠재적으로 발생할 수 있는 오류나 호출에 대한 응답에 반응할 수 있게 해준다.
HTTP 전송 요청은 비동기 작업으로, 시간이 걸리고 실패할 가능성도 있다.
따라서 최종 함수에 then()
을 추가하여 함수가 응답을 받을 때 호출되도록 한다.
또한 catch()
를 추가하여 잠재적 오류를 처리할 수도 있다.
API는 데이터를 JSON 형식으로 전송한다.
JSON
은 데이터 교환에 사용하는 간단하지만 매우 유명한 형식이다.
파일에서 자바스크립트로의 변환이 매우 쉽다는 이점이 있다.
responsse 객체에는 내장 메서드가 있어서 JSON response의 본문을 코드에서 사용할 수 있는 자바스크립트 객체로 자동 변환해준다. -> json()
, 프로미스를 반환한다.
이렇게 변환된 데이터는 state에 저장해 사용한다.
key 이름을 마음대로 바꾸어 쓸 수도 있다.
import React, { useState } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";
function App() {
const [movies, setMovies] = useState([]);
function fetchMoviesHandler() {
fetch("https://swapi.dev/api/films/")
.then((response) => {
return response.json();
})
.then((data) => {
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
});
}
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
<MoviesList movies={movies} />
</section>
</React.Fragment>
);
}
export default App;
then 대신 async & await
를 이용할 수도 있다.
하는 역할은 같지만 좀 더 읽기 편해진다.
async function fetchMoviesHandler() {
const response = await fetch("https://swapi.dev/api/films/");
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
}
데이터를 fetch하는데 시간이 걸리므로, 로딩 표시를 통해 사용자에게 현재 데이터를 불러오고 있다는 신호를 주어야 한다.
import React, { useState } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";
function App() {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false); // 로딩 상태 관리
async function fetchMoviesHandler() {
setIsLoading(true); // 로딩중
const response = await fetch("https://swapi.dev/api/films/");
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
setIsLoading(false); // 로딩 완료
}
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
{!isLoading && movies.length > 0 && <MoviesList movies={movies} />} // 로딩 완료
{!isLoading && movies.length === 0 && <p>Found no movies</p>} // 로딩완료 되었지만 데이터가 없을 때
{isLoading && <p>Loading...</p>} // 로딩중
</section>
</React.Fragment>
);
}
export default App;
2xx : 성공
4xx : 클라이언트 측 에러
5xx : 서버 측 에러
catch()
, async & await를 이용할 시에는 try-catch()
를 이용해 잠재적인 에러를 포착한다.response.ok()
를 이용해 요청이 성공적인지를 확인한다.throw new Error('에러 메시지')
를 이용해 에러 메시지를 설정한다.import React, { useState } from "react";
import MoviesList from "./components/MoviesList";
import "./App.css";
function App() {
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null); // 에러 상태 관리
async function fetchMoviesHandler() {
setIsLoading(true);
setError(null); // 이전에 받았을 수도 있는 에러 초기화
try {
const response = await fetch("https://swapi.dev/api/films/");
if (!response.ok) {
throw new Error("Something went wrong"); // 에러 메세지 설정
}
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
} catch (error) {
setError(error.message); // 에러 메시지 state에 저장
}
setIsLoading(false);
}
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>
{!isLoading && movies.length > 0 && <MoviesList movies={movies} />}
{!isLoading && movies.length === 0 && !error && <p>Found no movies</p>}
{isLoading && <p>Loading...</p>}
{!isLoading && error && <p>{error}</p>} // 에러 메시지 보여주기
</section>
</React.Fragment>
);
}
export default App;
보여줄 내용을 따로 관리할 수도 있다.
let content = <p>Found no movies</p>;
if (movies.length > 0) {
content = <MoviesList movies={movies} />;
}
if (isLoading) {
content = <p>Loading...</p>;
}
if (error) {
content = <p>{error}</p>;
}
return (
<React.Fragment>
<section>
<button onClick={fetchMoviesHandler}>Fetch Movies</button>
</section>
<section>{content}</section>
</React.Fragment>
);
}
useEffect(() => {
fetchMoviesHandler();
}, []);
useCallback
을 이용한다. const fetchMoviesHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch("https://swapi.dev/api/films/");
if (!response.ok) {
throw new Error("Something went wrong");
}
const data = await response.json();
const transformedMovies = data.results.map((movieData) => {
return {
id: movieData.episode_id,
title: movieData.title,
openingText: movieData.opening_crawl,
releaseDate: movieData.release_date,
};
});
setMovies(transformedMovies);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}, []);
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
데이터를 서버측에 보내기 위한 요청!
firebase의 realtime database
활용.
위에 생긴 url로 fetch한다.
이때, url 뒤에 /movies.json
을 추가하여 데이터베이스에 새로운 노드를 만든다.
(이름은 자유롭게 지으면 된다. user.json, clothes.json 등)
ex. fetch('https://movie-e13c8-default-rtdb.firebaseio.com/movies.json')
fetch는 기본적으로 GET
요청을 보낸다. 따라서 POST
요청을 보내기 위해서는 fetch API에 2번째 인자를 전달하면 된다.
method를 POST로 설정해준다.
method : 'POST'
저장해야 하는 리소스를 만든다.
(이때, 자바스크립트 객체나 배열을 json 형식으로 바꾸기 위해 JSON.stringify( )를 활용한다.)
body : JSON.stringify(movie)
어떤 컨텐츠가 전달되는지를 명확히 하기 위해 header를 추가한다.
headers : { 'Content-Type' : 'application/json' }
비동기 작업이므로 async & await
를 적어준다.
async function addMovieHandler(movie){
const response = await fetch('https://movie-e13c8-default-rtdb.firebaseio.com/movies.json', {
method : 'POST',
body : JSON.stringify(movie),
headers : {
'Content-Type' : 'application/json'
}
})
const data = response.json(); // 올린 데이터 확인
console.log(data); // 올린 데이터 확인
}
버튼을 눌렀을 때 위의 함수가 실행되도록 하고 실제로 실행해보면 아래처럼 movies
노드에 추가한 정보가 생긴 것을 볼 수 있다.
POST한 데이터를 가져와 보면, key가 암호화 되어 있기 때문에 key를 적절히 바꾸어야 한다.
(firebase에서 key를 자동으로 설정해줌.)
const fetchMoviesHandler = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const response = await fetch(
"https://movie-e13c8-default-rtdb.firebaseio.com/movies.json"
);
if (!response.ok) {
throw new Error("Something went wrong!");
}
const data = await response.json();
const loadedMovies = []; // 새로운 배열 만들기
// key를 원하는 대로 설정해서 배열에 담기
for (const key in data) {
loadedMovies.push({
id: key,
title: data[key].title,
openingText: data[key].openingText,
releaseDate: data[key].releaseDate,
});
}
setMovies(loadedMovies);
} catch (error) {
setError(error.message);
}
setIsLoading(false);
}, []);
useEffect(() => {
fetchMoviesHandler();
}, [fetchMoviesHandler]);
HTTP Method
https://velog.io/@yh20studio/CS-Http-Method-%EB%9E%80-GET-POST-PUT-DELETE