GraphQL back-ends are a must-have in the API-industry right now.

GraphQL gives the front-end more control about what data the back-end sends over the wire. This feature alone can lead to traffic reductions, which save money and help with slow mobile connections.

Since GraphQL is better specified than REST, different GraphQL implementations match each other more than REST APIs, which makes them more approachable for front-end developers.

In this article, we will build a GraphQL back-end with Apollo-Server and create a custom analytics extension with Apollo-Servers’ experimental extension system.

Why

GraphQL adds more library code between a request and a response, so wouldn’t it be nice to get a bit more insight into what’s happening when someone requests data from our GraphQL back-end?

A custom extension can hook into the lifecycle of a GraphQL request and allows us to use this information within our analytics tools and services.

We can also leverage that custom extension code with a debugger like the one included in VSCode.

What

GraphQL is an alternative to REST. It’s a specification for a query language and an implementation of this language. It lets us define typed API endpoints and allows clients to request just the data they need.

Apollo Server is an implementation of the GraphQL server specification in JavaScript on the Node.js runtime.

Visual Studio Code, or short VSCode, is an extensible code editor written in JavaScript. It comes with an integrated debugger for Node.js applications.

How

For all this to work, we need Node.js and VSCode installed.

GraphQL Server Setup

To get a simple GraphQL server up and running, we have to create a new Node.js project.

$ mkdir api-server
$ cd api-server
$ npm init -y

We also need the apollo-server and graphql packages from NPM.

$ npm i apollo-server graphql

Now, let’s open the api-server project with VSCode so that we can implement our server.

$ code .

We need a new index.js file with the following code:

const { ApolloServer, gql } = require("apollo-server");

const typeDefs = gql`
  type Task {
    text: String
  }
  type Query {
    tasks: [Task]
  }
`;

const resolvers = {
  Query: {
    tasks: () => [
      { text: "Buy chocolate!" },
      { text: "Pay rent." },
      { text: "Wash dishes." }
    ]
  }
};

const server = new ApolloServer({ typeDefs, resolvers });

server
  .listen({ port: 9999 })
  .then(({ url }) => console.log(`Server running! ${url}`));

Let’s go through it step-by-step.

First, we require GraphQL packages, apollo-server to configure and run our GraphQL server and graphql to parse the schema-definition we will supply to apollo-server.

Then we define a simple schema that only allows querying a task list from our server, and we implement one resolver for the tasks query that sends an array of tasks back to the client.

When we start the server with the following command:

$ node .

It will allow us to send GraphQL queries via the GraphQL Playground, which is Apollos alternative UI to the default GraphiQL UI of Facebook’s reference implementation of a GraphQL server.

To access this UI, we need to open http://localhost:9999/graphql in a browser.

With the help of the GraphQL Playground, we can now query for the tasks to see if everything worked and the server is configured and set up correctly.

{
  tasks {
    text
  }
}

If all worked, we should receive this response:

{
  "data": {
    "tasks": [
      {
        "text": "Buy chocolate!"
      },
      {
        "text": "Pay rent."
      },
      {
        "text": "Wash dishes."
      }
    ]
  }
}

The next step is our analytics extension.

Adding the Analytics Extension

The apollo-server package comes with an experimental extension system that can hook into the different lifecycle events of their GraphQL server implementation.

We can leverage this to build a custom extension that receives data from apollo-server and uses it in some way. To keep this article simple, we will log every lifecycle stage of a request with our extension.

For this we create a new file analyticsExtension.js that has the following content:

class AnalyticsExtension {
  requestDidStart({ queryString, operationName, variables }) {
    console.time("requestDidStart");

    if (!!operationName) {
      console.log("OPERATION");
      console.log(operationName);
    }

    console.log("QUERY");
    console.log(queryString.trim());

    console.log("VARIABLES");
    console.log(variables);

    return (...errors) => {
      if (errors.length) {
        console.log("ERRORS");
        console.log(JSON.stringify(errors, null, 2));
      }
      console.timeEnd("requestDidStart");
    };
  }

