GraphQL 대 REST - GraphQL 튜토리얼

게시 됨: 2022-03-11

새로운 아이인 GraphQL에 대해 들어보셨을 것입니다. 그렇지 않다면 GraphQL은 REST의 대안인 API를 가져오는 새로운 방법입니다. 페이스북 내부 프로젝트로 시작했고, 오픈소스로 공개되면서 큰 호응을 얻었다.

이 기사의 목적은 이미 GraphQL을 사용하기로 마음을 먹었든 그냥 시도할 의향이 있든 상관없이 REST에서 GraphQL로 쉽게 전환할 수 있도록 돕는 것입니다. GraphQL에 대한 사전 지식은 필요하지 않지만 이 기사를 이해하려면 REST API에 대한 어느 정도의 지식이 필요합니다.

GraphQL 대 REST - GraphQL 튜토리얼

이 기사의 첫 번째 부분은 개인적으로 GraphQL이 REST보다 우수하다고 생각하는 세 가지 이유를 설명하는 것으로 시작합니다. 두 번째 부분은 백엔드에 GraphQL 끝점을 추가하는 방법에 대한 자습서입니다.

Graphql 대 REST: REST를 삭제하는 이유는 무엇입니까?

GraphQL이 귀하의 요구 사항에 적합한지 여부에 대해 여전히 망설이고 있다면 "REST vs. GraphQL"에 대한 매우 광범위하고 객관적인 개요가 제공됩니다. 그러나 GraphQL을 사용해야 하는 세 가지 이유에 대해서는 계속 읽으십시오.

이유 1: 네트워크 성능

이름, 성, 이메일 및 10개의 기타 필드가 있는 백엔드에 사용자 리소스가 있다고 가정합니다. 클라이언트에서는 일반적으로 그 중 몇 개만 필요합니다.

/users 끝점에서 REST 호출을 하면 사용자의 모든 필드가 반환되고 클라이언트는 필요한 필드만 사용합니다. 모바일 클라이언트에서 고려할 수 있는 데이터 전송 낭비가 분명히 있습니다.

GraphQL은 기본적으로 가능한 가장 작은 데이터를 가져옵니다. 사용자의 성과 이름만 필요한 경우 쿼리에 지정합니다.

아래 인터페이스는 GraphQL용 API 탐색기와 같은 GraphiQL이라고 합니다. 이 기사의 목적을 위해 작은 프로젝트를 만들었습니다. 코드는 GitHub에서 호스팅되며 두 번째 부분에서 자세히 살펴보겠습니다.

인터페이스의 왼쪽 창에는 쿼리가 있습니다. 여기에서 우리는 모든 사용자를 가져오고(RES로 GET /users 를 수행함) 이름과 성을 가져옵니다.

질문

 query { users { firstname lastname } }

결과

 { "data": { "users": [ { "firstname": "John", "lastname": "Doe" }, { "firstname": "Alicia", "lastname": "Smith" } ] } }

이메일도 받고 싶다면 "성" 아래에 "이메일" 줄을 추가하면 됩니다.

일부 REST 백엔드는 부분 리소스를 반환하기 위해 /users?fields=firstname,lastname 과 같은 옵션을 제공합니다. 그만한 가치가 있으므로 Google에서 권장합니다. 그러나 기본적으로 구현되지 않으며 특히 다른 쿼리 매개변수를 던질 때 요청을 거의 읽을 수 없게 만듭니다.

  • &status=active 활성 사용자 필터링
  • &sort=createdAat 사용자를 생성 날짜별로 정렬
  • &sortDirection=desc 분명히 필요하기 때문에
  • &include=projects 사용자의 프로젝트를 포함

이러한 쿼리 매개변수는 쿼리 언어를 모방하기 위해 REST API에 추가된 패치입니다. GraphQL은 무엇보다도 처음부터 요청을 간결하고 정확하게 만드는 쿼리 언어입니다.

이유 2: "포함 vs. 끝점" 디자인 선택

간단한 프로젝트 관리 도구를 만들고 싶다고 가정해 봅시다. 사용자, 프로젝트 및 작업의 세 가지 리소스가 있습니다. 또한 리소스 간의 다음 관계를 정의합니다.

리소스 간의 관계

다음은 우리가 세상에 노출하는 몇 가지 끝점입니다.

끝점 설명
GET /users 모든 사용자 나열
GET /users/:id id:id가 있는 단일 사용자 가져오기
GET /users/:id/projects 한 사용자의 모든 프로젝트 가져오기

끝점은 간단하고 읽기 쉽고 잘 구성되어 있습니다.

