DEV Community

loading...

Azure Functions + GraphQL + Twitch, Making One Endpoint to Rule Them All Part 1

Erik Guzman
Developer, streamer, Microsoft MVP and Twitch dev contributer. I love to code and share the knowledge. Check me out at https://www.twitch.tv/talk2megooseman
Updated on ・6 min read

Objective

On May 1st, Twitch made a drastic change to their new API version, "Helix." All API consumers MUST authenticate when using the API, gone are the days of just needing a Client-ID to fetch general data. For my applications to continue working, I wanted to use Azure Functions to authenticate with Twitch and bring the data I needed.

First Attempt

Now that I knew my objective, it was time to get busy. I find the easiest way to get started with Azure Functions is to use Visual Studio Code with the Azure Function extension. I created a new Azure Functions project and set my new function to be an HTTP trigger.
The first set of data I needed from Twitch is from Get Users endpoint. Instead of reinventing the wheel with fetching data from Twitch, I decided to use the npm Twitch package. It comes with easy with to authenticate with Twitch using the Clients Credentials flow.

TwitchClient.withClientCredentials(clientId, clientSecret);

It also is built to support virtually all the different API endpoints Twitch offers. As I mentioned before, I need to fetch User's information, so the HelixUserAPI is what I need.

const user = await client.helix.users.getUserById('125328655');

Now that I have the package to make interfacing with Twitch easier, all that's left was to write a function to get a requested user's information, which ends up being very easy to do. It looked something like this:

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {

    const id = req.query.id
    const name = req.query.name
    if (!id && !name) {
        context.res = {
            status: 400,
            body: "You must provide and id or name to query by"
        };
    return;
    }

    const twitchClient = TwitchClient.withClientCredentials(TwitchCredentials.clientId, TwitchCredentials.clientSecret);
    let user: HelixUser | null;
    if (name) {
    user = await twitchClient.helix.users.getUserByName(name);
    } else if (id) {
    user = await twitchClient.helix.users.getUserById(id);
    }

    context.res = {
        body: User
    };
};

Great! This function does what I needed to do, get me a user's information by ID or name. This solves my first use case, getting a user. Now on to the next one, Get a Users Clips! But right before I just started coding things up, I started to struggle with how to implement this new functionality.

The Problem

Right now, I have an Azure function to Get a User. This works great, but now I want to Get a Users Clips. Should I just implement a new Azure function to fetch this information from Twitch? Or should I add it to the already existing Get a User function I just created?
If I add to my existing Get a User function, the logic will start to get messy and complex with all these weird optional parameters existing.
Adding a new Azure function is simple enough, but thinking about the long term, I will have to add many more functions to fetch any other set of data I want from Twitch. Yuck! Mo' Azure Function mo' problems for maintaining.
Okay, both ways have some of their flaws, and I have to choose one, right? Nope! I realized, why don't I try using GraphQL? I use it at work for a clients project, why not see if I can use it with Azure Functions.

Why GraphQL?

When stepping away from what I am trying to build, at a high level, I want a flexible API that will fetch me information from Twitch that I want. In the standard RESTful approach, this would mean loads or different API endpoints for the different entities that Twitch has to offer. If you look at any API documentation to will see this, just like Twitch's API. But GraphQL's objective to simplify this.
GraphQL is an alternative to building RESTful APIs. Using a single endpoint, all I have to do is "ask" it for what I need with all the proper parameters, and it will return it back. I am not going to detail GraphQL, there are plenty of other articles that will do a much better job doing a detailed explanation. But, to give a simple explanation. GraphQL is a query language for your API. Think of your API as a database; formulate a query based on the "schema" of your database, and once it is executed, it will return the results. There is a lot of implementation detail I am glossing over, but that's how on think of GraphQL in a nutshell.
Using GraphQL, I can rewrite my current implementation to reduce the complexity of having to expand my API when I need information from Twitch. Now I can just update my single GraphQL implementation and request only the information I need using the query language.

How to GraphQL on Azure Functions?

