cnode API GraphQL服务器 版本
发布于 4 年前 作者 phpsmarter 2501 次浏览 来自 分享

按照cnode-api的顺序来的, 基本只是做了包装, 需要修改的可以看代码,做出修改. 通过 graphql 的 rest-wrapper resolver 包装以后,就可以获得 graphql的一些很好的特征了. 因为没有数据写入的Grpahql 的数据库,所以所有的操作都用的是query. 没有用 mutate. 因为最终还是访问的 REST API.
这的graphql采用的是 graphcool 的服务器, 尽管试, 没有写入操作.

Graphcool-cnode-server graphiQL地址

服务器的初始化可以参考这里Graphcool Server

简单查询实例

query getUserInfo {
  getUserInfo(name: "phpsmarter") {
    loginname
    recent_topics
    score
  }
}
  • 点击执行按钮▶️
  • 获得结果
  • 或者我们可以在一次查询中返回多个 REST API的数据. 同时获取多个REST API的数据
query getServeralDatas {
  getUserInfo(name: "phpsmarter") {
    loginname
    recent_topics
    score
  }
  
  AllTopics(tab:"share",page:1){
       title
       tab
       visit
  }
}

  • 接下来,可以探索其他的用法了.点击右侧的 schema按钮, 所有查询的名字, 方法, 参数,返回的数据类型,一目了然.

推荐你自己敲入,GraphiQL 提供良好的自动补全

可以选择部署在本地docker 中, 如果用于测试可以 直接部署在云上.

大致的流程如下在 GraphQL客户端或者是 GraphiQL执行的操作,会经过 resolver 函数的处理, resolver 实际是 express 服务器, 在这里可以执行数据库操作,或者是执行转发任务, 如果是为 API 提供服务, 就使用转发. 我们这里就是转发.


主题

1 get/tpoics 主题首页

就是数据列表,tab用于分类, page 用于分页

allTopics.graphql

  //⛔️有些字段没有列出,可以做修改
type AllTopicsPayload {
  id: String!
	tab: String
	title: String!
	visit: Int!
	aurl: String!
	author_id: String!
}

input QueryInput {
	page: Int!
	tab: String!
}

extend type Query {
	AllTopics(page: Int!, tab: String!): [AllTopicsPayload!]!
}

allTopics.js

require('isomorphic-fetch');
const R = require('ramda');

const url = 'https://cnodejs.org/api/v1/topics';

module.exports = (event) => {
	const { tab, page } = event.data;
	urlWithParams = `${url}?tab=${tab}&page=${page}`;
	let options = {
		method: 'GET'
	};
	return fetch(urlWithParams, options).then((response) => response.json()).then((responseData) => {
		const NodeList = responseData.data;
		const allCnode = [];
			const selectPropertyX = (x) => ({
			id: x.id,
			tab: x.tab,
			aurl: x.author.avatar_url,
			visit: x.visit_count,
			title: x.title,
			author_id: x.author_id
		});

		const allTopics = R.map(selectPropertyX, NodeList);
		//const getIdcollections=R.curry(R.map(selectPropertyX,data));
		//const allCnode=getIdcollections(NodeList);

		return { data: allTopics };
	});
};

查询示意图

注意事项 graphiQL里执行的操作就是最好的文档, 这里执行的查询结果, 在其他地方可以完全复现,如果复现不了,就是你的客户端代码由问题. 在客户端我们可以使用 graphql-tag 的方法把这段查询的字符串给拼接出来.如果拼接没有问题, 得到的结果是完全一样的

2 get/topics主题详情

getOneTopic.graphql

 type OneTopicPayload {
  id: String!
  tab: String
  title: String!,
  content:String!,
}

extend type Query {
   getOneTopic(id:String!): OneTopicPayload!
}

getOneTopic.js

require('isomorphic-fetch')
const R = require('ramda');

module.exports = event => {
  const {id} = event.data
  const url = `https://cnodejs.org/api/v1/topic/${id}`

  return fetch(url)
    .then(response => response.json())
    .then(responseData => {
      return {data: responseData.data}

    })
}

查询

3新建主题 post/topics

createTopic.graphql

type createTopicPayload {
  tab: String!@default(value: "dev") #默认选了 dev
  title:String!
  accesstoken: String!
  content: String!
  success: Boolean
  
}