  parsingDidStart() {
    console.time("parsingDidStart");
    return () => console.timeEnd("parsingDidStart");
  }

  validationDidStart() {
    console.time("validationDidStart");
    return () => console.timeEnd("validationDidStart");
  }

  executionDidStart() {
    console.time("executionDidStart");
    return () => console.timeEnd("executionDidStart");
  }

  willSendResponse(options) {
    console.log("willSendResponse");
    console.log(JSON.stringify(options.graphqlResponse, null, 2));
  }
}

module.exports = AnalyticsExtension;

An extension is a class that implements some or all of the following methods:

  • requestDidStart
  • parsingDidStart
  • validationDidStart
  • executionDidStart
  • willSendResponse

All of these methods, but willSendResponse can return a callback function that will be called by apollo-server when their lifecycle stage is over. It uses a callback because these stages are asynchronous.

With this, we can define two callbacks per lifecycle stage.

In requestDidStart we log the query data we receive from the client and also set up some timers, so we can check how to log our server took to process the request.

In parsingDidStart, validationDidStart and executionDidStart we only setup timers to check how long each of these lifecycle stages took to execute.

In willSendResponse we log the response our GraphQL server produced.

All this data will be logged locally now, but we could also send it to an analytics and monitoring service like Moesif.

Now that we have defined our custom analytics extension, we have to hook it into our GraphQL server.

For this, we require the extension inside the index.js and pass a new instance to the extension array of the ApolloServer constructor.

const AnalyticsExtension = require("./analyticsExtension");

...

const server = new ApolloServer({
  typeDefs,
  resolvers,
  extensions: [() => new AnalyticsExtension()]
});

...

When we restart our server and send a query, we should now see our analytics output:

QUERY
{
  tasks {
    text
  }
}
VARIABLES
{}
executionDidStart: 0.252ms
willSendResponse
{
  "http": {
    "headers": {}
  },
  "data": {
    "tasks": [
      {
        "text": "Buy chocolate!"
      },
      {
        "text": "Pay rent."
      },
      {
        "text": "Wash dishes."
      }
    ]
  }
}
requestDidStart: 1.558ms

Every lifecycle method logs its timings and some also the data that was received or produced in its stage.

With our custom analytics extension in place, we can now go and debug our system.

Debug with VSCode

To set up the VSCode debugger, we need to create a .vscode/launch.json that tells VSCode how our server can be started.

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Server",
      "program": "${workspaceFolder}/index.js"
    }
  ]
}

If we open the debug view in VSCode, we can now see our “Debug Server” launch-configuration in the drop-down in the top left.

VSCode Debugger Screenshot

If everything has been set up correctly, we can now sprinkle our resolvers functions inside index.js and the AnalyticsExtension lifecycle methods inside analyticsExtenion.js with breakpoints.

If we hit one of these breakpoints, we can use the debugging controls of VSCode to navigate our code. We could, for example, jump out of our own extension code and into the function that called our lifecycle method.

<img alt=”VSCode Breakpoint Screenshot” src=”https://blog.moesif.com/images/posts/technical/graphql-analytics/breakpoint.png width=”500”>

With this setup, we now have more control over our system locally and even when it runs somewhere in the cloud.

Conclusion

The GraphQL server implementation of Apollo offers us a very convenient method to integrate with monitoring services of our choice. The extension system supplies us with granular access to our requests lifecycle stages so we can measure what is happening in our back-end.

In tandem with VSCode’s debugger, we can use our custom extensions code to get a better understanding of the code-paths our server takes at runtime.

All in all, this gives us a better understanding of what is happening in our GraphQL servers and where we can improve them in the future.

Moesif is the most advanced API Analytics platform, supporting REST, GraphQL and more. Thousands of API developers process billions of API calls through Moesif for debugging, monitoring and discovering insights.

Learn More

Leave a comment