호세 나바로 는 벨기에 브뤼셀의 FAMOCO에서 풀 스택 개발자로 일하고 있습니다. 그는 지난 3년 동안 웹 개발자 웹 개발 및 모바일 기술에 깊은 관심을 가지고 있으며 Node.js, Java, AngularJS, ReactJS에 능통합니다.

우리는 Graphql 서버에서 Express를 사용합니다. Graphql은 API용 쿼리 언어로, 페이스북에서 개발했으며 2015년에 출시되었습니다. 데이터 요구 사항과 상호 작용을 설명하기 위한 직관적이고 유연한 구문과 시스템을 제공하여 클라이언트 애플리케이션을 구축하도록 설계되었습니다. REST API와의 가장 큰 차이점 중 하나는 모든 리소스에 대해 하나의 엔드포인트가 아니라 모든 리소스에 대해 하나의 입력 엔드포인트만 있다는 것입니다. 또한 REST API 서비스가 반환하는 모든 것을 받는 대신 각 요청에 대해 원하는 속성을 지정할 수 있으므로 필요한 모든 데이터를 확보하고 요청 크기를 줄일 수 있다는 점입니다.

페이스북은 모바일 앱, 예를 들어 iOS 앱에서 몇 년 동안 사용해 왔으며 이전 버전의 모바일 앱은 동일한 스키마를 가진 하나의 엔드포인트이기 때문에 그래프QL이 변경되지 않아 여전히 문제없이 작동하지만, 새로운 버전의 API를 출시하면 엔드포인트가 변경되므로 클라이언트가 새로운 엔드포인트와 데이터에 적응해야 하므로 REST API에서는 불가능할 것입니다. 또한 Github 그래프 쿼리 서버를 열면 사용자가 REST API 서비스 대신 그래프 쿼리를 사용하여 서비스를 쿼리할 수 있습니다.

서버를 사용하여 쿼리하고 생성할 서버는 다음과 같습니다. 장소. 데이터를 저장하기 위해 카우치베이스를 사용하고 공간 뷰를 사용하여 지리적 위치별로 장소를 쿼리할 것입니다. 저는 이전에 post 에서 노드와 카우치베이스에 대해 설명했으므로 이전 포스트에서 작성한 DB 구성은 생략하겠습니다.

요구 사항

컴퓨터에 설치되어 있어야 합니다:

- nodejs

- 카우치베이스 서버

코드를 찾을 수 있습니다. 깃허브 리포지토리.

공간 보기

먼저 공간 뷰를 만들어야 합니다. 제 경우에는 관리자 페이지로 이동합니다. http://localhost:8091/ 을 클릭하고 내 사용자 아이디와 비밀번호로 로그인합니다. 그런 다음 데이터 버킷 버킷을 만들어서 그래프 쿼리. 그런 다음 보기을 클릭한 다음 개발 공간 뷰 만들기를 클릭하고 값을 입력합니다.

Create Spatial Development View

사용 장소별_위치 둘 다에서 디자인 문서 이름 그리고 이름 보기. 이제 편집를 클릭하고 다음 코드를 추가합니다.


함수 (문서) {

if (doc._type === '장소' && doc.location) {

emit([{

"유형": "포인트",

"좌표": [doc.location.long, doc.location.lat].

}], 문서);

}

}
를 클릭하고 저장.

여기에서 버킷에 있는 문서로 보기를 테스트할 수도 있습니다.

장소 모델

장소의 경우 장소 이름과 설명을 문자열로 저장하겠습니다.

우리는 스페이셜뷰카우치베이스 가 제공하는 *location*이라는 객체에 위도와 경도를 저장하여 장소의 위치를 저장할 것입니다.

또한 장소를 추가할 때 기본적으로 생성된 날짜가 설정됩니다.


const PlaceModel = ottoman.model('Place', {

이름: '문자열',

설명: '문자열',

위치: {

위도: '숫자',

lon: '숫자'

},

생성되었습니다: {

유형: '날짜',

기본값입니다: Date.now

}

});
공간 쿼리의 경우 공간 쿼리를 수행하는 함수를 정의하겠습니다. 이를 위해 couchbase 패키지를 사용하여 공간 쿼리를 생성하겠습니다.


