TIL. Cloudinary를 사용한 이미지 업로딩(1)

ichbinmin2·2021년 3월 6일
1

React

목록 보기
23/25
post-thumbnail

👩🏻‍💻 Toy Project(Teta Card Maker)의 진행 기록 : Cloudinary을 활용한 이미지 업로더 기능 구현

카드의 정보를 작성하면서, 프로필 사진도 이미지도 업로더 할 수 있는 기능을 추가해볼 것이다. 이때, 다른 서비스(firebase)에 비해서 비교적 이미지 커스터마이징이 쉬운 Cloudinary 서비스를 적극 활용하고자 한다.

Cloudinary가 제공하는 서비스

Cloudinary 서비스는 사용자의 이미지를 업로드 하거나 사용자가 업로드한 이미지를 resizing 하고 Transformation 및 필터링을 적용할 수 있도록 도와주는 서비스다. (firebase에서도 이미지 업로드 서비스를 제공하지만, 원본 자체로 제공하기 때문에 커스텀마이징이 불가능한 단점이 있다.) Cloudinary를 가입하면 개인의 API KEY 주소를 제공하는데 이 주소를 사용해서 Cloudinary 서비스를 이용할 수 있다.

Cloudinary의 개인 페이지 Dashboard의 Settings 페이지로 이동하면 우리가 사용할 때 필요한 설정을 해줄 수 있다. Upload 탭에서 내려보면 Upload presets 설정을 할 수가 있는데,

초록색의 Signed mode는 로그인해서 인증이 완료된 사람들만 사용할 수 있는 API인 것이고, 빨간색이 Unsigned mode는 가입을 하거나 로그인을 하지 않아도 API를 이용할 수 있도록 하는 설정이다. 이러한 설정을 통해서 어떤 방식의 API를 받아올 것인지 커스텀마이징을 할 수가 있다.

만약, 새로운 Upload 설정을 만들고 싶다면 Add upload preset을 클릭한 뒤 원하는 Signing Mode 설정을 하고, 사용자가 이미지를 업로드 했을 때 이미지를 자동으로 커스텀마이징 하고 싶다면 Upload Manipulations 탭에서 커스텀 설정을 해주면 된다.

이미지를 자동으로 커스텀마이징 할 수 있는 Upload Manipulations 탭

나는 Format에서 이미지가 jpg로 변환되도록 변경을 해주었고, Edit Transformation을 통해서 이미지 사이즈나 해상도를 설정하였다.

이용자가 API를 사용해서 이미지를 업로드 하면 Upload presets 에서 설정한 값으로 Media Libray에 추가되는 것을 확인할 수 있다.

Cloudinary 서비스를 이용하는 법(공식 문서 보는 법)

Media Upload Guides를 확인해보면 Rest API 또는 Cloudinary SDK를 제공하고 있다고 되어있다.

Cloudinary에서 이용하는 서비스가 많다면, SDK를 이용하는 것을 고려해볼 수도 있으나 단 한 두가지의 기능만 사용할 예정이라면 Rest API를 사용하면 된다.

가이드를 확인해보면, http 중에서도 POST request를 써야하며 해당하는 아래의 URL을 사용해야 한다고 되어있다.

https://api.cloudinary.com/v1_1/<cloud name>/<resource_type>/upload

POST 요청을 할때는 해당하는 URL과 body 부분을 request로 전송하게 된다. presets 에서 설정한 값 두가지 각각 원하는 body 내용이 다르므로 확인 후 추가한다.

이제 Cloudinary API를 사용하여 image uploading 을 해보자!

Image uploading ⚡️

Dependency Injection를 통해서 API의 보안을 관리하는 중이므로, 이전과 동일하게 service라는 폴더 안에 네트워크 통신을 담을 class 파일.js(순수 자바스크립트 확장명으로 지정함)을 만들고, 이미지를 업로드하는 일만 담당하는 class를 작성했다.

Cloudinary API의 민감한 퍼스널 API나 ID 같은 경우에 보안 상의 문제가 있으므로, API KEY를 .env 파일에 따로 담아놓을 예정이다.

공식 문서에서 보여주는 실제 사용 form

const url = "https://api.cloudinary.com/v1_1/**{개인 Clodinary 이름}**/image/upload";
const form = document.querySelector("form");

form.addEventListener("submit", (e) => {
  e.preventDefault();

  const files = document.querySelector("[type=file]").files;
  const formData = new FormData();

  for (let i = 0; i < files.length; i++) {
    let file = files[i];
    formData.append("file", file);
    formData.append("upload_preset", "**docs_upload_example_us_preset**");

    fetch(url, {
      method: "POST",
      body: formData
    })
      .then((response) => {
        return response.text();
      })
      .then((data) => {
        document.getElementById("data").innerHTML += data;
      });
  }
});
  1. {개인 Clodinary 이름} 은 개인 계정의 Dashboard로 들어가면 주어지는 이름인데 Dashboard 오른쪽 위에서 확인할 수 있으며, 이것을 url 주소 안에 순서대로 넣어주면 되며,

  1. docs_upload_example_us_preset 은 내가 미리 Settings → Upload 탭에서 설정한 Upload presets 에서 주어진 이름을 넣는다.

