Back/Node.js

[NodeJs, GraphQL, Apollo, Express, Prisma, TypeScript] Setting

 

GraphQL을 검색 서버에서 활용해보고 싶었다.

 

앱 내에 다양한 검색들이 있었는데,

 

검색 부분에서 restApi보단,

 

endpoint를 동일하게하고, 프론트에서 원하는 데이터만 축출해가는 디자인 패턴이

 

더 효율적이라 생각했기 때문이다.

 

일단 프로젝트 배포전 임의로 셋팅 해봤다.

 

app.ts

import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import apolloServer from './apollo/apolloserver';
import 'dotenv/config';

const main = async () => {
  const app = express();

  app.use(express.json());
  app.use(express.urlencoded({ extended: false }));
  app.use(express.static('public'));
  app.use(bodyParser.urlencoded({ extended: true }));

  app.use(
    cors({
      origin: '*',
      credentials: true,
    }),
  );

  apolloServer.listen(process.env.PORT).then(({ url }) => {
    console.log(`Listening at ${url}`);
  });
};

main();

 

아폴로 서버를 사용했다.

실서버에선 cors를 origin에 맞게 넣어줘야겠다.

 

apolloserver.ts

import { ApolloServer } from 'apollo-server';
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core';
import schema from '../gql/schema';
import memberAuthMiddleWare from '../middlewares/memberAuth.middleware';
import resolvers from '../gql/resolvers/member.resolvers';
import 'dotenv/config';

const apolloServer = new ApolloServer({
  rootValue: resolvers,
  schema,
  cors: {
    allowedHeaders: '*',
    origin: '*',
    credentials: true,
  },
  introspection: true,
  plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
  context: async ({ req }) => {
    const authorization = req.headers.authorization || '';
    const token = await memberAuthMiddleWare(authorization);
    return token;
  },
});


export default apolloServer;

cors는 실 프로덕트에선 사용할 origin만 넣어야겠다.

introspection은 playground와 applo-send-box사용할 때 사용되는 것인데,

메인 프로덕트는 false로 배포해야겠다.

plugins에선 playground를 사용하겠다는 옵션을 넣었다. (또한 실 프로덕트에선 빼야함)

 

context는 graphql에서 미들웨어라고 생각하면 편하다.

위 코드는 멤버 토큰을 받아오고, 나중에 context.token으로 불러올 수 있다.

 

 

member.typeDefs.ts

import { gql } from 'apollo-server';

const memberTypeDefs = gql`
  type MemberInfo {
    memberIdx: Int
    memberId: String
    memberPw: String
    memberJoin_type: String
    memberName: String
    memberPhone: String
    memberEmail: String
    gender: Int
    memberAge: Int
    dietMode: Int
    height: Int
    weightGoal: Int
    netCarbohydrateGoal: Int
    proteinGoal: Int
    fatGoal: Int
    caloriesGoal: Int
    status: Int
    profileCheck: Int
    joinRoute: Int
    profileImg: String
    delYn: Int
    createdAt: String
    updatedAt: String
  }

  type SnsInfo {
    memberIdx: Int
    followingMemberIdx: Int
  }

  type Query {
    getMemberListSearchedByWord(word: String): [MemberInfo]
    getFollowingList: [SnsInfo]
  }

`;

export default memberTypeDefs;

타입들을 지정해줬다. 지정한 것들은 리졸브나 뮤테이션에서 사용할 수 있다.

 

member.resolvers.ts

import { Context } from '../../types/context';
import memberService from '../../service/member.service';

const memberResolvers = {
  Query: {
    getMemberListSearchedByWord: async (_parent: any, args: any, _context: Context, _info: any) => {
      const result = await memberService.getMemberListSearchedByWord(args.word);
      return result;
    },

    getFollowingList: async (_parent: any, _args: any, context: Context, _info: any) => {
      const result = await memberService.getFollowingList(context.token);
      return result;
    },
  },
};

export default memberResolvers;

resolvers에서 사용되는 것들로, 클라이언트에서 쿼리로 요청할 때 return값을 반환한다.

memberService는 서비스 로직이다.

controller와 service로 나누던걸, resolvers와 service로 나누는 느낌이 들었다.

 

args는 클라이언트에서 보낸 데이터를 받는데 사용되고,

context는 아까 아폴로 서버에서 만들어 둔 미들웨어를 받는데 사용한다.

 

 

클라이언트 코드

import axios from "axios";

axios.get(serverEndPoint, {
			params: {
				query: `{
                  getMemberListSearchedByWord(word: “검색해보기“) {
                    memberIdx
 					memberName
                  }
                }`,
			},
		})
		.then((result) => {
			console.log("hello", result);
		})
		.catch((err) => {
			console.log(err);
		});
})

restApi처럼 axios나 request를 이용해서 요청할 수 있다.

다만 엔드포인트가 똑같고, params에 query를 넣어서 요청한다.

mutations같은 경우엔 post를 이용한다.

 

mutations나 resovlers나 모두 post로 통일해서 이용할 수 있지만, 해당 클라이언트 코드에선 get method를 사용했다.