요청이 더 복잡해지면 상황이 더 복잡해집니다. GET /users/:id/projects 끝점을 사용하겠습니다. 여러 REST 호출을 수행하지 않고 홈 페이지에는 프로젝트 제목만 표시하고 대시보드에는 프로젝트+작업을 표시하고 싶다고 가정해 보겠습니다. 나는 전화할 것이다:

  • 홈 페이지에 대한 GET /users/:id/projects .
  • GET /users/:id/projects?include=tasks (예: 대시보드 페이지)를 사용하면 백엔드가 모든 관련 작업을 추가할 수 있습니다.

이 작업을 수행하기 위해 쿼리 매개변수 ?include=... 를 추가하는 것이 일반적이며 JSON API 사양에서도 권장합니다. ?include=tasks 와 같은 쿼리 매개변수는 여전히 읽을 수 있지만 머지 않아 ?include=tasks,tasks.owner,tasks.comments,tasks.comments.author 입니다.

이 경우 /projects 엔드포인트를 생성하여 이를 수행하는 것이 더 현명할까요? /projects?userId=:id&include=tasks 와 같은 것입니다. 포함할 관계 수준이 한 단계 낮기 때문입니다. 또는 실제로 /tasks?userId=:id 끝점도 작동할 수 있습니다. 이것은 어려운 디자인 선택이 될 수 있으며 다대다 관계가 있는 경우 훨씬 더 복잡합니다.

GraphQL은 모든 곳에서 include 접근 방식을 사용합니다. 이것은 관계를 가져오는 구문을 강력하고 일관되게 만듭니다.

다음은 id가 1인 사용자로부터 모든 프로젝트와 작업을 가져오는 예입니다.

질문

 { user(id: 1) { projects { name tasks { description } } } }

결과

 { "data": { "user": { "projects": [ { "name": "Migrate from REST to GraphQL", "tasks": [ { "description": "Read tutorial" }, { "description": "Start coding" } ] }, { "name": "Create a blog", "tasks": [ { "description": "Write draft of article" }, { "description": "Set up blog platform" } ] } ] } } }

보시다시피 쿼리 구문은 쉽게 읽을 수 있습니다. 더 깊이 들어가 작업, 댓글, 사진 및 작성자를 포함하고 싶다면 API를 구성하는 방법에 대해 두 번 생각하지 않을 것입니다. GraphQL을 사용하면 복잡한 객체를 쉽게 가져올 수 있습니다.

이유 3: 다양한 유형의 클라이언트 관리

백엔드를 구축할 때 우리는 항상 API를 가능한 한 모든 클라이언트가 널리 사용할 수 있도록 하는 것으로 시작합니다. 그러나 클라이언트는 항상 덜 호출하고 더 많이 가져오기를 원합니다. 깊은 포함, 부분 리소스 및 필터링을 사용하면 웹 및 모바일 클라이언트의 요청이 서로 많이 다를 수 있습니다.

REST에는 몇 가지 솔루션이 있습니다. 사용자 지정 끝점(예: 별칭 끝점, 예: /mobile_user ), 사용자 지정 표현( Content-Type: application/vnd.rest-app-example.com+v1+mobile+json ) 또는 클라이언트를 만들 수도 있습니다. -특정 API(Netflix가 한때 그랬던 것처럼). 세 가지 모두 백엔드 개발 팀의 추가 노력이 필요합니다.

GraphQL은 클라이언트에 더 많은 기능을 제공합니다. 클라이언트에 복잡한 요청이 필요한 경우 해당 쿼리 자체를 빌드합니다. 각 클라이언트는 동일한 API를 다르게 사용할 수 있습니다.

GraphQL을 시작하는 방법

오늘날 "GraphQL vs. REST"에 대한 대부분의 논쟁에서 사람들은 둘 중 하나를 선택해야 한다고 생각합니다. 이것은 사실이 아닙니다.

최신 애플리케이션은 일반적으로 여러 API를 노출하는 여러 서비스를 사용합니다. 우리는 실제로 GraphQL을 이러한 모든 서비스에 대한 게이트웨이 또는 래퍼로 생각할 수 있습니다. 모든 클라이언트는 GraphQL 엔드포인트에 도달하고 이 엔드포인트는 데이터베이스 계층, ElasticSearch 또는 Sendgrid와 같은 외부 서비스 또는 기타 REST 엔드포인트에 도달합니다.

GraphQL과 REST 엔드포인트 비교

