+24

Hướng dẫn tạo và deploy GraphQL server KHÔNG CẦN SERVER với Cloudflare

👋👋👋 Hello hello, xin chào tất cả anh em. Anh em nào đã lỡ vào đây rồi thì comment chào nhau một cái nhé cho đông vui nhé!

Ngày hôm nay, mình sẽ chia sẻ tới các bạn một chủ đề mới cũng không kém phần thú vị so với bài Giới thiệu về Microservices Architecture vừa rồi. Với các bạn quan tâm tới GraphQL và Serverless thì bài viết này là dành cho bạn.

👉️ Prerequisites

  • Đã nắm được cơ bản về GraphQL: query, mutation
  • Biết về Apollo Server thì càng tốt (optional)
  • Biết về Cloudflare Workers thì càng tốt (optional)

Trong bài viết này, mình sẽ hướng dẫn cách build và deploy một GraphQL server lên Cloudflare Workers. Bạn không tốn một đồng chi phí server nào cả do dùng Cloudflare Workers với Free plan: 100,000 requests / ngày.

👉️ Apollo Server

GraphQL Server mình dùng trong bài viết này là Apollo Server. Apollo Server là một trong số các library thông dụng của Node.js để dựng GraphQL Server. Một số điểm nổi bật đó là:

  • Dựng GraphQL server nhanh chóng
  • Dễ dàng mở rộng, làm việc trong đội hình nhiều team
  • Implement sẵn các cơ chế cache trên cả server và client side
  • Hỗ trợ cơ chế Federation, giúp tách GraphQL server thành nhiều GraphQL server nhỏ hơn (gọi là sub-graph)
  • Document cung cấp rõ ràng, tutorial hệ thống hóa chi tiết và dễ tiếp cận

(Hình 1: Apollo Federation - Nguồn: ApolloGraphQL)

Ra mắt từ khá lâu, Apollo Server mới release v4 vào tháng 10 năm 2022 (mình nhớ không nhầm thì vậy), với một số breaking change. Trong đó thì các phần tích hợp Apollo Server vào các framework như: Express, H3, Cloudflare, AWS Lambda... bị xóa bỏ hoàn toàn khỏi official package của Apollo Server.

Thời điểm đó Cloudflare cũng chưa có nhận phát triển lại phần integration. Cộng với việc mình đang maintain một repo-template để chạy Apollo Server v3 trên Cloudflare nên mình đã phát triển luôn một phiên bản integration để tích hợp Cloudflare Workers và Apollo Server v4.

👉️ Template Repository

Bài viết này sử dụng Apollo Server v4 và một phiên bản tích hợp với package apollo-server-integrations-cloudflare-workers do chính mình phát triển. Hiện tại package này mới được publish trên NPM dưới namespace @as-integrations/cloudfare-workers. Tạm thời đang để v0.1.0 do mình chưa có thời gian viết bổ sung tests.

https://www.npmjs.com/package/@as-integrations/cloudflare-workers

Các bạn yêu thích thì vào repo thả cho mình một star với nhé! 😉

Để cài đặt vào project Cloudflare Workers đã có sẵn, sử dụng NPM như sau:

npm install @apollo/server graphql @as-integrations/cloudflare-workers

Trong bài viết này, mình demo bằng cách sử dụng một source code được mình setup sẵn dưới dạng một Template Repo trên Github. Bạn chỉ cần nhấn nút xanh lá cây Use this template là dùng thôi:

image.png

(Hình 2: Khởi tạo source code với Github Template)

Link repo: https://github.com/kimyvgy/worker-apollo-server-template


👉️ Viết code GraphQL Server

Tạo file schema.graphql

Không giống như REST, các request lên server sẽ chia làm hai loại cơ bản là QueryMutation:

  • Query: Client muốn lấy dữ liệu về một resource nào đó về. VD: GetAuthor(username: "huukimit")
  • Mutation: Client muốn thực hiện action sửa đổi/update dữ liệu. VD: FollowAuthor(username: "huukimit")

Các mô tả về Query và Mutation được khai báo trong file schema.graphql. Cả client và server đều sử dụng file này để generate ra code khai báo Type của từng Resource tương ứng.

Trong ví dụ này, mình sẽ tạo schema như sau:

type Query {
  example: String!
  pokemon(id: ID!): Pokemon
}

type PokemonSprites {
  front_default: String!
  front_shiny: String!
  front_female: String!
  front_shiny_female: String!
  back_default: String!
  back_shiny: String!
  back_female: String!
  back_shiny_female: String!
}

type Pokemon {
  id: ID!
  name: String!
  height: Int!
  weight: Int!
  sprites: PokemonSprites!
}

Hiểu đơn giản là phía server cung cấp 2 query:

  • example: Không cần tham số, trả về một string
  • pokemon: Truy vấn thông tin một Pokemon theo một ID xác định, với các trường nhất định. Bạn đọc chắc là cũng hiểu ngay.

Các thông tin về pokemon mình sẽ viết resolver lấy từ service https://pokeapi.co/api/v2/.

GraphQL codegen

Cloudflare chạy JavaScript nên mình sử dụng các công cụ của graphql-codegen để tự động tạo các kiểu dữ liệu để dùng cho TypeScript + React. File cấu hình này cũng đã được tạo sẵn trong repo.

schema: "./src/schema.graphql"

generates:
  ./src/@types/schema.generated.ts:
    plugins:
      - typescript
      - typescript-resolvers
    config:
      enumsAsConst: true
      namingConvention:
        enumValues: keep