image_uploader.js

const url = process.env.REACT_APP_CLOUDINARY_API_KEY;
const name = process.env.REACT_APP_CLOUDINARY_PROJECT_NAME;
class ImageUploader {
	// 하나의 기능, 즉 imageUpload만 하는 함수를 지정해주고 file을 인자로 받았다.
  async imageUpload(file) {
		// file을 업로드했다면 서버에 있는 이미지의 url을 전달해줄 것이다.
		// 서버에 업로드하고 업로드가 완료되면 그 결과값을 return 해야하니까 
		// async를 하는 것이 좋을 것이다.
		
		// FormData.append()를 통해서 간단하게 data 파일을 업로드 할 수 있다. MDN 참고*
    const data = new FormData();
    data.append("file", file);
    data.append("upload_preset", `${name}`);

    const result = await fetch(`${url}`, {
      method: "POST",
      body: data,
    });
    return await result.json();
  }
}

최종적으로 API를 받아올 result 값 안(body)에 넣어준 뒤, result를 json으로 변환하여 return 하였다.

이미지 업로더 API를 담은 <ImageUploader/> 를 전달받는 컴포넌트는 image-Input.jsx 이고 이 <ImageInput/> 컴포넌트를 사용하는 곳은 card-add.jsxcard-edit.jsx 이다. 너무 많은 부모-자식 컴포넌트가 있는 경우에 전달해야되는 것이 너무 많기 때문에 여러모로 귀찮다(?!). 그럴 때엔 image_input 이라는 컴포넌트를 외부에서 만든 다음에 전달해나가면 쉬워진다.

index.js

import ImageUploader from "./service/image_uploader";
import ImageInput from "./components/image_input/image_input";

const authService = new AuthService();
const imageUploader = new ImageUploader();**

const FileInput = (props) => (
  <ImageInput {...props} imageUploader={imageUploader} />
);

ReactDOM.render(
  <React.StrictMode>
    <App authService={authService} FileInput={FileInput} />
  </React.StrictMode>,
  document.getElementById("root")
);

new 키워드를 사용해서 ImageUploader를 함수 선언해준 것을 imageUploader 변수에 담은 뒤에 새로운 <FileInput/> 라는 컴포넌트를 생성한다. 이 컴포넌트는 props를 받으면 <ImageInput/> 컴포넌트에 각각 받아온 props는 알아서 전달이 되도록 {...props}로 설정한 뒤, 위에서 변수 설정한 imageUploader 를 같은 이름으로 전달해준다.

그래서 이 <FileInput/> 컴포넌트 자체를 다른 컴포넌트에 전달해줄 것이다.

잘못된 방법 x

**<ImageInput imageUploader={imageUploader} />**

확장성을 고려한 방법 o

**<ImageInput {...props} imageUploader={imageUploader} />**

component의 확장성을 고려해야 하는 이유

만약에 필요한 props들을 여기서 완성해서 컴포넌트 자체를 전달하게 나중에 다양한 props을 전달하고 싶을 때 사용하고자 하는 사람이 컴포넌트를 설정해서 다른 props을 전달할 수 없기 때문에, 가능한 확장성을 고려하여 작성해야 한다.

사용자가 이 <FileInput/> 이라는 컴포넌트를 사용할 때 원하는 props를 전달하면 그 props를 그대로 이 코드에서 전달해주면 된다. 이렇게 전달한다면, 확장성을 높은 컴포넌트가 되고, 필요한 props는 인젝터가 되기 때문에 더욱 확장성이 높아진다.

컴포넌트 props인 경우 대문자로 시작하게끔 전달한다.

<App authService={authService} **FileInput={FileInput}** />

그리고, 최종적으로 해당 컴포넌트 props는 App ⇒ MainBoard ⇒ CardMaker ⇒ CardEditor ⇒ CardEdit / CardAdd 에 각각 전달한다.

card_edit.jsx

const CardEdit = ({ **FileInput**, card, updateCard, deleteCard }) => {
  .
	.
	.
  return (
    <form ref={formRef} className={styles.writeform}>
      .
			.
			.
        **<FileInput />**
	    .
			.
			.
    </form>
  );
};

export default CardEdit;

card_add.jsx

const CardAdd = ({ **FileInput**, onAddCard }) => {
	.
	.
	.
  return (
    <form ref={formRef} className={styles.writeform}>
      .
			.
			.      
        **<FileInput />**
      .
			.
			.
    </form>
  );
};

export default CardAdd;

Dependency Injection의 장점

쓸데없이 많은 서비스를 자식-부모 props를 통해 전달-전달하지 않아도 되며, 추가로 조금 더 많은 서비스가 필요할 때 index.js에서 정의한 컴포넌트 props에서 지정하면 간단해지므로, 확장성과 가독성이 높아진다.


**참조 **
Cloudinary : 공식문서
FormData.append() : MDN 공식문서

profile
N개월차 프론트엔드 개발자, Teta Min

0개의 댓글