After all of that thinking, I have decided to try to use GraphQL for my Azure Function. Cool! But is it easy? Time to find out and see if GraphQL and be easily integrated with Azure Functions. After a quick search, I find a golden ticket.

Apollo GraphQL has Azure Function wrapper for their Apollo serve library!

The best part about this, the tutorial is super easy to follow. It looks like I will be able to change my implementation to use GraphQL easily enough.

Implementing Get Users with GraphQL

After following along with the apollo-server-azure-functions tutorial, I had the example "Hello World" running and was able to send a query for the response. Next was to build the schema for Get a User from Twitch.

  type Query {
    helix: Helix
  }

  type Helix {
    userById(id: ID!): HelixUser
    userByName(name: String!): HelixUser
  }

  type HelixUser {
    id: ID
    broadcasterType: BroadcasterType
    description: String
    displayName: String
    name: String
    profilePictureUrl: String
    offlineImageUrl: String
    views: Int
  }

As a design choice, I decided to put all "Helix" API endpoints under the helix field. This is so that there are no type collisions in the future if I end up extending my GraphQL implementation to include Twitch's older kind of sort of deprecated API called "Kraken." Next, I added two fields with arguments to get a user by ID or name. The last little bit is defining the HelixUser type and the fields that should exist.
After designing the schema, next came the resolver to fetch the data.

const resolvers = {
  Query: {
    helix() {
      return {
        userById: ({ id }: { id: string }) => {
      const twitchClient = TwitchClient.withClientCredentials(TwitchCredentials.clientId, TwitchCredentials.clientSecret);
      return await twitchClient.helix.users.getUserById(id);
    },
        userByName: ({ name }: { name: string }) => {
      const twitchClient = TwitchClient.withClientCredentials(TwitchCredentials.clientId, TwitchCredentials.clientSecret);
      return await twitchClient.helix.users.getUserByName(name);
    }),
      };
    }
  },
};

Nothing too complex going on here. First I needed to implement the helix field implementation, in this case it just a function return an object with userById and userByName subfields implementations. Now focusing on the subfields, all I have to do is fetch the User information based off the ID or name like in the old implementation and return that. GraphQL will handle the rest of the response fields since it knows what data is returned by the user object from the schema I had created.

The whole thing might look something like this:

const { ApolloServer, gql } = require('apollo-server-azure-functions');
import TwitchClient from 'twitch';
import HelixUser from "twitch/lib/API/Helix/User/HelixUser";

// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type Query {
    helix: Helix
  }

  type Helix {
    userById(id: ID!): HelixUser
    userByName(name: String!): HelixUser
  }

  type HelixUser {
    id: ID
    broadcasterType: BroadcasterType
    description: String
    displayName: String
    name: String
    profilePictureUrl: String
    offlineImageUrl: String
    views: Int
  }
`;

// Provide resolver functions for your schema fields
const resolvers = {
  Query: {
    helix() {
      return {
        userById: ({ id }: { id: string }) => {
      const twitchClient = TwitchClient.withClientCredentials(TwitchCredentials.clientId, TwitchCredentials.clientSecret);
      return await twitchClient.helix.users.getUserById(id);
    },
        userByName: ({ name }: { name: string }) => {
      const twitchClient = TwitchClient.withClientCredentials(TwitchCredentials.clientId, TwitchCredentials.clientSecret);
      return await twitchClient.helix.users.getUserByName(name);
    }),
      };
    }
  },
};

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

exports.graphqlHandler = server.createHandler();

Now if I want to get a users ID and display name from Twitch, a request will look something like this.

query getUser($name: String!) {
  helix {
    userByName(name: $name) {
      id
      displayName
    }
  }
}

Variables:
{
  "name": "talk2megooseman"
}

With my response being:

{
  "data": {
    "helix": {
      "userByName": {
        "id": "120750024",
        "displayName": "Talk2meGooseman",
      }
   }
}

If I want more information for the user entity, I just need to add the other fields I will like.

Next Time

In the next article I will go over how this new implementation with GraphQL will be it super easy to extend the API to include clips of the user. So until next time I hope you found this article a little helpful and don't be afraid to ask questions!

Discussion (0)