Chạy lệnh sau để chạy codegen:

npm run generate

Command thực ra là alias của:

npx graphql-codegen --config codegen.yml

Chạy GraphQL Server

Để tạo server, mình khởi tạo instance cho Apollo Server cũng khá đơn giản, với vài dòng như sau:

const server = new ApolloServer<ContextValue>({
  typeDefs,
  resolvers,
  introspection: true,
  plugins: [
    ApolloServerPluginLandingPageLocalDefault({ footer: false }),
  ],
});

Full source code tích hợp với Cloudflare Wokers bằng integration @as-integrations/cloudflare-workers sẽ như sau:

import type { CloudflareWorkersHandler } from '@as-integrations/cloudflare-workers';

import { ApolloServer } from '@apollo/server';
import { startServerAndCreateCloudflareWorkersHandler } from '@as-integrations/cloudflare-workers';
import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default';

interface ApolloDataSources {
  pokemonAPI: PokemonAPI;
}

interface ContextValue {
  token: string;
  dataSources: ApolloDataSources;
};

const server = new ApolloServer<ContextValue>({
  typeDefs,
  resolvers,
  introspection: true,
  plugins: [
    ApolloServerPluginLandingPageLocalDefault({ footer: false }),
  ],
});

const handleGraphQLRequest: CloudflareWorkersHandler = startServerAndCreateCloudflareWorkersHandler(server, {
  context: async ({ request }) => {
    const token = request.headers.token || '';
    const cache = server.cache;

    const dataSources: ApolloDataSources = {
      pokemonAPI: new PokemonAPI({ cache, token }),
    };

    return { dataSources, token };
  },
});

addEventListener((e) => handleGraphQLRequest(e.request as Request));

Trong đó:

  • Hàm chạy chính của graphql server là startServerAndCreateCloudflareWorkersHandler. Nó sẽ chạy apollo server trên Cloudflare Workers, sau đó trả về một handler có kiểu CloudflareWorkersHandler.
  • CloudflareWorkersHandler là một function, nhận vào tham số kiểu Request và trả về Response để dùng trực tiếp trong Cloudflare Workers.

Viết resolver

File schema chỉ là mô tả về các Query, còn query đó xử lý logic như nào thì chúng ta cần viết thêm resolvers cho Apollo Server. Trong ví dụ này mình viết đơn giản như sau:

import { Resolvers } from "./@types/schema.generated";

const resolvers: Resolvers<ApolloContext> = {
  Query: {
    example: () => {
      return 'Hello world!';
    },

    pokemon: (_source, { id }, { dataSources }) => {
      return dataSources.pokemonAPI.getPokemon(id);
    },
  }
}

export default resolvers;

Trong đó:

  • example: mình trả về câu "Hello world!"
  • pokemon: mình sẽ dùng REST datasource để gửi request tới PokemonAPI.

Đối với REST Datasource, mình đã implement sẵn KV Cache để cache lại response vào Cloudflare KV. Cách sử dụng bạn tham khảo thêm trong source code và repo nhé. 😉

Development

Dưới local, chạy lệnh npm run dev để build và start GraphQL.

npm run dev

Lệnh này thực hiện 2 việc:

  • Chạy lại codegen khi schema file thay đổi
  • Rebuild lại worker khi source code thay đổi

Truy cập http://localhost:8787 để vào trang Sandbox test thử GraphQL:

image.png

(Apollo GraphQL Sandbox)

Deploy

Sử dụng wrangler v2 để deploy source code lên Cloudflare.

npm run deploy
yarn run v1.22.19
$ wrangler publish
 ⛅️ wrangler 2.1.9
-------------------
Running custom build: npm run generate && npm run build

> generate
> graphql-codegen --config codegen.yml

✔ Parse Configuration
✔ Generate outputs

> build
> NODE_ENV=production node worker.build.js

Your worker has access to the following bindings:
- KV Namespaces:
  - GRAPHQL_CACHE: dbdc624f2c684f1bb88fa38ab249a13e
- Vars:
  - GRAPHQL_BASE_ENDPOINT: "/"
  - GRAPHQL_KV_CACHE: "true"
Total Upload: 1520.16 KiB / gzip: 285.68 KiB
Uploaded worker-apollo-server (2.71 sec)
Published worker-apollo-server (0.28 sec)
  https://worker-apollo-server.webee-asia.workers.dev
Done in 7.29s.

CI/CD

Trong repo template, mình cũng đã setup sẵn Github Actions để tự động deploy. Bạn cần cung cấp API Token + Account ID của tài khoản Cloudflare vào cho github action là OK.

image.png

(Thêm repo secrets cho CI/CD để auto deploy)

image.png

(Github Actions Pipeline)

Tổng kết

Trên đây là bài viết chia sẻ cách tích hợp và deploy Apollo Server với Cloudflare Workers. Chi tiết tham khảo:


⚠️ CHỖ NÀY PHẢI CHÚ Ý:

  • Nếu bạn quan tâm tới chủ đề này và muốn mình ra thêm các bài viết tương tự, hãy comment xuống dưới nhé!
  • Đừng quên cho mình một upvote / bookmark / follow để ủng hộ mình và có nhận được thông báo khi có bài viết mới nha.
  • Donate cho mình nếu bạn thấy nội dung này hữu ích và muốn mình làm thêm về các topic bạn mong đợi: Mono, Paypal, Bank
  • => Link donate: https://kimyvgy.webee.asia

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí