Cloudinary - 이미지를 업로드하고 URL로 변환한 값 리턴받기 (매우쉬움)

성택·2022년 5월 27일
6

프로젝트에서 파티를 생성하는 페이지를 맡게 되었고, 사용자가 업로드한 파티의 대표 사진을 서버로 전송해야 하는 니즈가 있었다.

나는 사용자가 이미지를 업로드하면 URL로 변환해줘서 DB에는 사진의 URL만을 저장하는 방법을 생각했고, 원래는 파이어베이스를 활용해 볼 생각이었는데 팀원 중 한 명이 이미지 업로드와 변환 기능은 Cloudinary 라는 서비스가 좋다고 추천을 해주었다.

파이어베이스에도 사진 업로드 기능이 있지만, cloudinary는 사용자가 미리 여러가지 옵션을 설정해놓으면 자동으로 customizing 해준다. (파이어베이스에는 커스터마이징 기능이 없고, 사진의 원본을 그대로 사용해야만 하는 점이 다르다.)

나는 모바일 크기로 구축된 웹 페이지에서의 이미지가 필요했기에 자동으로 픽셀을 조절하여 용량도 줄어드는 cloudinary를 사용하기로 결정했다. 그런데 cloudinary 공식 홈페이지는 한국어가 지원되지 않아서 영어로 되있는 공식 홈페이지를 참고하면서 사용하여 약간의 어려움이 있었다. 그래서 내가 어떻게 사용했는지 그 과정을 글로 기록하여 미래의 나, 혹은 다른 개발자 분이 좀 더 원할하게 cloudinary 서비스를 사용하기를 바라면서 글을 작성하기로 했다.

1. 회원가입 및 커스터마이징 설정

우선 cloudinary 홈페이지에 방문해서 회원가입을 해보자 https://cloudinary.com/

회원가입을 하고 로그인을 하면 화면과 같이 대쉬보드 탭으로 이동하게 되고, 이 페이지에서 내 클라우드 네임 및 API 키를 확인할 수도 있고, 내 사용량 등의 정보도 확인할 수 있다. 일단 커스터마이징 설정을 위해 오른쪽 상단에 톱니바퀴 아이콘을 클릭해 settings 페이지로 이동한다.

settings 페이지로 이동한 후에 두 번째 탭인 upload 로 이동한다.

그 후 스크롤을 내리면 미리 내 사진 업로드 환경 설정을 할 수 있는 Upload presets 메뉴가 있고 여기서 Add upload preset 버튼을 클릭해서 새로운 설정을 등록해야 한다.

처음 해당 페이지로 가면 Signed mode로 되어있는 기본 설정만 등록되어 있는 것을 확인할 수 있는데, Signed mode는 로그인 후 인증이 완료된 사람들만 API를 사용할 수 있게 설정되어 있는 것이고, 우리는 그냥 사진을 업로드하고 URL로 변환받는 서비스만 이용할 것이기 때문에 Unsigned mode로 등록해야 한다. Add upload preset 버튼을 클릭하면 signing mode를 선택할 수 있는데 unsigned를 선택해 주면 된다.

그리고 왼쪽 탭에서 Upload Manipulations 탭으로 이동하면 이미지 저장 포맷이나 변환 설정을 등록할 수 있다.
이미지 변환 설정을 하기 위해서 Incoming Transformation 설정을 등록할 것이다. 화면에서 보이는 Edit 버튼을 클릭하면 픽셀 사이즈나 퀄리티, 투명도 등의 다양한 설정을 할 수 있다.

나는 이미지를 파티 프로필의 대표 사진으로 사용할 것이라 크게는 필요없어서 최대 width, height를 500px 로 제한하는 설정만 적용하고 저장하였다. 그 후 주황색 버튼의 save 버튼을 눌러주면 이제 커스터마이징 설정 등록이 끝났다.

2. REST API 방식의 업로드 기능 사용하기

이제 공식 홈페이지의 DOCS 를 참조하여 사용법을 파악해보자. 나는 이 부분에서 조금 헤맸는데 (어디에 설명이 있는지 잘 못 찾아서) 사실 알고보면 간단하다. 우선 화면 오른쪽 상단에 있는 물음표 버튼을 클릭하고 Documentation 을 클릭하면 공식 DOCS 페이지로 이동할 수 있다.