extend type Query {
  createTopic(title: String!,accesstoken:String!,tab:String!,content:String!): createTopicPayload
}

createTopic.js

'use latest'
import fetch from 'node-fetch'
const api = 'https://cnodejs.org/api/v1/topics' 
module.exports = async event => {
  const {tab, accesstoken, title, content} = event.data
  const body = {
    accesstoken: accesstoken,
    tab: tab,
    title: title,
    content: content
  }
  const ress = await fetch(api, {
    method: 'POST',
    body: JSON.stringify(body),
    headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
    })
    .then(res => res.json())
    .then(json => json)
  const success = {
    'success': ress.success
  }
  console.log(ress);
  return {data: success}
}

查询示意

4编辑主题 post/topics/update

updateTopic.graphql

type updateTopicPayload {
    tab: String!@default(value: "dev")
    title:String!
    accesstoken: String!
    content: String!
    success: Boolean
    topic_id:String!
  
}

extend type Query {
    updateTopic(title: String!,accesstoken:String!,tab:String!,content:String!,topic_id:String!): updateTopicPayload
}


updateTopic.js

'use latest';
import fetch from 'node-fetch';
const api = 'https://cnodejs.org/api/v1/topics/update';
module.exports = async event => {
	const { tab, accesstoken, title, content, topic_id } = event.data;
	const body = {
		accesstoken: accesstoken,
		tab: tab,
		title: title,
		content: content,
		topic_id: topic_id
	};
	const ress = await fetch(api, {
		method: 'POST',
		body: JSON.stringify(body),
		headers: { Accept: 'application/json', 'Content-Type': 'application/json' }
	})
		.then(res => res.json())
		.then(json => json);
	const success = {
		success: ress.success
	};
	console.log(ress);
	return { data: ress };
};

查询结果 和创建基本是一样的

收藏

1收藏主题 get topic_collection/collect

createTopicCollection.graphql

type createTopicCollectionPayload {
  topic_id: String
  accesstoken: String
  success: Boolean
  
}

extend type Query {
  createTopicCollection(topic_id: String!,accesstoken:String!): createTopicCollectionPayload
}

createTopicCollection.js

'use latest'
import fetch from 'node-fetch'
const api = 'https://cnodejs.org/api/v1/topic_collect/collect'
module.exports = async event => {
  const {topic_id, accesstoken} = event.data
  const body = {
    accesstoken: accesstoken,
    topic_id: topic_id
  }
  const ress = await fetch(api, {
    method: 'POST',
    body: JSON.stringify(body),
    headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
    })
    .then(res => res.json())
    .then(json => json)
  const success = {
    'success': ress.success
  }
  return {data: success}
}

2取消主题收藏 post/topic_collection/de_collect

cancelTopicCollection.graphql

type cancelTopicCollectionPayload {
  topic_id: String
  accesstoken: String
  success: Boolean
  
}

extend type Query {
  cancelTopicCollection(topic_id: String!,accesstoken:String!): cancelTopicCollectionPayload
}

cancelTopicCollection.js

'use latest'
import fetch from 'node-fetch'
const api = 'https://cnodejs.org/api/v1/topic_collect/de_collect'
module.exports = async event => {
  const {topic_id, accesstoken} = event.data
  const body = {
    accesstoken: accesstoken,
    topic_id: topic_id
  }
  const ress = await fetch(api, {
    method: 'POST',
    body: JSON.stringify(body),
    headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
    })
    .then(res => res.json())
    .then(json => json)
  const success = {
    'success': ress.success
  }
  return {data: success}
}

3 主题首页 get/topic_collection/:loginname

allCollections.graphql

type AllCollectionsPayload {
  id: String!
  tab: String
  title: String!
  visit:  Int!
  
}
extend type Query {
  AllCollections(name:String!): [AllCollectionsPayload !]!
}

allCollections.js

require('isomorphic-fetch')
const R = require('ramda')

const url = 'https://cnodejs.org/api/v1/topic_collect/'

module.exports = (event) => {
  const {name} = event.data
  urlWithParams = `${url}${name}`
  let options = {
    method: 'GET'
  }
  return fetch(urlWithParams, options).then((response) => response.json()).then((responseData) => {
    
    const Collections = responseData.data
    const selectPropertyX = (x) => ({id: x.id, tab: x.tab, title: x.title, visit: x.visit_count, author_id: x.author_id})
    const allCollections = R.map(selectPropertyX, Collections)
    return {data: allCollections}
  })
};

查询结果

评论

1 新建评论 post/ topic/:topic_id/replies

createReplies.graphql

type createRepliesPayload {
     topic_id:String!
     accesstoken:String!
     content: String!
     reply_id: String 
     success:Boolean
  
}

extend type Query {
  createReplies(topic_id: String!,accesstoken:String!,content:String!,reply_id:String): createRepliesPayload
}

createReplies.js

'use latest'
import fetch from 'node-fetch'
module.exports = async event => {
  const {topic_id, accesstoken, content, reply_id} = event.data
  const body = {
    accesstoken: accesstoken,
    content: content,
    reply_id: reply_id
  }

   const  api=`https://cnodejs.org/api/v1/topic/${topic_id}/replies`
  const ress = await fetch(api, {
    method: 'POST',
    body: JSON.stringify(body),
    headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
      })
    .then(res => res.json())
    .then(json => json)
  const success = {
    'success': ress.success
  }
  console.log(ress)
  return {data: success}
}

查询结果

2为评论点赞 post /reply/:reply_id/ups

createUps.graphql

type createUpsPayload {
     
     accesstoken:String!
     reply_id: String!
     success:Boolean
     
  
}

extend type Query {
  createUps(accesstoken:String!,reply_id:String): createUpsPayload
}


createUps.js

'use latest'
import fetch from 'node-fetch'

module.exports = async event => {
  const { accesstoken, reply_id} = event.data
  const body = {
    accesstoken: accesstoken
  }

  const api = `https://cnodejs.org/api/v1/reply/${reply_id}/ups`
  const ress = await fetch(api, {
    method: 'POST',
    body: JSON.stringify(body),
    headers: {'Accept': 'application/json', 'Content-Type': 'application/json'}
    
  })
    .then(res => res.json())
    .then(json => json)
  const success = {
    'success': ress.success
  }
  console.log(ress)
  return {data: success}
}

查询结果

用户

1用户详情 get /user/:loginname

getUserInfo.graphql

type UserInfoPayload {
  loginname:  String!
  avatar_url: String!
  score: Int!
  recent_topics:[Json!]!
  recent_replies:[Json!]!
}


extend type Query {
  getUserInfo(name:String!): UserInfoPayload!
}

getUserInfo.js

require('isomorphic-fetch')


const url = 'https://cnodejs.org/api/v1/user/'

module.exports =async  (event) => {
  const {name} = event.data
  urlWithParams = `${url}${name}`
  let options = {
    method: 'GET'
  }
  return fetch(urlWithParams, options)
    .then(response => response.json())
    .then(responseData => {
      return {data: responseData.data}
    })
}

查询结果

消息通知

获取未读消息 get/message/count

getMessageCount.graphql

type MessageCountPayload {
    accesstoken:String!
    data:Int
    success:Boolean
}
extend type Query {
  getMessageCount(accesstoken:String!): MessageCountPayload!
}

getMessageCount.js

require('isomorphic-fetch')
const url = 'https://cnodejs.org/api/v1/message/count?accesstoken='

module.exports =async  (event) => {
  const {accesstoken} = event.data
  urlWithParams = `${url}${accesstoken}`
  let options = {
    method: 'GET'
  }
  return fetch(urlWithParams, options)
    .then(response => response.json())
    .then(responseData => {
      return {data: responseData}
    }) 
}

2获取已读和未读消息 get /messages

getMessages.graphql

		type MessagesPayload {
    
    has_read_messages:[Json]
    hasnot_read_messages:[Json]
    
}


extend type Query {
  getMessages(accesstoken:String!): MessagesPayload!
}

getMessages.js

require('isomorphic-fetch')
const url = 'https://cnodejs.org/api/v1/messages?accesstoken='

module.exports =async  (event) => {
  const {accesstoken} = event.data
  urlWithParams = `${url}${accesstoken}`
  let options = {
    method: 'GET'
  }
  return fetch(urlWithParams, options)
    .then(response => response.json())
    .then(responseData => {
      return {data: responseData.data}
    })
}

查询结果

3全部标记已读 post /message/mark_all

markAll.graphql

type markAllPayload {
    
    accesstoken: String!
    success: Boolean
}
extend type Query {
    markAll(accesstoken:String!): markAllPayload
}

markAll.js