const queryByLocation = (bbox = [0, 0, 0, 0], next) => {}

const query = couchbase.SpatialQuery.from('dev_place_by_location', 'place_by_location').bbox(bbox);

버킷.쿼리(쿼리, 다음);

}
부터* 함수에서 *디자인 문서 이름*과 *보기 이름*을 제공해야 합니다. 그런 다음 bbox(바운딩 박스)에 **[ 최소 경도 , 최소 위도 , 최대 경도 , 최대 위도 ]**의 4개의 실수 배열을 제공해야 합니다.

마지막 단계는 버킷에서 쿼리를 수행하는 것입니다.

Graphql 서버

여기서는 express 서버와 express-graphql 패키지를 사용하겠습니다.

나중에 정의할 graphql 서버의 스키마를 가져옵니다.


const express = require('express');

const graphqlHTTP = require('express-graphql');

const PORT = process.env.PORT || 5000;

const schema = require('./schemas');

const app = express();

app.use('/graphql', graphqlHTTP({

스키마: 스키마,

graphiql: true,

}));

// 서버 시작

const server = app.listen(PORT, () => {

console.log(서버가 ${ server.address().port }에서 시작되었습니다.);

});
graphql 서버에서는 */graphql* 경로를 사용하겠습니다. 그리고 쿼리를 실행할 수 있는 그래픽 인터페이스를 제공하는 graphiql과 같은 옵션을 설정할 것입니다.

마지막 단계는 익스프레스 서버를 시작하는 것입니다.

그래프 쿼리 스키마

Graphql 쿼리 및 변이는 우리가 정의한 스키마에 의존합니다. 따라서 Place 개체에 대한 스키마를 만들어야 합니다.

먼저 다음에 대한 스키마를 정의하겠습니다. 위치.


const {

그래프QLObjectType,

그래프QL플로트,

GraphQLNonNull,

} = require('graphql');

const LocationSchema = new GraphQLObjectType({

이름: '위치',

설명: '지리적 위치',

필드: {

위도: {

유형: 새로운 그래프QLNonNull(그래프QL플로트),

설명: '위도',

},

lon: {

유형: 새로운 그래프QLNonNull(그래프QL플로트),

설명: '경도',

},

}

});

module.exports = LocationSchema;

다음에서 유형을 가져와야 합니다. 그래프 쿼리 패키지입니다. 스키마에서 이름과 설명을 정의할 수 있으며, 이 필드는 쿼리를 문서화하는 데 유용하므로 사용자가 해당 필드의 의미를 이해할 수 있습니다.

그런 다음 다음을 정의해야 합니다. 필드에서 스키마 내부의 필드를 지정할 것이며, 이 경우 다음과 같이 정의했습니다. 위도 그리고 lon. 모든 필드에서 유형을 지정해야 하며, 이 경우 이러한 필드는 실수 값이며 필수이므로 다음을 사용합니다. 그래프QLNonNull  및 유형 그래프QL플로트. 이제 그 의미를 알 수 있도록 설명을 추가합니다.

이제 스키마를 정의하겠습니다. 장소.

여기서는 다음에서 유형을 가져올 것입니다. 그래프 쿼리 및 스키마 위치 를 정의했습니다.


const {

그래프QLObjectType,

그래프QLString,

GraphQLNonNull,

} = require('graphql');

const LocationSchema = require('./location');

const PlaceSchema = new GraphQLObjectType({

이름: '장소',

설명: '장소 설명',

필드: {

id: {

유형: 그래프QLString,

resolve(place) {

place._id를 반환합니다;

}

},

이름: {

유형: 그래프QLString,

},

설명:{

유형: 그래프QLString,

},

위치: {

유형: 위치 스키마,

},

생성되었습니다: {

유형: 그래프QLString,

}

}

});

module.exports = PlaceSchema;

모델에서 필드를 일치시키고 있으므로 확인 함수를 제공할 필요가 없습니다. 필드에 대해서만 id필드에 값을 반환하기 때문입니다. _id.

그래프 쿼리

