兩篇文章學完了GraphQL(基礎篇, 原理篇),接下去即是實踐的過程,這個實踐咱們使用了以下技術棧去實現一套任務管理系統,源碼就不公開了, 等穩定後再發布。效果以下:css
使用的技術棧有:前端
以下圖:react
分爲兩大部分:client
和server
,其中client
的目錄結構以下:git
各個目錄的解釋在圖中已經體現。github
server
端的目錄結構以下:web
各個目錄的含義的解釋在圖中已經體現。typescript
由於咱們主要是講graphql的應用,因此其他的細節忽略不說。至於Nest框架的使用,請參考文檔Nest.js數據庫
實踐GraphQL咱們不會直接用graphql-js,而是使用功能更加豐富、社區支持更多apollo-graphql
。其文檔編寫的也是很詳盡,基本上全部的問題均可以在文檔上找到答案。推薦新手能夠先按照Get started來入手編程
由於咱們使用了apollo-boost
,因此在前端入口文件上,要拿這個包進行一些初始化,獲得apolloClient的實例(無關的代碼已經去掉):json
import React from 'react';
import ReactDOM from 'react-dom';
import { ApolloProvider } from 'react-apollo';
import ApolloClient from 'apollo-boost'
... ...
const GW_BASE_URL = process.env.NODE_ENV === 'production' ? '/graphql' : 'http://127.0.0.1:8888/graphql'
const client = new ApolloClient({
uri: GW_BASE_URL,
// 須要設置這個,這樣每次請求的時候認證信息纔會帶上
fetchOptions: {
credentials: 'include',
},
// 緩存讀取配置
clientState: {
typeDefs,
resolvers,
},
// 設置這個是爲了配合jwt
request: async (operation) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : '',
Origin: location.href,
},
});
},
// 設置全局錯誤處理信息,這樣就不用每一個請求都進行error處理
onError: (errObj) => {
if (errObj.graphQLErrors) {
const errorMsg = errObj.graphQLErrors[0].message;
if (errorMsg === '身份信息已過時,請從新登陸') {
... ...
message.info(errorMsg, 3, () => location.hash = '#/user/login');
} else if (errorMsg && (errorMsg as any).statusCode === 403) {
message.error('權限不足,請不要重試');
} else {
message.error(errorMsg);
}
}
},
});
ReactDOM.render(
<ApolloProvider client={client}>
<LocaleProvider>
<App />
</LocaleProvider>
</ApolloProvider>,
document.getElementById('root'),
);
複製代碼
每一個頁面都會新建三個文件:
graphql.ts
index.scss
index.tsx
複製代碼
其中graphql.ts
定義了客戶端的請求,好比:
import gql from 'graphql-tag';
// 用來查詢全部的用戶
export const QUERY_USERS = gql`
query {
userList {
id
roles
team
mobile
staffCode
email
username
}
}
`;
複製代碼
然後在index.tsx
文件中就可使用這個查詢語句,以下:
整個流程是很清晰的,由於使用了typescript,因此在客戶端能夠引用到服務端定義的返回類型,從而提升了代碼編寫的速度。
react-apollo
目前發現了個bug,若是我返回的數據的層級太深,好比達到了4層以上,數據更新到緩存的時候便會出錯。由於服務端使用了Nest.js,因此沒有直接用apollo提供的服務器,而是用了Nest框架封裝出來適用於Nest框架的graphql包graphql。該包仍是提供了不少功能的。
在app.modules.ts
中,咱們要去初始化graphql模塊(無關代碼已忽略):
@Module({
imports: [
GraphQLModule.forRoot({
// 指定服務端schema存放的位置
typePaths: ['graphql/schema.graphql'],
// 配置了該選項,能夠自動根據代碼生成schema
autoSchemaFile: path.join(__dirname, 'graphql/schema.graphql'),
buildSchemaOptions: {
},
// 能夠自動生成types文件
// definitions: {
// path: path.join(__dirname, 'types/graphql.ts'),
// },
debug: true,
playground: true,
context: ({ req }) => ({ req }), // 必定要這裏設置req到上下文中,不然在guard中是拿不到這個req參數的
}),
],
controllers: [],
providers: []
})
複製代碼
每一個業務目錄都會存在這麼些文件:
咱們在dto
目錄下定義三種類型文件:xx.args.ts
/xx.input.ts
/xx.model.ts
,分別定義下面三種狀況
然後在xx.resolver.ts
中實現resolve函數,藉助於修飾器,好比:
import { Query, Resolver, Args, Mutation } from '@nestjs/graphql'
@Resolver('User')
export class UserResolver {
@Query(returns => [UserItem])
... ...
async userList(): Promise<UserItem[]>{
return this.userService.getUserList();
}
}
複製代碼
UserItem
在這裏(user.model.ts
)定義:
import { ObjectType, Field, ID, registerEnumType, Int } from 'type-graphql'
... ...
@ObjectType()
export class UserItem {
@Field(type => ID, {nullable: true})
_id?: number;
@Field({nullable: true})
username?: string;
@Field({nullable: true})
email?: string;
... ...
}
複製代碼
如此便完成了整個服務端數據流的過程。看着是否是很easy啊?
至此,三篇關於GraphQL的文章到此結束了,花了不少時間斷斷續續地學習,但願能夠給你們呈現一份不同地文章,供你們思考。後續我所在的公司網關團隊會持續實踐GraphQL,爭取貢獻出更多的解決方案。