How to Deploy GraphQL as a BFF Gateway on Alibaba Cloud EdgeRoutine

This article explains how to use GraphQL as a Backend‑for‑Frontend gateway on Alibaba Cloud EdgeRoutine, covering the migration of Apollo Server, TypeScript setup, CDN edge caching, resolver implementation, and adding a Playground debugger, with full code examples and deployment tips.

Taobao Frontend Technology
Taobao Frontend Technology
Taobao Frontend Technology
How to Deploy GraphQL as a BFF Gateway on Alibaba Cloud EdgeRoutine

1. Introduction

If you are unfamiliar with GraphQL, you can start with the resources listed below:

Official website: GraphQL CN

Apollo: Apollo GraphQL

Lecture & Demo (Session 3): Control Your Tesla with GraphQL

Technical articles:

GraphQL and Apollo workflow

Replace Redux with GraphQL and Apollo?

TypeScript + GraphQL = TypeGraphQL

Data export based on GraphQL

After four years of effort, GraphQL has become the sole standard for API description, exposure, and invocation in our CCO technology department. Companies such as Facebook, Netflix, GitHub, PayPal, Microsoft, Volkswagen, and Walmart also use GraphQL at large scale, and Apollo secured $130 million in Series D funding. A global front‑end developer survey shows GraphQL as the most interested technology to learn.

1.2 GraphQL Gateway and CDN Edge Computing

EdgeRoutine is Alibaba Cloud CDN's new Serverless platform. It provides a ServiceWorker‑like container that leverages global CDN nodes for distributed elastic computing. It is completely free for internal users (still in trial) and will be officially released in early September.

Query‑type requests constitute a large proportion of traffic, yet they often return data that changes rarely or never. Sending each request to the backend server is inefficient.

We propose using CDN EdgeRoutine as a proxy layer for GraphQL query requests. The first query is forwarded to the GraphQL gateway, which then calls the backend service (e.g., via HSF). The response is cached at the CDN edge, and subsequent identical queries can be served from cache based on TTL rules, dramatically reducing QPS on the origin application.

2. Porting Apollo GraphQL Server

2.1 Build TypeScript Development Environment and Scaffold

We create a TypeScript environment for EdgeRoutine using a custom TypeScript definition library and a local simulator. Webpack serves as the dev server, and ServiceWorker APIs provide hot‑reload capabilities.

2.2 Implement ApolloServer for EdgeRoutine

Apollo does not provide documentation for EdgeRoutine, but by extending ApolloServerBase we can add a listen(path: string) method that registers a fetch event listener.

import { ApolloServerBase } from 'apollo-server-core';
import { handleGraphQLRequest } from './handlers';
/** Apollo GraphQL Server implementation on EdgeRoutine. */
export class ApolloServer extends ApolloServerBase {
  /** Listen for GraphQL POST requests on the specified path. */
  async listen(path = '/graphql') {
    this.assertStarted('listen');
    addEventListener('fetch', async (event: FetchEvent) => {
      const { request } = event;
      if (request.method === 'POST') {
        const url = new URL(request.url);
        if (url.pathname === path) {
          const options = await this.graphQLServerOptions();
          event.respondWith(handleGraphQLRequest(this, request, options));
        }
      }
    });
  }
}

The handleGraphQLRequest function parses the HTTP request body, executes the GraphQL operation, and returns a JSON response while preserving any HTTP headers.

import { GraphQLOptions, GraphQLRequest } from 'apollo-server-core';
import { ApolloServer } from './ApolloServer';
export async function handleGraphQLRequest(
  server: ApolloServer,
  request: Request,
  options: GraphQLOptions
): Promise<Response> {
  let gqlReq: GraphQLRequest;
  try {
    gqlReq = await request.json();
  } catch (e) {
    throw new Error('Error occurred when parsing request body to JSON.');
  }
  const gqlRes = await server.executeOperation(gqlReq);
  const response = new Response(JSON.stringify({ data: gqlRes.data, errors: gqlRes.errors }), {
    headers: { 'content-type': 'application/json' },
  });
  for (const [key, value] of Object.entries(gqlRes.http.headers)) {
    response.headers.set(key, value);
  }
  return response;
}

3. Simple Weather Query GraphQL CDN Proxy Example

3.1 What We Do

