[course] Fullstack TypeScript
keywords: typescript
, graphQL
本篇非原創文章,內容整理自 Fullstack TypeScript @ Frontend Masters
練習用專案:fem-fullstack-ts @ gitlab
GraphQL Intro
Why GraphQL
想像原本的 API 能夠滿足 UI 的需求,但在某一次改版時,UI 需要額外多一個欄位的資料,但這個欄位是來自另一個 resource / model 的。
這時候我們可以有幾種不同的作法:
- 使用原本的 API endpoint,並且多加上所需的新欄位
- 可能會有 over-fetching 的情況,同一支 API 回傳的內容越來越多,甚至可能包含到 UI 上使用不到的資料
- 建立一支新的 API,在那支 API 只回傳所需的新欄位,在載入 UI 的時候同時打兩支不同的 API
- 很難維護,將會不清楚那些 API 在那裡被使用
- client 需要打很多 API,server 的 QPS 會變高,並且增加了 race condition 問題發生的機可能性
- 建立一支新的 API endpoint,並且在這支 API 回傳所有需要的資料,包含原有的欄位和新欄位,以滿足該頁面的需求
- 違反了原本 RESTful model 的慣例
- 調整現有的 API,讓它可以根據需要回傳不同類型的資料(例如,使用 quertParam)
- 增加測試的複雜度
從上面的情境可以看到,不論做的是哪一種選擇,都還是有其缺點,而 GraphQL 的目的就是要解決這樣的問題 —— client 只去請求它所需的資料,且這個資料可以是來自許多不同的 resources / models。
Basic Syntax
const typeDefs = gql`
type Query {
// 驚嘆號表示 required (non-nullable),預設都是 nullable
currentUser: User!
// array of Suggestion
// []! 的意思是,一定會有 Array 存在
// [Suggestion!] 的意思是,在 Array 中不會有 null
// 所以空陣列還是合法的
suggestions: [Suggestion!]!
}
type User {
id: String!
name: String!
handle: String!
coverUrl: String!
avatarUrl: String!
createdAt: String!
updatedAt: String!
}
type Suggestion {
name: String!
handle: String!
avatarUrl: String!
reason: String!
}
`;
Server: Setup Apollo Server
- Setup Apollo Server @ gitlab commit
- Hello Apollo @ Frontend Masters
使用 apollo-server-express
提供的 ApolloServer
來建立 Apollo Server。
Server: Resolver
Write Schema
feat(resolver): move schema into a single file @ gitlab commit
可以把 graphQL schema 的檔案拆成獨立的檔案(例如,xxx.graphql
),這裡需要搭配 @graphql-tools
提供的方法,像是 loadSchemaSync
、GraphQLFileLoader
、addResolersToSchema
等等。
Write Resolver
最後,也可也把一開始定義好的 resolver 拆成獨立的檔案。
// https://gitlab.com/PJCHENder/fem-fullstack-ts/-/commit/b3338de9ec2d5acaffa17acd21788a904a781a28
const resolvers = {
Query: {
currentUser: () => {
return {
id: '123',
name: 'John Doe',
handle: 'johndoe',
coverUrl: '',
avatarUrl: '',
createdAt: '',
updatedAt: '',
};
},
suggestions: () => {
return [];
},
},
};
Typed Resolver
可以使用 @graphql-codegen
這個套件來自動根據 resolver 產生出對應的 TypeScript 型別,例如 resolvers-types.generated
。接著即可使用這支檔案中所定義好的型別,像是 Resolvers
和 QueryResolvers
。
@graphql-codegen
會去讀取專案中 codegen.yml
的設定檔。
如果是用 VSCode 的話,跑完 codegen 後,有很高的機會要記得 restart TS server 和 restart ESLint server。
Typed ResolverContenxt
feat(resolver): typed the ResolverContext @ gitlab commit
我們可以根據需要自己定義 Context 的 interface:
export interface TwitterResolverContext {
db: Db;
}
Client: Setup Apollo Client
feat(apollo-client): setup apollo-client @ gitlab commit
使用 @apollo/client
提供的 ApolloClient
、ApolloProvider
和 InMemoryCache
來建立與 ApolloServer 的連線。
Client: Query Data
一般來說,不論是 Query 或 Mutation 都可以分成這三個步驟:
- Create Query:使用
gql
來撰寫 GraphQL 用的 Query 或 Mutation - GraphQL Codegen:透過
@graphql-codegen
來產生對應的型別和可以用來呼叫的方法 - Query Data:使用 codegen 產生的檔案中所提供的方法
useGetCurrentUserQuery
來 query 我們所需的資料。
如果是用 VSCode 的話,跑完 codegen 後,有很高的機會要記得 restart TS server 和 restart ESLint server。
GraphQL
幾個關鍵字:
- Query
- Nested Resolver
- Mutation
- Union
資料來源
- Fullstack TypeScript @ Frontend Master