Hands-On Full-Stack Web Development with GraphQL and React
上QQ阅读APP看书,第一时间看更新

Combining Express.js with Apollo

First things first; we need to install the Apollo and GraphQL dependencies:

npm install --save apollo-server-express graphql graphql-tools

Apollo offers an Express.js-specific package that integrates itself into the web server. There is also a standalone version without Express.js. Apollo allows you to use the available Express.js middleware. In some scenarios, you may need to offer non-GraphQL routes to proprietary clients who do not implement GraphQL or are not able to understand JSON responses. There are still reasons to offer some fallbacks to GraphQL. In those cases, you can rely on Express.js, since you are already using it.

Create a separate folder for services. A service can be GraphQL or other routes:

mkdir src/server/services/
mkdir src/server/services/graphql

Our GraphQL service must handle multiple things for initialization. Let's go through all of them one by one:

  1. We require the apollo-server-express and graphql-tools packages.
import { ApolloServer } from 'apollo-server-express';
import { makeExecutableSchema } from 'graphql-tools';
  1. We must combine the GraphQL schema with the resolver functions. We import the corresponding schema and resolver functions at the top from separate files. The GraphQL schema is the representation of the API, that is, the data and functions a client can request or run. Resolver functions are the implementation of the schema. Both need to match 100 percent. You cannot return a field or run a mutation that is not inside the schema.
import Resolvers from './resolvers';
import Schema from './schema';
  1. The makeExecutableSchema function of the graphql-tools package merges the GraphQL schema and the resolver functions, resolving the data we are going to write. The makeExecutableSchema function throws an error when you define a query or mutation that is not in the schema. The resulting schema is executable by our GraphQL server resolving the data or running the mutations we request.
const executableSchema = makeExecutableSchema({
typeDefs: Schema,
resolvers: Resolvers
});
  1. We pass this as a schema parameter to the Apollo Server. The context property contains the request object of Express.js. In our resolver functions, we can access the request if we need to.
const server = new ApolloServer({
schema: executableSchema,
context: ({ req }) => req
});
  1. This index.js file exports the initialized server object, which handles all GraphQL requests.
export default server;

Now that we are exporting the Apollo Server, it needs to be imported somewhere else, of course. I find it convenient to have one index.js file on the services layer so that we only rely on this file if a new service is added.

Create an index.js file in the services folder and enter the following code:

import graphql from './graphql';

export default {
graphql,
};

The preceding code requires our index.js file from the graphql folder and re-exports all services in one big object. We can define further services here if we need them.

To make our GraphQL server publicly accessible to our clients, we are going to bind the Apollo Server to the /graphql path.

Import the services index.js file in the server/index.js file as follows:

import services from './services';

The services object only holds the graphql index. Now we must bind the GraphQL server to the Express.js web server with the following code:

const serviceNames = Object.keys(services);

for (let i = 0; i < serviceNames.length; i += 1) {
const name = serviceNames[i];
if (name === 'graphql') {
services[name].applyMiddleware({ app });
} else {
app.use(`/${name}`, services[name]);
}
}

For convenience, we loop through all indexes of the services object and use the index as the name of the route the service will be bound to. The path would be /example for the example index in the services object. For a typical service, such as a REST interface, we rely on the standard app.use method of Express.js.

Since the Apollo Server is kind of special, when binding it to Express.js, we need to run the applyMiddleware function provided by the initialized Apollo Server and avoid using the app.use function of Express.js. Apollo automatically binds itself to the /graphql path because it is the default option. You could also include a path parameter if you want it to respond from a custom route.

Two things are missing now: the schema and the resolvers. The schema is next on our to-do list.