We wrap the third‑party weather service (tianqiapi.com) with a GraphQL CDN gateway. The free tier allows 300 QPS per day, so we cache the first request for a city and serve subsequent requests from CDN.

3.2 Weather API Overview

Example request:

Request URL: https://www.tianqiapi.com/free/day?appid={APP_ID}&appsecret={APP_SECRET}&city=%E5%8D%97%E4%BA%AC
Request Method: GET
Status Code: 200 OK

Example response (JSON):

{
  "air": "94",
  "city": "南京",
  "cityid": "101190101",
  "tem": "31",
  "tem_day": "31",
  "tem_night": "24",
  "update_time": "14:12",
  "wea": "多云",
  "wea_img": "yun",
  "win": "东南风",
  "win_meter": "9km/h",
  "win_speed": "2级"
}

3.3 Define GraphQL SDL

type Query {
  versions: Versions!
  weatherOfCity(name: String!): Weather!
}

type City {
  id: ID!
  name: String!
}

type Versions {
  api: String!
  graphql: String!
}

type Weather {
  city: City!
  updateTime: String!
  code: String!
  localized: String!
  tempOfDay: Float!
  tempOfNight: Float!
}

3.4 Implement Resolvers

import { version as graphqlVersion } from 'graphql';
import { apiVersion } from '../api-version';
import { fetchWeatherOfCity } from '../tianqi-api';
export function versions() {
  return { api: apiVersion, graphql: graphqlVersion };
}
export async function weatherOfCity(parent, args: { name: string }) {
  const raw = await fetchWeatherOfCity(args.name).then(res => res.json());
  return {
    city: { id: raw.cityid, name: raw.city },
    updateTime: raw.update_time,
    code: raw.wea_img,
    localized: raw.wea,
    tempOfDay: raw.tem_day,
    tempOfNight: raw.tem_night,
  };
}

3.5 Create and Start Server

import { ApolloServer } from '@ali/apollo-server-edge-routine';
import typeDefs from '../graphql/schema.graphql';
import * as resolvers from '../resolvers';
const server = new ApolloServer({ typeDefs, resolvers });
server.start().then(() => server.listen());

3.6 Build Configuration

tsconfig.json:

{
  "compilerOptions": {
    "alwaysStrict": true,
    "esModuleInterop": true,
    "lib": ["esnext", "webworker"],
    "module": "esnext",
    "moduleResolution": "node",
    "outDir": "./dist",
    "preserveConstEnums": true,
    "removeComments": true,
    "sourceMap": true,
    "strict": true,
    "target": "esnext",
    "types": ["@ali/edge-routine-types"]
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

Webpack fallback for Node polyfills:

resolve: {
  fallback: {
    assert: require.resolve('assert/'),
    buffer: require.resolve('buffer/'),
    crypto: require.resolve('crypto-browserify'),
    os: require.resolve('os-browserify/browser'),
    stream: require.resolve('stream-browserify'),
    zlib: require.resolve('browserify-zlib'),
    util: require.resolve('util/')
  }
}

3.7 Add CDN Cache

export async function fetchWeatherOfCity(city: string) {
  const url = new URL('http://www.tianqiapi.com/free/day');
  url.searchParams.set('appid', '23035354');
  url.searchParams.set('appsecret', '8YvlPNrz');
  url.searchParams.set('city', city);
  const urlString = url.toString();
  if (isCacheSupported()) {
    const cached = await cache.get(urlString);
    if (cached) return cached;
  }
  const response = await fetch(urlString);
  if (isCacheSupported()) cache.put(urlString, response);
  return response;
}

3.8 Add Playground Debugger

addEventListener('fetch', (event) => {
  const response = handleRequest(event.request);
  if (response) event.respondWith(response);
});
function handleRequest(request: Request): Promise<Response> | void {
  const url = new URL(request.url);
  const path = url.pathname;
  if (request.method === 'GET' && path === '/graphql') {
    return Promise.resolve(new Response(rawPlaygroundHTML, { status: 200, headers: { 'content-type': 'text/html' } }));
  }
}

4. Looking Ahead

The demo does not yet show a secondary GraphQL proxy layer, but you can watch a video at graphcdn.io for more details. Future work includes using CDN edge KV storage to cache query results and automatically invalidate caches when mutations affect related entities.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendCDNGraphQLApollo ServerEdgeRoutine
Taobao Frontend Technology
Written by

Taobao Frontend Technology

The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.