처음엔 우선 Get started 를 가서 읽어봤는데 cloudinary는 REST API 혹은 SDK 방식으로 이용할 수 있다고 되어있다. 나는 사진을 업로드하고 URL을 받는 단순한 기능만을 사용할 것이었기 때문에 REST API 방식으로 했다. cloudinary의 좀 더 다양한 기능을 사용할 생각이라면 SDK 방식을 써보는 것도 좋겠다.

REST API 방식으로 이미지를 업로드 하는 방법GUIDES 메뉴에서 Media upload - Uploading assets 세션에서 확인할 수 있다.
해당 페이지에서 upload using Cloudinary's REST API 를 클릭하면 바로 REST API를 통한 사진 업로드 방법에 대한 설명 페이지로 넘어갈 수 있다.

셜명을 읽어보면 사진 업로드는 POST 메서드를 사용하고 요청 URL은 다음과 같다.

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

cloud name 은 대쉬보드에서 확인할 수 있는 내 클라우드 이름이고,
resource_type은 image라고 입력해주면 된다.

authenticated requests (=signed mode) 유형과 unauthenticated requests (=unsigned mode) 유형에서 각각 필요한 파라미터들도 설명해 주고 있는데,
우리가 사용할 unauthenticated requests 에서는 fileupload_preset 가 필요하다.

file실제로 업로드할 파일이고, upload_preset은 settings 페이지에서 등록했던 preset 설정의 이름이다.

이제 공식 홈페이지에서 제공하고 있는 예제 코드를 보면서 어떤 식으로 사용하는 건지 알아보자. 예제 코드는 다음과 같다.

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;
      });
  }
});

위의 예제 코드는 form을 이용하여 여러 개의 파일을 업로드하는 예제 코드이다.
쿼리 셀렉터를 이용해서 업로드한 파일을 files 변수에 할당하였고, 반복문을 통해 files 배열의 각각의 요소(각각의 파일)들을 new 연산자로 생성된 FormData 객체append 메서드를 통해 file 프로퍼티의 값으로 추가하였다. 더불어 upload_preset도 직접 입력하는 방식으로 추가했고, fileupload_preset 프로퍼티를 가지고 있는 FormData 객체를 fetch를 이용하여 post 메서드의 body에 넣어 전송하고 그 결과값인 response 값을 출력한다.

이제 위의 예제 코드를 우리가 사용할 방식으로 변형하여 사용해보자.
우선은 file을 매개 변수로 받고, FormData를 객체를 생성한 뒤에 파라미터로 받은 file과 upload_preset를 FormData 속성으로 추가하여 fetch로 전송하는 imageUploader 함수를 만들어보자.

  const imageUploader = async (file) => {
    // FormData 생성
    const data = new FormData();
    
    // FormData 에 file, upload_preset 추가
    data.append("file", file);
    data.append("upload_preset", "내 preset Name");
    
    // .env 파일에 있는 cloudName 가져오기
    const cloudName = import.meta.env.VITE_CLOUD_NAME;
	
    // body에 FormData 객체를 넣어 전송
    const res = await fetch(`https://api.cloudinary.com/v1_1/${cloudName}/upload`, {
      method: "POST",
      body: data,
    });
    
    // fetch로 받아온 값은 .json() 메서드를 이용해야 사용 가능. 프로미스 반환
    return res.json();
  };

위의 코드가 작동하려면 실제 업로드한 사진을 파라미터로 전달하는 로직을 구성해야 한다. 우선 useRef로 사진을 업로드할 input 태그를 연결한다.

// 초기값은 null, input 태그를 연결해야하니 타입은 HTMLInputElement 지정
const inputRef = useRef<HTMLInputElement>(null);

return (
  	// input에 ref 로 inputRef 연결, 타입과 업로드할 파일 제한 설정
	<input ref={inputRef} type="file" accept="image/*">
  )

버튼을 생성해서 버튼을 클릭하면 input이 클릭되는 것과 같은 효과를 넣어준다.


const inputRef = useRef<HTMLInputElement>(null);

// input.current.click = ref로 연결된 요소 클릭 이벤트 실행 
const imageUploadBtn = () => {
	inputRef.current.click();
};

return (
	<input ref={inputRef} type="file" accept="image/*">
  	// 버튼에 input이 클릭되는 효과를 주는 이벤트 연결
	<button onClick={imageUploadBtn}>이미지 업로드</button>
  )