'use latest';
import fetch from 'node-fetch';
const api = 'https://cnodejs.org/api/v1/message/mark_all';
module.exports = async event => {
	const {accesstoken} = event.data;
	const body = {
		accesstoken: accesstoken,
		
	};
	const ress = await fetch(api, {
		method: 'POST',
		body: JSON.stringify(body),
		headers: { Accept: 'application/json', 'Content-Type': 'application/json' }
	})
		.then(res => res.json())
		.then(json => json);
	const success = {
		success: ress.success
	};
	console.log(ress);
	return { data: ress };
};

查询结果

后记,如果要在 客户端使用 GraphQL查询,怎么操作呢?

我使用的的 Apollo-client的方法

在顶层组件导入配置文件

import { ApolloProvider } from 'react-apollo'
import { ApolloClient, HttpLink, InMemoryCache} from 'apollo-client-preset'

const httpLink = new HttpLink({ uri: 'https://api.graph.cool/simple/v1/cjaxudkum2ugf0127kok921bc' })

const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
  connectToDevTools: false
})

export default class App extends React.Component {
  render() {
    return (
      <ApolloProvider client={client}>
        <Navigator />
      </ApolloProvider>
    )
  }
}

实际是单一数据来源的 store, 在组件中可以直接使用查询了

import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
//这里拼接的查询字符串和我们在浏览器中使用的字符串完全相同
//如果在浏览器中正常,这里没有结果,或者结果不一致, 问题就在这里了.
const getCollections = gql`
  query ($name: String!){
    AllCollections(name:$name) {
       id,
       tab,
       title,
       visit
       
    }
  }
`;

class Collection extends Component {
    componentDidMount() {
         
    }
    render() {
      const {navigate} = this.props.navigation;
      //console.log(this.props.data)
      if(this.props.data.loading){
        return (
            <View style={{flex:1,alignItems:'center',justifyContent:'center'}}>
              <Text>Loading...</Text>
            </View>
        )
      }
      //console.log(this.props.data.AllCollections);
      return (
        <List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
          <FlatList
            data={this.props.data.AllCollections}
            renderItem={({ item }) => (
              <ListItem
                roundAvatar
                title={`${item.title}`}
                subtitle={`访问次数:${item.visit}`}
                avatar={{ uri: "https://avatars3.githubusercontent.com/u/3118295?v=4&s=120" }}
                containerStyle={{ borderBottomWidth: 0.2 }}
                onPress={() => navigate('Detail',{id:`${item.id}`})}
              />
            )}
            keyExtractor={item => item.id}
            onEndReachedThreshold={50}
          />
        </List>
      );
    }
  }
 //注入 graphql 查询
  export default graphql(getCollections, {options: ({navigation}) => {
  
    return ({
      variables: {
        name: 'alsotang',
       
      }
    });
  }})(Collection)

GraphQL在这里不仅仅是对 API进行了包装这么简单,它大概提供了一下的好处:

  • :严格了类型,类型错误,或者缺少都会报错
  • :可以根据需求灵活的选择需要返回的数据,例如 列表并不需要 cotent,我们可以选择性的在 graphql 返回数据时省掉这一个字段. 如果 graphQL 服务器和 REST 服务器在同一个主机下,效率会大大提高.
  • :为 API生成了一个单一的入口,多个查询可以一次返回. 减少请求次数
  • : 提供了强有力的测试和说明工具: graphiql, 统一前后端的操作.
  • : graphQL自带说明性质, 有很好的代码提示和自动完成功能. 后续的客户端也会提供这个功能, 出错的机会大大降低了
1 回复

graphiQL 的简单使用

query getUserInfo {
  getUserInfo(name: "phpsmarter") {
    loginname
    recent_topics
    score
  }
}
  • 点击执行按钮▶️
  • 获得结果
  • 或者我们可以在一次查询中返回多个 REST API的数据. 同时获取多个REST API的数据
query getServeralDatas {
  getUserInfo(name: "phpsmarter") {
    loginname
    recent_topics
    score
  }
  
  AllTopics(tab:"share",page:1){
       title
       tab
       visit
  }
}

  • 接下来,可以探索其他的用法了.点击右侧的 schema按钮, 所有查询的名字, 方法, 参数,返回的数据类型,一目了然.

推荐你自己敲入,GraphiQL 提供良好的自动补全

回到顶部