GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. To setup a serverless, auto-scaling GraphQL API, we can use Firebase Cloud Functions to seamlessly deploy and test our server!
Prerequisites
This tutorial assumes you have an existing Firebase project with Cloud Functions enabled.
Implementation
To serve our GraphQL API, we are going to take advantage of the ability to pass an Express.js app to the onRequest()
argument for an HTTP function (see example below). For more info, read Call functions via HTTP requests.
1import * as express from 'express';2const app = express();34// Expose Express API as a single Cloud Function:5exports.graphql = functions.https.onRequest(app);
Step 1: Create Apollo GraphQL Express server
Start by creating a simple Apollo Express Server. Apollo Server is an implementation of GraphQL and integrates easily with several popular Node.js middleware libraries, including Express. For more info, read Integrating with Node.js middleware.
1// functions/graphql/server.js23import * as express from 'express';4import { ApolloServer } from 'apollo-server-express';56import schema from './schema';7import resolvers from './resolvers';89function createGraphQLServer() {10 const app = express();1112 const server = new ApolloServer({13 typeDefs: schema,14 resolvers,15 // Enable GraphQL GUI16 introspection: true,17 playground: true18 });1920 server.applyMiddleware({ app, path: '/', cors: true });2122 return app;23}2425export default createGraphQLServer;
Step 2: Define your GraphQL schema
Construct a schema using the GraphQL schema language. The schema represents the structure of your data set and defines what data clients can query.
1// functions/graphql/schema.js23import { gql } from 'apollo-server-express';45const schema = gql`6 type Query {7 hello: String8 }9`;1011export default schema;
Step 3: Define a resolver
Resolvers tell Apollo Server how to fetch the data associated with a particular type. In this example, we haved hardcoded the hello
field to return “Hello world!“. Though, you could just as easily make a database call to return a value instead.
1// functions/graphql/resolvers.js23// Provide resolver functions for your schema fields.4const resolvers = {5 Query: {6 hello: () => 'Hello world!'7 }8};910export default resolvers;
Step 4: Connect cloud function handler
With the server, schema and resolvers ready, we can go ahead and hook up our Apollo Express Server to an HTTP function.
1// functions/index.js23import { https } from 'firebase-functions';4import createGraphQLServer from './graphql/server';56const server = createGraphQLServer();78// Graphql API9// https://<region-name>-<project-name>.cloudfunctions.net/graphql10const graphql = https.onRequest(server);1112export { graphql };
Step 5: Test server locally
To ensure everything is in working order, we can use the Firebase Emulator to run our function locally before deploying.
1firebase emulators:start --only functions
You should now have a local url like http://localhost:5001/<project-id>/<region>/graphql
that you can open up to view the GraphQL GUI that gets shipped with Apollo.
Limitations
This approach to running a serverless GraphQL API has one major drawback; You cannot use GraphQL subscriptions. GraphQL subscriptions are a way to push data from the server to clients that choose to listen to real time messages from the server. To do this, GraphQL uses websockets to maintain a long-lasting connection with a client. Currently, Google Cloud Functions don’t support subscriptions.
Be careful with polling
Polling provides near-real-time synchronisation with your server by causing a query to execute periodically at a specified interval (see example below).
1const { loading, error, data } = useQuery(GET_DOG_PHOTO, {2 variables: { breed },3 skip: !breed,4 pollInterval: 500,5});
Polling is enabled by passing the pollInterval
configuration option to the useQuery
hook. By setting the pollInterval
to 500, you’ll be making a fetch request to the server every 0.5 seconds. This can be problematic if our GraphQL API is running on a cloud function because it means we are triggering an invocation every 0.5 seconds.
In testing, I managed to trigger over a million invovations in less than 24 hours. Therefore, with a current flat rate of $0.4 per million invocations, the cost of polling using cloud functions is inefficient.
Conclusion
By design, Cloud Functions only handles one request per instance, ensuring each request has the full amount of compute and memory allocated to it. This may make rapid scaling slower with Cloud Functions. Therefore, I would not recommend using the above approach in a production environment.
For setting up a productiong grade GraphQL API server, I would containerise the above Apollo server and deploy it inside a fully-managed serverless container platform, such as Google App Engine (Flex) or Google Cloud Run.