이제 input에 업로드 된 파일을 imageUploader 로 전달하는 onChange 이벤트를 만들어 주면 끝이다.

const inputRef = useRef<HTMLInputElement>(null);

// input.current.click = ref로 연결된 요소 클릭 이벤트 실행 
const imageUploadBtn = () => {
	inputRef.current.click();
};

const fileChange = async (e) => {
	const uploaded = await imageUploader(e.target.files[0]);
	setImage(uploaded.url);
};

return (
	<input ref={inputRef} type="file" accept="image/*" onChange={fileChange}>
  	// 버튼에 input이 클릭되는 효과를 주는 이벤트 연결
	<button onClick={imageUploadBtn}>이미지 업로드</button>
  )

최종적으로 완성된 코드는 다음과 같다.
input에 사진 파일을 업로드하면 반환하는 결과값의 url을 Box에 props으로 전달하여, 바로 업로드된 이미지를 확인할 수 있는 로직이다.

import React, { useRef, useState } from "react";
import styled from "styled-components";

interface Box {
  url: string;
}

const UploadImage = () => {
  const [image, setImage] = useState("");

  const imageUploader = async (file) => {
    const data = new FormData();
    data.append("file", file);
    data.append("upload_preset", "내 preset Name");
    const cloudName = import.meta.env.VITE_CLOUD_NAME;

    const res = await fetch(`https://api.cloudinary.com/v1_1/${cloudName}/upload`, {
      method: "POST",
      body: data,
    });
    return res.json();
  };

  const inputRef = useRef<HTMLInputElement>(null);

  const imageUploadBtn = () => {
    inputRef.current.click();
  };
  const fileChange = async (e) => {
    const uploaded = await imageUploader(e.target.files[0]);
    setImage(uploaded.url);
  };

  return (
    <PhotoUpload>
      {image ? <Box url={image}></Box> : <p>대표 사진을 업로드 해주세요</p>}
      <input ref={inputRef} type="file" accept="image/*" onChange={fileChange} />
      <button onClick={imageUploadBtn}>이미지 업로드</button>
    </PhotoUpload>
  );
};

const PhotoUpload = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 150px;
  input {
    display: none;
  }
  button {
    margin: 10px 0;
    width: 100px;
    height: 30px;
    border-radius: 10px;
    background-color: ${({ theme }) => theme.colors.LIGHT_GREY};
  }
`;
const Box = styled.div<Box>`
  background: center url(${(props) => props.url});
  background-size: contain;
  width: 100px;
  height: 120px;
  padding: 10px;
`;

export default UploadImage;

직접 실행해보면 이런 식으로 작동한다. image state가 빈 문자열이면 대표 사진을 업로드 해주세요라는 p 태그를 출력하고, image 값이 url로 변경되면 Box의 전달된 url로 바로 이미지를 출력해준다.

이미지를 업로드하고 imageUploader 함수의 반환값을 출력해보면 다음과 같다. 여러 가지 값들을 가지고 있는 것을 알 수 있는데, 이 중 url 값으로 image state 값을 변경해 준 것이다.

업로드된 이미지를 cloudinary에서도 확인할 수 있다. 홈페이지의 Media Library에 가면 지금까지 업로드한 이미지들을 확인할 수 있고, 저장 및 삭제를 할 수도 있다.

이렇게 cloudinary를 통해 이미지를 업로드하고 URL을 얻는 방법을 알아보았다. cloudinary는 이미지뿐 아니라 미디어도 업로드할 수 있고 여러가지 커스터마이징과 위젯 설정 등 더 다양한 기능이 있다. 나는 이번엔 확실한 목적이 있었기에 필요한 기능만 가볍게 사용하고 넘어갔지만 다른 기능도 언젠가 사용할 날이 있을 것 같다.

cloudinary 사이트를 이용하시는 다른 분들도 어떤 기능이 있는지 정도는 공식 문서를 통해 직접 알아보시는 것을 추천한다.

혹시 위에서 쓰인 예제 코드들이 실제 프로젝트에서 어떻게 사용되었는지 궁금하시면 해당 프로젝트의 깃허브 링크에서 확인하시면 될 것 같다. https://github.com/team1codadoc/Vegopa/blob/dev/src/pages/CreateParty.tsx

profile
frontend developer

0개의 댓글