둘 다 사용하는 두 번째 방법은 REST API에 별도의 /graphql 끝점을 갖는 것입니다. 이것은 이미 REST API를 사용하는 수많은 클라이언트가 있지만 기존 인프라를 손상시키지 않고 GraphQL을 시도하려는 경우에 특히 유용합니다. 이것이 오늘날 우리가 탐구하는 솔루션입니다.

앞서 말했듯이 GitHub에서 사용할 수 있는 작은 예제 프로젝트로 이 자습서를 설명합니다. 사용자, 프로젝트 및 작업이 있는 단순화된 프로젝트 관리 도구입니다.

이 프로젝트에 사용된 기술은 웹 서버용 Node.js와 Express, 관계형 데이터베이스용 SQLite, ORM용 Sequelize입니다. 사용자, 프로젝트 및 작업의 세 가지 모델은 models 폴더에 정의되어 있습니다. REST 끝점 /api/users , /api/projects/api/tasks 는 세계에 노출되며 rest 폴더에 정의됩니다.

GraphQL은 프로그래밍 언어를 사용하여 모든 유형의 백엔드 및 데이터베이스에 설치할 수 있습니다. 여기에 사용된 기술은 단순성과 가독성을 위해 선택되었습니다.

우리의 목표는 REST 끝점을 제거하지 않고 /graphql 끝점을 만드는 것입니다. GraphQL 끝점은 데이터베이스 ORM에 직접 도달하여 데이터를 가져오므로 REST 논리와 완전히 독립적입니다.

유형