쿼리는 서버의 데이터를 검색하는 방식입니다.

쿼리 객체도 이전과 마찬가지로 스키마입니다. 이 경우 필드는 사용자가 수행할 수 있는 쿼리입니다. 세 가지 유형의 쿼리를 정의하겠습니다.

모든 장소

이 쿼리에서는 데이터베이스의 모든 장소를 쿼리하고 생성된 필드에 따라 순서를 지정하므로 가장 최근의 장소를 먼저 반환합니다.

장소의 배열을 반환하는 것처럼, 우리는 유형 유형 그래프QL리스트 그리고 우리는 장소 스키마


...

const PlaceSchema = require('./place');

const Place = require('../모델/place');

모든 장소: {

유형: 새로운 GraphQLList(PlaceSchema),

설명: '모든 장소에 대한 쿼리',

resolve(root, args) {

반환 새 프로미스((해결, 거부) => {{

Place.find({}, {

정렬: {

생성되었습니다: -1

},

}, (오류, 장소) => {

if (err) {

reject(err);

}

해결(장소);

})

});

}

}

설명도 추가하며, 이 필드는 선택 사항입니다.

마지막 매개변수는 데이터베이스의 데이터를 검색하는 방법을 지정하는 함수, 즉 resolve 함수입니다. 데이터베이스에 대한 호출은 비동기적이므로 약속을 반환할 것이며, 이 약속은 우리가 ottoman으로 정의한 장소 모델을 사용할 것입니다. 이 모델에서는 find를 사용하여 문서를 쿼리하고, 모든 문서를 쿼리하기 때문에 첫 번째 매개변수로 빈 객체를 전달합니다. 두 번째 매개변수는 쿼리의 옵션이며, 이 경우 필드를 기준으로 주문할 것입니다. 생성 를 내림차순으로 나열합니다. 마지막으로 값으로 프로미스를 해결하거나 오류 발생 시 이를 거부하는 콜백 함수를 제공합니다.

장소

이 쿼리에서는 공간 보기를 사용하여 쿼리할 것이므로 쿼리의 매개 변수에 bbox 포인트를 전달해야 합니다.


...

const queryByLocation = require('../models/place').queryByLocation;

장소: {

유형: 새로운 GraphQLList(PlaceSchema),

설명: '경계 상자 안의 모든 위치에 대한 쿼리',

args: {

minLon: {

유형: 새로운 그래프QLNonNull(그래프QL플로트),

설명: '경계 상자의 최소 경도',

},

maxLon: {

유형: 새로운 그래프QLNonNull(그래프QL플로트),

설명: '경계 상자의 최대 경도',

},

minLat: {

유형: 새로운 그래프QLNonNull(그래프QL플로트),

설명: '경계 상자의 최소 위도',

},

maxLat: {

유형: 새로운 그래프QLNonNull(그래프QL플로트),

설명: '경계 상자의 최대 위도',

},

},

resolve(root, args) {

// bbox = [ 최소 경도 , 최소 위도 , 최대 경도 , 최대 위도 ]입니다.

const bbox = [

args.minLon,

args.minLat,

args.maxLon,

args.maxLat,

];

반환 새 프로미스((해결, 거부) => {{

queryByLocation(bbox, (err, places) => {

if (err) {

reject(err);

}

resolve(places.map((place) => place.value));

})

});

}

}

먼저 공간 쿼리를 수행하기 위해 정의한 함수를 가져옵니다.

이전 쿼리에서와 같이 유형을 장소 배열로 정의하고 설명을 추가합니다.

이 쿼리에서는 일부 매개변수가 필요하므로 매개변수를 참조하는 args를 정의하고, 내부의 각 값을 args 는 매개변수와 일치하며, 이 경우 4를 정의합니다, 최소론, 최대론, 최소위도, 최대위도 그리고 이들 모두에 대해 필요한 유형과 부동형을 정의할 것입니다.

이 경우 해결 함수도 프로미스입니다. 먼저 함수를 전달하기 위해 bbox 배열을 빌드합니다. 위치별 쿼리. 오류가 발생하면 오류로 약속을 거부하고, 성공하면 공간 보기가 전체 문서를 반환하는 지오포인트와 값을 반환하기 때문에 다른 공간 보기를 정의하면 변경되므로 DB에서 객체를 매핑해야 합니다.

장소

마지막으로 정의할 쿼리는 ID로 한 장소를 쿼리하는 쿼리입니다.


장소: {

유형: 장소 스키마,

설명: '장소 ID로 장소 쿼리',

args: {

id: {

유형: 새로운 GraphQLNonNull(GraphQLString),

설명: '장소 ID',

}

},

resolve(root, args) {

반환 새 프로미스((해결, 거부) => {{

Place.getById(args.id, (err, place) => {{

if (err) {

reject(err);

}

해결(장소);

});

});

}
이 경우 유형은 장소 스키마를 정의하기 위해서는 인수에 있는 id 를 문자열 및 필수로 설정했습니다.

해결 함수는 다시 약속이며, 이 경우에는 다음과 같은 함수를 사용할 것입니다. queryById 를 모델에 전달하고 id 값을 반환합니다.

그래프 쿼리 돌연변이

돌연변이를 사용하면 서버의 데이터를 수정할 수 있습니다. 쿼리에서 돌연변이 개체는 스키마입니다. 따라서 이전 스키마와 동일한 필드를 정의해야 합니다.

변이 쿼리를 수행할 때 괄호 사이에 값을 입력하고 쿼리와 마찬가지로 수정된 객체에서 검색하려는 값을 입력합니다.

여기서는 장소의 생성, 업데이트 및 삭제를 수행하겠습니다.

createPlace

이 돌연변이에서 우리는 새로운 장소를 만들 것입니다.

스키마 유형에서는 다음과 같이 정의할 것입니다. 장소 스키마생성된 장소를 반환할 것이기 때문입니다.


createPlace: {

유형: 장소 스키마,

설명: '장소 만들기',

args: {

이름: {

유형: 새로운 GraphQLNonNull(GraphQLString),

설명: '장소 이름',

},

설명: {

유형: 새로운 GraphQLNonNull(GraphQLString),

설명: '장소 설명',

},

위도: {

유형: 새로운 그래프QLNonNull(그래프QL플로트),

설명: '장소의 위도',

},

경도: {

유형: 새로운 그래프QLNonNull(그래프QL플로트),

설명: '장소의 경도',

}

},

resolve(source, args) {

반환 새 프로미스((해결, 거부) => {{

const place = 새로운 장소({

이름: args.name,

설명: args.description,

위치: {

lat: args.latitude,

LON: ARGS.경도,

},

});

place.save((err) => {

if (err) {

reject(err);

}

해결(장소);

})

});

}

}
쿼리와 마찬가지로 새 장소를 만드는 데 필요한 값으로 인수를 정의합니다. 이 경우 이름과 설명은 문자열로, 위도와 경도는 실수로 입력해야 하며 모든 필드가 필수입니다.

해결 함수에서는 프로미스를 반환할 것입니다. 프로미스 내부에 쿼리 값을 가진 장소를 만들 것입니다. args. 그런 다음 저장 를 클릭합니다. 마지막으로, 장소를 저장하는 데 오류가 발생하면 오류와 함께 약속을 거부하거나 장소 데이터로 약속을 해결합니다.

업데이트 장소

에서와 같이 createPlace 돌연변이 업데이트 장소 변이도 비슷하지만, 차이점은 이 경우 인수의 모든 값이 필요하지 않고 ID 필드가 필수 문자열이라는 점입니다; 그리고 해결 함수에서는 먼저 객체를 id를 호출한 다음 사용자가 제공한 필드를 확인하고 장소를 업데이트한 다음 마지막으로 저장하고 새 객체를 반환합니다.


updatePlace: {

유형: 장소 스키마,

설명: '장소 업데이트',

args: {

id: {

유형: 새로운 GraphQLNonNull(GraphQLString),

설명: '장소의 아이디',

},

이름: {

유형: 그래프QLString,

설명: '장소 이름',

},

설명: {

유형: 그래프QLString,

설명: '장소 설명',

},

위도: {

유형: 그래프QL플로트,

설명: '장소의 위도',

},

경도: {

유형: 그래프QL플로트,

설명: '장소의 경도',

}

},

resolve(source, args) {

반환 새 프로미스((해결, 거부) => {{

Place.getById(args.id, (err, place) => {{

if (err) {

reject(err);

} else {

if (args.name) {

place.name = args.name;

}

if (args.description) {

place.name = args.name;

}

if (args.latitude) {

place.location.lat = args.lat;

}

if (args.longitude) {

place.location.lon = args.경도;

}

place.save((err) => {

if (err) {

reject(err);

}

해결(장소);

});

}

})

});

}

}

삭제장소

마지막 변이는 삭제이며, 여기서는 다음과 같은 유형을 정의합니다. 장소 스키마삭제한 객체를 반환할 것이기 때문입니다.

인수에서, 우리는 단지 id 를 삭제할 장소로 클릭합니다.

해결 함수에서는 아이디로 장소를 검색하고 제거를 수행하는 프로미스를 반환할 것입니다. 장소를 찾을 수 없거나 제거하는 동안 오류가 있는 경우 프로미스를 거부하거나, 성공적으로 제거한 경우 장소 데이터로 프로미스를 해결합니다.


deletePlace: {

유형: 장소 스키마,

설명: '장소 삭제',

args: {

id: {

유형: 새로운 GraphQLNonNull(GraphQLString),

설명: '장소의 아이디',

},

},

resolve(source, args) {

반환 새 프로미스((해결, 거부) => {{

Place.getById(args.id, (err, place) => {{

if (err) {

reject(err);

} else {

place.remove((err) => {

if (err) {

reject(err);

}

해결(장소);

});

}

})

});

}

}

테스트

앱을 테스트하기 위해 서버에서 허용하는 Graphiql을 사용하겠습니다. http://localhost:5000/graphql

graphiql

이 페이지에서는 이전에 정의한 쿼리 및 변형을 수행할 수 있습니다.

만들기


돌연변이 {

createPlace(

이름: "testplace"

설명: "테스트 설명"

위도: 1.36

경도: 18.36

) {

id

}

}

mutation create

업데이트


돌연변이 {

업데이트 플레이스(

id: “41133f98-18e8-4979-89e0-7af012b0e14f”

이름: "updateplace"

설명: "업데이트된설명"

위도: 2.36

경도: 15.96

) {

id

이름

설명

}

}

mutation update

삭제


돌연변이 {

deletePlace(id: "41133f98-18e8-4979-89e0-7af012b0e14f") {

id

}

}
mutation delete

모두 쿼리하기


쿼리 {

모든 장소 {

id

이름

위치 {

위도

lon

}

}

}

query all

경계 상자별 쿼리


쿼리 {

장소(

minLon: 3

maxLon: 5

minLat: 49

maxLat: 51

) {

이름

위치 {

위도

lon

}

}

}
query bbox

ID로 장소 조회하기


돌연변이 {

deletePlace(id: "41133f98-18e8-4979-89e0-7af012b0e14f") {

id

}

}


query id

결론

Graphql은 정의한 정보에 대해서만 쿼리할 수 있으므로 언더페칭이나 오버페칭을 피할 수 있고, 항상 데이터를 확보할 수 있는 좋은 쿼리 언어입니다.

Graphql 서버에서는 클라이언트가 하나의 엔드포인트만 사용하므로 백엔드의 복잡성과 로직을 숨기므로 서버가 다른 백엔드에 연결하거나 다른 데이터베이스를 사용할 수 있으며, 변경되더라도 엔드포인트가 동일하므로 클라이언트 로직은 변경할 필요가 없습니다.

또한 카우치베이스로 데이터에서 지리적 쿼리를 수행하는 방법도 살펴봤습니다.

참조

작성자

게시자 Laura Czajkowski, 개발자 커뮤니티 관리자, Couchbase

로라 챠코브스키는 카우치베이스의 Snr. 개발자 커뮤니티 매니저로 카우치베이스의 커뮤니티를 총괄하고 있습니다. 그녀는 월간 개발자 뉴스레터를 담당하고 있습니다.

댓글 남기기