오프라인 코드캠프 5일차 TIL

전은평·2023년 3월 21일
0

TIL

목록 보기
5/16

apollo

: graphql-API를 작성할 때 이용하는 프로그램!
: rest-API 작성할 때 expressswagger를 이용했는데 이와 동일한 역할을 수행하는 프로그램이라 생각하면 이해가 쉽다


apollos를 통해 API 만들기 전에 프론트엔드 개발자가 HTML에서 백엔드 서버로 API를 요청할 때 사용하는 axios에 대해 먼저 알아보자!

axios를 이용하는 방법에는 다양한 방법이 있는데 아래의 주소를 참조하자

공식사이트
: https://axios-http.com/kr/docs/intro

이번 강의에서는 CDN(Contents Delevery Network)을 사용하는 방법으로 axios 프로그램을 이용해보았다.
아래의 사진에서 보이는 것처럼 script태그를 이용해 주소를 기입하면 된다. 아래의 예시에는 인증하기 버튼을 클릭하면 해당 프론트엔드 서버로부터 받아온 고객의 전화번호를 백엔드서버의 해당 API로 등록 요청하는 과정이다.

axios.post()의 첫번째 매개변수로는 해당 API 주소를 받고, 두번째 매개변수로는 등록할 데이터 즉 req에 해당하는 내용을 받으면 된다.

qqq는 임의로 선언한 내용이지만 백엔드 서버에서도 qqq로 정의되어 있기에 정보를 등록하는데 있어 문제될 건 없다. 여기서 .then()을 이용해서 정보통신을 다 기다린 후 응답값을 어떻게 이용할 것인지 정의하면 된다. 여기서 응답값은 인증완료! 니깐 인증상태를 인증완료로 바꾸어 주는 형태로 활용되었다고 보면 된다.

then()은 promise 객체의 응답을 기다려주는 매서든데 처음에 이해하는데 꽤 애를 먹었지만, 실습을 직접 하면서 이러한 로직으로 활용된다는 것을 점점 몸소(?) 이해하고 있는 중이다 💆🏻‍♂️

여기서 또한 중요한 개념 하나를 배웠다. 위 사진에서 노란색으로 줄 쳐진 부분을 볼 수 있을텐데, 이부분을 생략한다면 API요청 및 응답이 이루어지지 않고 아래와 같은 에러를 직면할 것이다.

이를 이해하기 위해 바로 'CORS' 의 개념에 대해 알아보고자 한다.


cors란 ?

: 서로 다른 출처(origin)를 가진 주소로 요청이 들어왔을 때 발생할 수 있는 에러