데이터 모델은 강력한 형식의 유형 으로 GraphQL에서 표현됩니다. 모델과 GraphQL 유형 간에 1:1 매핑이 있어야 합니다. User 유형은 다음과 같습니다.

 type User { id: ID! # The "!" means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }

쿼리

쿼리 는 GraphQL API에서 실행할 수 있는 쿼리를 정의합니다. 규칙에 따라 모든 기존 쿼리를 포함하는 RootQuery 가 있어야 합니다. 또한 각 쿼리에 해당하는 REST를 지적했습니다.

 type RootQuery { user(id: ID): User # Corresponds to GET /api/users/:id users: [User] # Corresponds to GET /api/users project(id: ID!): Project # Corresponds to GET /api/projects/:id projects: [Project] # Corresponds to GET /api/projects task(id: ID!): Task # Corresponds to GET /api/tasks/:id tasks: [Task] # Corresponds to GET /api/tasks }

돌연변이

쿼리가 GET 요청인 경우 돌연변이POST / PATCH / PUT / DELETE 요청으로 볼 수 있습니다(실제로는 쿼리의 동기화된 버전이지만).

관례에 따라 모든 돌연변이를 RootMutation 에 넣습니다.

 type RootMutation { createUser(input: UserInput!): User # Corresponds to POST /api/users updateUser(id: ID!, input: UserInput!): User # Corresponds to PATCH /api/users removeUser(id: ID!): User # Corresponds to DELETE /api/users createProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): Project createTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task }

여기에 UserInput , ProjectInputTaskInput 이라는 새로운 유형이 도입되었습니다. 이는 리소스 생성 및 업데이트를 위한 입력 데이터 모델을 생성하는 REST에서도 일반적인 방법입니다. 여기서 UserInput 유형은 idprojects 필드가 없는 User 유형이며 type 대신 키워드 input 을 확인합니다.

 input UserInput { firstname: String lastname: String email: String }

개요

유형, 쿼리 및 변형을 사용하여 GraphQL 끝점이 세상에 노출되는 GraphQL 스키마 를 정의합니다.

 schema { query: RootQuery mutation: RootMutation }

이 스키마는 강력한 형식을 갖추고 있으며 GraphiQL에서 편리한 자동 완성 기능을 제공합니다.

해결사

이제 공개 스키마가 있으므로 이러한 각 쿼리/변이가 요청될 때 수행할 작업을 GraphQL에 알려야 합니다. 해결사 는 힘든 일을 합니다. 예를 들어 다음을 수행할 수 있습니다.

  • 내부 REST 엔드포인트 적중
  • 마이크로서비스 호출
  • CRUD 작업을 수행하려면 데이터베이스 계층을 누르십시오.

예제 앱에서 세 번째 옵션을 선택하고 있습니다. resolvers 파일을 살펴보겠습니다.

 const models = sequelize.models; RootQuery: { user (root, { id }, context) { return models.User.findById(id, context); }, users (root, args, context) { return models.User.findAll({}, context); }, // Resolvers for Project and Task go here }, /* For reminder, our RootQuery type was: type RootQuery { user(id: ID): User users: [User] # Other queries }

즉, GraphQL에서 user(id: ID!) 쿼리가 요청되면 데이터베이스에서 Sequelize ORM 함수인 User.findById() 를 반환합니다.

요청에 다른 모델에 합류하는 것은 어떻습니까? 더 많은 확인자를 정의해야 합니다.

 User: { projects (user) { return user.getProjects(); // getProjects is a function managed by Sequelize ORM } }, /* For reminder, our User type was: type User { projects: [Project] # We defined a resolver above for this field # ...other fields } */

따라서 GraphQL에서 User 유형의 projects 필드를 요청하면 이 조인이 데이터베이스 쿼리에 추가됩니다.

마지막으로 돌연변이에 대한 리졸버:

 RootMutation: { createUser (root, { input }, context) { return models.User.create(input, context); }, updateUser (root, { id, input }, context) { return models.User.update(input, { ...context, where: { id } }); }, removeUser (root, { id }, context) { return models.User.destroy(input, { ...context, where: { id } }); }, // ... Resolvers for Project and Task go here }

여기에서 이것으로 놀 수 있습니다. 서버의 데이터를 깨끗하게 유지하기 위해 돌연변이에 대한 리졸버를 비활성화했습니다. 즉, 돌연변이는 데이터베이스에서 생성, 업데이트 또는 삭제 작업을 수행하지 않으므로 인터페이스에서 null 을 반환합니다.

질문

 query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: "New Project", UserId: 2}) { id name } }

결과

 { "data": { "user": { "firstname": "Alicia", "lastname": "Smith", "projects": [ { "name": "Email Marketing Campaign", "tasks": [ { "description": "Get list of users" }, { "description": "Write email template" } ] }, { "name": "Hire new developer", "tasks": [ { "description": "Find candidates" }, { "description": "Prepare interview" } ] } ] } } }

기존 앱의 모든 유형, 쿼리 및 확인자를 다시 작성하는 데 시간이 걸릴 수 있습니다. 그러나 도움이 되는 많은 도구가 있습니다. 예를 들어, 해석기를 포함하여 SQL 스키마를 GraphQL 스키마로 변환하는 도구가 있습니다!

모든 것을 합치다

스키마의 각 쿼리에서 수행할 작업에 대한 잘 정의된 스키마와 해석기를 사용하여 백엔드에 /graphql 엔드포인트를 마운트할 수 있습니다.

 // Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));

그리고 백엔드에 멋진 GraphiQL 인터페이스를 가질 수 있습니다. GraphiQL 없이 요청하려면 요청의 URL을 복사하고 cURL, AJAX로 실행하거나 브라우저에서 직접 실행하십시오. 물론 이러한 쿼리를 작성하는 데 도움이 되는 일부 GraphQL 클라이언트가 있습니다. 몇 가지 예는 아래를 참조하십시오.

무엇 향후 계획?

이 기사의 목표는 GraphQL이 어떤 모습인지 맛보고 REST 인프라를 버리지 않고 GraphQL을 시도하는 것이 확실히 가능하다는 것을 보여주는 것입니다. GraphQL이 귀하의 요구 사항에 적합한지 확인하는 가장 좋은 방법은 직접 사용해 보는 것입니다. 이 기사가 당신이 잠수하는 데 도움이 되기를 바랍니다.

실시간 업데이트, 서버 측 일괄 처리, 인증, 권한 부여, 클라이언트 측 캐싱, 파일 업로드 등과 같이 이 기사에서 논의하지 않은 많은 기능이 있습니다. 이러한 기능에 대해 배울 수 있는 훌륭한 리소스입니다. GraphQL 방법입니다.

다음은 기타 유용한 리소스입니다.

서버 측 도구 설명
graphql-js GraphQL의 참조 구현입니다. express-graphql 과 함께 사용하여 서버를 생성할 수 있습니다.
graphql-server Apollo 팀이 만든 올인원 GraphQL 서버.
다른 플랫폼에 대한 구현 루비, PHP 등
클라이언트 측 도구 설명
계전기 React와 GraphQL을 연결하기 위한 프레임워크입니다.
아폴로 클라이언트. React, Angular 2 및 기타 프런트 엔드 프레임워크에 대한 바인딩이 있는 GraphQL 클라이언트.

결론적으로 저는 GraphQL이 과대 광고 그 이상이라고 생각합니다. 아직 내일 REST를 대체하지는 않지만 진정한 문제에 대한 성능 솔루션을 제공합니다. 이는 비교적 새롭고 모범 사례가 여전히 개발 중이지만 향후 몇 년 동안 우리가 듣게 될 기술입니다.