Xây dựng ứng dụng React với Prisma, GraphQL, apollo

GraphQl là gì

Theo định nghĩa GraphQL là Graph Query Language, một giao thức giao tiếp giữa client và server được Facebook tạo ra năm 2012. Client truy vấn đến server theo các format có sẵn Client truy vấn đến server qua duy nhất một endpoint, với RestAPI ta phải định nghĩa các endpoint khác nhau trên phía server. Bên cạnh đó, với 1 endpoint duy nhất, GraphQl vẫn cho phép ta xử lý những query phức tạp và trả về output tùy thuộc vào Query của client. GraphQL giống với một layer nắm giữa server và datasource nhận được query ở Client, validate và trả về data.

Backend

Backend mình sử dụng prisma. Prisma sử dụng appollo-server để tạo một server trỏ về endpoint của Graphql. Có thể hiểu prisma là một secure API layer có nằm giữa Client và database. Prisma cung cấp Authentication, Logging, quản lý Rate-limit .... Client sẽ không Query trực tiếp lên GraphQL mà sẽ qua Prisma.

Backend mình sử dụng graphql-yoga và prisma. Graphql-yoga sử dụng apolo-server có chức năng tạo ra một server ở giữa Client và database, mọi query từ Client đều sẽ qua Server này. Config như sau

const { GraphQLServer } = require('graphql-yoga')
const { Prisma } = require('prisma-binding')

const server = new GraphQLServer({
  typeDefs: './src/schema.graphql',
  resolvers,
  context: req => ({
    ...req,
    db: new Prisma({
      typeDefs: 'src/generated/prisma.graphql',
      endpoint: 'http://localhost:4466/my-app/dev',
      secret: 'mysecret123',
      debug: true,
    }),
  }),
})

server.start(() => console.log('Server is running on http://localhost:4000'))

Sau khi cài đặt xong các package thì bạn có thể chạy prisma deploy để deploy server của mình trên cluster, Cluster được định nghĩa trong file prisma.yml. Ở đây mình để là local nê prisma sẽ build trên local Bạn có thể chạy prisma init, prisma sẽ tự động cài đặt và config cho mình, có các tuỳ chọn cho nodejs, typescript ...

Client

Client mình sử dụng apolo-client và react-apollo, graphql-tag cho phép ta có thể query được graphql từ client ApolloClient sử dụng apollo-cache-inmemory làm data store. Khởi tạo Apolloclient trỏ vào địa chỉ localhost:4000

import { InMemoryCache } from 'apollo-cache-inmemory'

const httpLink = new HttpLink({ uri: 'http://localhost:4000' })

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

Router chúng ta vẫn viết như trước

<Router>
      <React.Fragment>
        <div></div>
      </React.Fragment>
</Router>

Để sử dụng được apollo với react, ta cần wrap Component với ApolloProvider như sau

ReactDOM.render(
  <ApolloProvider client={client}>
    <App></App>
  </ApolloProvider>,
  document.getElementById('root'),
)

Client mình sử dụng apolo-client và react-apollo, graphql-tag cho phép ta có thể query được graphql từ client

Chúng ta có thể gửi request lên server sử dụng 2 package graphql-tag và react-appolo như sau graphql-tag cho phép ta có thể viết truy vấn graphql trong Javascript một cách dễ dàng

const FEED_QUERY = gql`
  query FeedQuery {
    feed {
      id
      text
      title
      isPublished
    }
  }
`

export default graphql(FEED_QUERY, {
  name: 'feedQuery', 
  options: {
    fetchPolicy: 'network-only',
  },
})(FeedPage)

react-apollo cho phép ta inject props với name và option vào Component. Ở đây ta inject feedQuery vào Component FeedPage với option fetchPolicy: network-only'. fetchPolicy là một option của API apollograph cho phép ta có thể tùy chỉnh cách mà Component tương tác với Apollo data cache. Mặc định thì Component sẽ lấy data từ datacache trước, và nếu mà tất cả các data của query đều nằm trong cache thì Apollo đơn giản chỉ trả về data từ cache. Nếu tất cả data của query không nằm trong cache thì Apollo sẽ xử lý request của bạn sử dụng network-interface. Ở đây mình sử dụng network-only, dữ liệu khởi tại của query sẽ không được lấy từ cache.

Sau khi đã inject

class FeedPage extends React.Component {
  componentWillReceiveProps(nextProps) {
    if (this.props.location.key !== nextProps.location.key) {
      this.props.feedQuery.refetch()
    }
  }

  render() {
    if (this.props.feedQuery.loading) {
      return (
        <div className="flex w-100 h-100 items-center justify-center pt7">
          <div>Loading (from {process.env.REACT_APP_GRAPHQL_ENDPOINT})</div>
        </div>
      )
    }

    return (
      <React.Fragment>
        <h1>Feed</h1>
        {this.props.feedQuery.feed &&
          this.props.feedQuery.feed.map(post => (
            <Post
              key={post.id}
              post={post}
              refresh={() => this.props.feedQuery.refetch()}
              isDraft={!post.isPublished}
            />
          ))}
        {this.props.children}
      </React.Fragment>
    )
  }
}

sau khi inject query vào props của Component mình thực hiện lấy dữ liệu bằng hàm refetch() như ở trên. Trên đây là một số thứ cơ bản mình tìm hiểu được. Bài viết sau mình sẽ cố gắng viết kĩ hơn về cache, network layer của apollographql

Tài liệu tham khảo

https://github.com/graphcool/prisma

http://graphql.org/learn/