: 출처(origin)란, port번호까지 포함한 url을 의미
예를 들어 이러한 url(http://localhost:3000/tokens/phone)에서 originhttp://localhost:3000이다

SOP 정책
: same origin(원본,출처) policy(규칙)
: 이는 간단하게 말해 주소가 같은 곳에서 요청되고 있냐?라는 의미이다. 즉 같은 주소에서만 API 요청 가능함(엄격한 편)
: 프론트엔드랑 백앤드랑 주소가 같은 곳에서 요청이 오고 있는지를 확인하는 것
: 네이버(프론트)->네이버(백) / 다음(프론트)->다음(백)

CORS의 발생배경 및 취지

: 타 주소에서도 API 받아와서 쓸 수 있게 하자
즉, 자유도를 풀어주자 이런 취지에서 나온 것이 CORS이다

: CORS는 'Cross Origin Resource Sharing' 의 줄임말로 앞서 문제라고 표현은 했지만 사실 나쁜 것은 아님.
: 요청지가 크로스 되더라도 자료를 공유하자

: CORS가 좋은 취지로 만들어진 것 확실하지만 누구한테나 막 줄 수 없기 때문에 백엔드 컴퓨터서버에서 CORS 허용 규칙을 정함
: 허용 규칙이란 백엔드 서버에서 API 요청할 수있는 주소를 등록함
: 위의 사진을 보면 백엔드 서버 js파일에 요청할 수 있는 주소를 따로 정해놓진 않았다. 이러한 경우 누구나 요청가능하다는 의미이다

app.use(cors())

만약 제한을 두고 싶다면 아래와 같이 하면 된다.

app.use(cors({origin:["www.naver.com","www.daum.net"]}))

CORS의 특징

API요청은 사실상 두번의 과정을 통해 이루어진다.

첫번째 요청은 preflight라고 해서 API요청이 가능한지, 가능하면 어느 범위(get,post..)까지 가능한지를 먼저 요청하는 사전 요청단계이다.

만약 CORS를 통해 허용되어 있지 않은 상태이면 해당 프론트엔드 '브라우저'에 의해 차단되어 두번째로 진짜 API요청을 하지 않고, 허용 상태이면 다시 API요청을 보내는데 이것이 진짜 요청하는 것이라고 보면 될 것 같다!

하지만 프론트 쪽 브라우저에 의해서 차단이 되다 보니깐 꼼수 쓸 수 있음!

예를 들면 네이버 프론트에서 네이버 백엔드로 API요청 보내고 네이버 백엔드에서 다음 백엔드로 API 요청해서 받을 수 있는다고 가정하자. 이렇게 우회해서 대신 처리해 주는 서버를 proxy 서버라고 한다. 왜냐하면 백엔드 API는 콜스 안걸리기 때문에

(+) 브라우저에서 금지하는거니깐 스마트폰상에서도 가능한지?
: 앱에서 요청하는 경우에도 우회접속 가능
(그러나 휴대폰 브라우저로 하면 차단 ON!)

결과적으로 봤을 때 CORS는 브라우저를 보호하기 위해 만들어진 것이다!!!!

CSRF (cross site request forgery : 크로스 사이트 요청 위조) 와 같은 상황으로부터 보호해주는 역할.

브라우저에는 변수 외에도 쿠키, 로컬/세션 스토리지와 같은 자체적인 저장공간 있다. 이 공간에는 프론트 서버로부터 로그인 API요청을 하고 나면 백엔드 서버로부터 로그인 증표(?)와 같은 정보가 쿠키의 형태로 저장된다. 한번 저장된 이후로는 API요청을 할 때마다 쿠키가 자동으로 같이 따라 들어가게 된다고 한다.만약 나쁜 마음을 먹은 해커가 기존 사이트와 육안으로는 동일한 사이트를 만들어서 해당 백엔드 서버로 API요청을 하게 할 수도 있다. 그렇게 되면 클라이언트가 원치 않던 요청을 자동으로 로그인 증표 인증을 통해 API 작동이 이루어질 수도 있는데 이러한 상황을 막기 위해 CORS가 작동하는 것이다. CORS가 정상 작동된다면 해당 브라우저 상에서 차단이 되기 때문에 이러한 상황을 사전에 차단가능한 것이다.

추가적으로 설명하자면 위에서 말한 로그인 증표(쿠키)는 브라우저 전체에 저장이 되는 것이고 쿠키는 쿠키마다 발급한 (백엔드)도메인 주소가 저장된다. 그렇기 때문에 우회해서 들어가는건 해당 백엔드가 일치하지 않으면 따라 가지 않음! 쿠키에 저장된 도메인 주소와 백엔드 컴퓨터의 주소가 일치해야 따라가는 것임

CORS 설치법 및 사용법

  1. 해당 터미널로 이동 후 yarn add cors 입력
  2. 백엔드 서버 js파일에 아래 코드 입력
import cors from 'cors'
const app = express() // express이용한 것이라 가정
app.use(cors({origin:요청 허용할 주소}))

// 만약 apollo 서버를 이용중이라면
const server = new ApolloServer({
	typeDefs:typeDefs,
    resolvers:resolvers,
    cors:true
    // 모든 사이트 허용하고 싶을 때
    cors: { origin: ["https://naver.com", "https://daum.net"] }
    // 특정 사이트만 지정하고 싶을 때
});

어쩌다 보니 CORS에 대한 설명이 길어졌는데 다시 apollo에 대해 알아보자. 일단 설치는 npmjs에서 가능!

https://www.npmjs.com/package/@apollo/server

위 사진처럼 백엔드 서버 js파일에 작성을 해야하는데, typeDefs는 일종의 docs를 의미하고 resolvers는 실제 API를 생성하는 공간이라 생각하면 된다. 밑의 server는 말그대로 서버를 만든다고 생각하면 됨! typeDefs와 resolvers로 구성된 서버!
(express에서 const app = express()한것과 동일한 로직)

const app = express()

☝🏻 또한 express 뿐만 아니라 apollo server를 이용할 때도 아래와 같이 listen기능을 이용해 해당 포트 번호의 주소를 연결할 수 있다.

server.listen(3000).then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

Graphql API는 playground에서!

rest API는 postman을 이용해서 백엔드 서버로 요청을 보냈고, swagger를 통해서 프론트엔드쪽에선 클라이언트로부터 어떻게 정보를 받아서 백엔드로 요청해야 하는지에 대한 설명을 볼 수 있었다. 마찬가지고 graphql API에서도 이와 같은 역할을 하는 것이 있는데 playground이다.


Graphql API 만들기

1. fetchBoards API생성
: Query를 통한 데이터 조회 API


const resolvers = {
  Query: {
    fetchBoards: () => {
      // 1. 데이터를 조회하는 로직 => DB에 접속해서 데이터 꺼내오기
      const result = [
        {
          number: 1,
          writer: '철수',
          title: '제목입니다~~',
          contents: '내용이에요@@@',
        },
        {
          number: 2,
          writer: '영희',
          title: '영희 제목입니다~~',
          contents: '영희 내용이에요@@@',
        },
        {
          number: 3,
          writer: '훈이',
          title: '훈이 제목입니다~~',
          contents: '훈이 내용이에요@@@',
        },
      ];

      // 2. 꺼내온 결과 응답 주기
      return result;
    },
  },
}

rest API에서는 res.send를 이용해서 데이터 반환했지만, graphql API에서는 return을 이용해서 함수를 종료하면서 데이터를 반환한다. 여기 resolvers에서 반환되는 값들의 type 또한 typeDefs에서 지정해 주어야 함! docs역할

const typeDefs = gql`
  type BoardReturn {
    number: Int
    writer: String
    title: String
    contents: String
  }

  type Query {
    # fetchBoards: BoardReturn => 객체 1개를 의미
    fetchBoards: [BoardReturn] # => 배열 안에 객체 1개 이상을 의미
  }
`;

반환값은 배열 안에 객체의 형태로 이루어져 있는데, 먼저 객체 안의 타입을 지정해주기 위해서 type BoardReturn{}을 선언해주고 요소의 타입을 지정했다. 그리고 BoardReturn을 배열로 감싸서 fetchBoards:[BoardReturn]과 같이 지정해주었다. 이를 apollo server에서 실행시켜주고 localhost:3000/graphql로 접속하여 테스트 진행해보면 query API는 잘 등록된 것을 확인해볼 수 있다.

query {
  fetchBoards {
    number
    writer
    title
    contents
  }
}

2. creatBoard API 생성
: Mutation을 통한 등록 API 생성

API 내 생성된 함수를 통하여 입력값을 받아 올 수 있는데, 이 함수는 4개의 매개변수를 가질 수 있다.

const resolvers = {
  Mutation: {
    createBoard: (parent, args, context, info) => {
     
    },
  },
};

4개의 매개변수는 다음과 같은 역할을 함!

  • parent : 부모 타입 resolver에서 반환된 결과를 가진 객체
  • args : 쿼리 요청 시 전달된 parameter를 가진 객체
  • context : GraphQL의 모든 resolver가 공유하는 객체로서 로그인 인증, 데이터베이스 접근 권한 등에 사용
  • info : 명령 실행 상태 정보를 가진 객체
    
    rest-API에서는 요청 데이터를 확인하기 위해 매개변수로 req를 사용한 반면 graphql-API에서는 요청 데이터를 확인 가능한 args를 사용한다. 앞의 매개변수 중 사용하지 않는 것은 _ 로 선언
const resolvers = {
  Query: {
    fetchBoards: () => {
      // ...
    },
  },

  Mutation: {
    createBoard: (_, args) => {
      // 1. 브라우저에서 보내준 데이터 확인하기
      console.log(args);
			console.log("=========================")
      console.log(args.createBoardInput.writer)
      console.log(args.createBoardInput.title)
      console.log(args.createBoardInput.contents)

			// 2. DB에 접속 후, 데이터를 저장 => 데이터 저장했다고 가정

      // 3. DB에 저장된 결과를 브라우저에 응답(response) 주기
      return '게시물 등록에 성공하였습니다!!';
    },
  },
};

🌟 typeDefs 설정할 때 INPUT으로 작성해야 함! 값을 받아오는 경우이기 때문에 type이 아니라 input!!

# index.js

const typeDefs = gql`
  input CreateBoardInput {
    writer: String
    title: String
    contents: String
  }

  type Mutation {
    # createBoard(writer: String, title: String, contents: String): String => 입력값을 낱개로 받아오는 것을 의미
    createBoard(createBoardInput: CreateBoardInput!): String # => 입력값을 객체로 받아오는 것을 의미
  }
`;

apollo server를 통한 API 테스트

mutation {
  createBoard(createBoardInput: {
    writer: "코드캠프",
    title: "온라인 부트캠프 백엔드",
    contents: "매우 좋습니다!!!"
  })
}

🌟input CreateBoardInput에 지정해 놓은 요소들의 데이터 타입에 맞게 요청을 보내야 함

resolvers 라는 객체 안의 Mutation 객체에 작성해 놓은 createBoard함수가 실행됩니다. 이 때, type Mutation 에 지정해 놓은 타입에 맞게 입력값을 받게 되면 결과 응답 메세지가 잘 반환되는 것을 확인할 수 있음!!


내가 겪은.. 에러....

오늘은 apollo를 이용해서 graphql API를 만드는 공부를 했다. 수업이 너무 빨리 지나가서 제대로 다 듣지 못한 게 이번 일의 원흉이 된 것 같다...거의 6시간을 잡고 있었..던 나 칭찬해.. 그래도 덕분에 이건 절.대 안잊을 아니 못잊을 것만 같은 생각이 든다... 무튼 오늘도 기록하자..

graphql에선 docs부분과 API를 같은 파일내에서 만들어 주는데 아래의 화면을 보면 typeDefs라고 해서 docs부분을 만들어 주는 곳이다.
여기서 잘 보면 QQQ와 Query, Mutation의 차이가 있는데 그건 바로 !
!!!!inputtype이다

type은 기본적으로 return값에 자체적으로 들어가는 것인 경우에 type으로 작성을 하고, input은 아래처럼 값을 받아오는 input이 존재하는 경우(여기에선 myphone이라는 휴대폰 번호 값을 받아오도록 API를 만들고 있었음) input으로 작성을 해야 playground에서도 실행되고.. 서버도 에러가 안난다


여기 playground에서도 얼마나 고생을 했던지... 앞으로... 또 이런 일이 있다면 해당 페이지를 참고하도록 하자.. 인간은 같은 실수를 또 반복한다는 말이 있듯이 곧 다시 방문할 것만 같은 슬픈 예감이..💆🏻‍♂️

profile
`아는 만큼 보인다` 라는 명언을 좋아합니다. 많이 배워서 많은 걸 볼 수 있는 개발자가 되고 싶습니다.

0개의 댓글