GraphQL 체험해보기

Hank Kim·2023년 1월 29일
0

GraphQL이란

페이스북에서 만든 데이터 쿼리 언어
웹 클라이언트에서 서버로부터 데이터를 효율적으로 가져오기 위해 만들어졌다.
줄여서 gql이라고 부른다.
특정 데이터베이스나 언어에 종속적이지 않다.

클라이언트에서 apollo client를 이용해서 쿼리를 작성하고 GraphQL으로 개발된 API서버에 요청을 보낸다. 받은 요청을 바탕으로 DB로부터 데이터를 가져와 클라이언트로 전송한다.

서버

서버는 gql로 작성된 쿼리를 입력받아 처리한 결과를 클라이언트로 보내준다.
서버에서는 여러개의 api endpoint를 만들 필요가 없다.

const express = require("express");
const colors = require("colors");
require("dotenv").config();
const cors = require("cors");
const { graphqlHTTP } = require("express-graphql");
const schema = require("./schema/schema");
const connectDB = require("./config/db");
const port = process.env.PORT || 5001;
const app = express();

connectDB();
app.use(cors());
app.use(
  "/graphql",
  graphqlHTTP({
    schema,
    graphiql: process.env.NODE_ENV === "development",
  })
);

app.listen(port, () => {
  console.log("server running");
});

graphQLHTTP 메소드에 schema를 전달하면 된다.
graphiql은 devtool이다.
ex) localhost:5000/graphql 으로 이동하면 사용할 수 있다.

GraphQL 요청에는 Query와 Mutation이 있다.
React-Query에서 사용되던 의미와 같다고 보면 될 것 같다.
데이터를 가져오기만 하는 get요청에서는 query를 사용하고 post, update등의 요청에는 mutation을 사용한다.

const RootQuery = new GraphQLObjectType({
  name: "RootQueryType",
  fields: {
    clients: {
      type: new GraphQLList(ClientType),
      resolve(parents, args) {
        return Client.find();
      },
    },
    client: {
      type: ClientType,
      args: { id: { type: GraphQLID } },
      resolve(parent, args) {
        return Client.findById(args.id);
      },
    },
  },
});


const mutation = new GraphQLObjectType({
  name: "Mutation",
  fields: {
    // Add a client
    addClient: {
      type: ClientType,
      args: {
        name: { type: GraphQLNonNull(GraphQLString) },
        email: { type: GraphQLNonNull(GraphQLString) },
        phone: { type: GraphQLNonNull(GraphQLString) },
      },
      resolve(parent, args) {
        const client = new Client({
          name: args.name,
          email: args.email,
          phone: args.phone,
        });

        return client.save();
      },
    }
})
  
module.exports = new GraphQLSchema({
  query: RootQuery,
  mutation,
});

각 요청마다 어떤 데이터를 전송할지 혹은 데이터베이스에 생성할지를 resolve메소드(resolver라고 한다)안에 작성한다.
또한 graphql은 타입 체크를 하기 때문에

const ClientType = new GraphQLObjectType({
  name: "Client",
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString },
    email: { type: GraphQLString },
    phone: { type: GraphQLString },
  }),
});

각 타입마다 어떤 필드를 가지는지 명시한 객체를 생성해야한다.
이렇게 생성한 타입 객체를 Query와 Mutation의 return Type을 지정할 때 사용한다.

클라이언트

단 한개의 endpoint로만 요청을 보내면 된다.
ex) localhost:5000/graphql
클라이언트 측에서는 gql요청을 보내기 위해서 Apollo Client를 사용한다. Apollo Client는 gql 쿼리문을 작성하는 기능을 제외하면 react-query와 비슷한 역할을 한다. (데이터 fetch 및 캐싱) 메소드 이름도 useQuery와 useMutation으로 비슷하다.
React Query처럼 background fetching은 아니므로 완전히 대체하지는 않아서 같이 사용할수도 있다.
저장뿐만 아니라 전역상태도 관리할 수 있어서 저장된 데이터가 변경되면 리렌더링 된다. Redux를 굳이 사용할 필요가 없다.

Query예시

import { gql } from "@apollo/client";

 const GET_CLIENTS = gql`
  query getClients {
    clients {
      id
      name
      email
      phone
    }
  }
`;

function Clients() {
  const { loading, error, data } = useQuery(GET_CLIENTS);
  if (loading) return <Spinner />;
  if (error) return <p>wtf</p>;
  return (
    <>
      {!loading && !error && (
        <table className="table table-hover mt-3">
        ...
          <tbody>
            {data.clients.map((client) => (
              <ClientRow key={client.id} client={client} />
            ))}
          </tbody>
        </table>
      )}
    </>
  );
}

Mutation예시

import { gql } from "@apollo/client";
export const ADD_CLIENT = gql`
  mutation addClient($name: String!, $email: String!, $phone: String!) {
    addClient(name: $name, email: $email, phone: $phone) {
      id
      name
      email
      phone
    }
  }
`;

const [addClient] = useMutation(ADD_CLIENT, {
    variables: { name, email, phone },
    refetchQueries: [{ query: GET_CLIENTS }],
  });

  const onSubmit = (e) => {
    e.preventDefault();

    if (name === "" || email === "" || phone === "") {
      return alert("Please fill in all fields");
    }

    addClient(name, email, phone);

    setName("");
    setEmail("");
    setPhone("");
  };

refetchQueries를 통해서 데이터를 변경할때 새로운 데이터로 갱신되도록 한다.

장점

  1. 엔드포인트가 하나라서 프론트에서 요청 보내기 편하다.
  2. 프론트에서 쿼리문을 작성해서 보내므로 원하는 데이터만 받을 수 있다. (underfetching, overfetching 방지)
  3. REST API에 비해서 HTTP 응답의 사이즈가 작아서 모바일 사용에 유리하다고 한다.
  4. API개발에서 request, response형식을 많이 논의할 필요가 없다.
  5. 필요한 데이터들이 api각각에 나누어져 있을때도 여러번 요청할 필요가 없다.

단점

  1. 서버에서 스키마, 리턴타입 등 설정해야 하는게 많다
  2. form-data형식을 받을 수 없어서 파일업로드 불가
profile
JavaScript, Python Algorithm

0개의 댓글