This is an open source twitch API graphql wrapper. If you clone this repository you can run it as a full-functioning server. It is also installable as an npm package.
By default it will run at http://localhost:5555/graphql
.
This project assumes that you already have a Twitch API App set up, and an OAuth token with every scope that you will need. This information will be stored inside of several environment variables in a .env file. If you need a Twitch API Application (for user ID and client secret), information regarding setting one up is documented in the Twitch API documentation. If you require an OAuth or refresh token, there is documentation available in the Twitch API documentation. Alternatively, there is a guide below on how to retrieve a refresh token.
Variable | Value |
---|---|
SECRET | Your application's secret |
USER_ID | Your application's User ID |
TWITCH_ID | Your Twitch account's ID |
REFRESH_TOKEN | The refresh token for your OAuth token |
PORT | The port that you want to run the server on |
GRAPHIQL | Whether or not you want the server to enable graphiql |
WEBHOOK_PORT | The port that you want to run the webhook listener on |
CALLBACK_URL | The full URL (including http) for twitch to send webhooks to (the URL the webhook port is accessible at) |
This library now supports authentication via request headers. Headers will take priority over environment variables.
Variable | Value |
---|---|
secret | Your application's secret |
user_id | Your application's User ID |
twitch_id | Your Twitch account's ID |
refresh_token | The refresh token for your OAuth token |
These items will be stored in the GraphQL modules global context in the same name as they appear in the headers. All of the headers are supported as connectionParams for Subscriptions.
For your convenience, the context
and onConnect
methods have been provided to automatically set the global context in the expected manner.
import { context, onConnect } from 'twitch-graphql'
const server = new ApolloServer({
schema,
subscriptions: {
path: `/subscriptions`,
onConnect,
},
context,
})
Here is an apollo client setup that will just work.
const httpLink = new HttpLink({
uri: 'http://localhost:5555/graphql',
headers: {
twitch_id: 'YOUR_TWITCH_ID',
refresh_token: 'YOUR_TOKEN',
secret: 'YOUR_SECRET',
user_id: 'YOUR_USER_ID',
},
})
const wsLink = new WebSocketLink({
uri: `ws://localhost:5555/subscriptions`,
options: {
reconnect: true,
connectionParams: {
twitch_id: 'YOUR_TWITCH_ID',
refresh_token: 'YOUR_TOKEN',
secret: 'YOUR_SECRET',
user_id: 'YOUR_USER_ID',
},
},
})
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
httpLink
)
const client = new ApolloClient({
uri: 'http://localhost:5555/graphql',
cache: new InMemoryCache(),
link: splitLink,
})
Twitch doesn't have a PubSub for follows yet, so if you need to use the follow subscription, you need to have this server port forwarded and open to outside requests.
Webhooks work like this:
If you don't want to bother with port forwarding, you can use ngrok to set up a secure tunnel to your server from the outside using the following steps.
./ngrok http PORT
in the directory where ngrok was downloaded, where PORT
is your WEBHOOK_PORT
environment variable.http
url and an https
something.ngrok.io
url in the console window, copy and paste this into the CALLBACK_URL
environment variable.You will need to repeat this process each time that you restart ngrok.
This library is also available as a NPM package.
To install, run
npm install twitch-graphql
or
yarn add twitch-graphql
Usage of the package depends on the structure of your application. If you choose to use graphql-modules (Recommended), you can import the modules that you require and add them to your application, complete with authentication.
Every module in this library depends on the base module, "Query". You will ALWAYS NEED TO IMPORT AND USE QueryModule
Example:
import express from 'express'
import http from 'http'
import { QueryModule } from './schema/query-type-schema'
import { SubscriberModule } from './schema/subscriber-type-schema'
import { UserModule } from './schema/user-type-schema'
import { StreamModule } from './schema/stream-type-schema'
import { GameModule } from './schema/game-type-schema'
import { createApplication } from 'graphql-modules'
import { UserSubscriberLinkModule } from './schema/user-subscriber-link-type-schema'
import { GameStreamLinkModule } from './schema/game-stream-link-type-schema'
import { RedemptionPubSubModule } from './schema/redemption-pubsub-type-schema'
import { StreamUserLinkModule } from './schema/stream-user-link-type-schema'
import { RedemptionUserLinkModule } from './schema/redemption-pubsub-user-link-type-schema'
import { ChatPubSubModule } from './schema/chat-pubsub-type-schema'
import { ApolloServer } from 'apollo-server-express'
import { ChatUserLinkModule } from './schema/chat-pubsub-user-link-schema'
import { BitPubSubModule } from './schema/bit-pubsub-type-schema'
import { BitUserLinkModule } from './schema/bit-pubsub-user-link-schema'
import { SubscriptionPubSubModule } from './schema/subscription-pubsub-type-schema'
import { SubscriptionPubSubUserLinkModule } from './schema/subscription-pubsub-user-link-schema'
import { SubscriptionPubSubChatLinkModule } from './schema/subscription-pubsub-chat-link-schema'
import { onConnect, context } from './helpers/ServerSetup'
import { FollowPubSubModule } from './schema/follow-pubsub-type.schema'
const app = createApplication({
modules: [
QueryModule,
SubscriberModule,
UserModule,
StreamModule,
UserSubscriberLinkModule,
GameStreamLinkModule,
GameModule,
StreamUserLinkModule,
RedemptionPubSubModule,
RedemptionUserLinkModule,
ChatPubSubModule,
ChatUserLinkModule,
BitPubSubModule,
BitUserLinkModule,
SubscriptionPubSubModule,
SubscriptionPubSubUserLinkModule,
SubscriptionPubSubChatLinkModule,
FollowPubSubModule,
],
})
app.createSubscription()
const schema = app.createSchemaForApollo()
const server = new ApolloServer({
schema,
introspection: true,
subscriptions: {
path: `/subscriptions`,
onConnect,
},
context,
playground: true,
})
const expressApp = express()
server.applyMiddleware({ app: expressApp })
const httpServer = http.createServer(expressApp)
server.installSubscriptionHandlers(httpServer)
httpServer.listen(port, () => {
console.log(
`🚀 Server ready at http://localhost:${port}${server.graphqlPath}`
)
console.log(
`🚀 Subscriptions ready at ws://localhost:${port}${server.subscriptionsPath}`
)
})
This example will run a server containing all of the current Twitch API modules.
Alternatively, you can import the schema and resolvers separately. However, since the resolvers require a grapqhl-modules dependency injection to retrieve authentication information, you may have to rewrite them entirely if you choose to not use grapqhl-modules for your architecture.
Examples of importing Schema and Resolvers:
import { QuerySchema, QueryResolver } from 'twitch-graphql'
import { StreamSchema, StreamResolvers } from 'twitch-graphql'
import { SubscriberSchema, SubscriberResolvers } from 'twitch-graphql'
import { UserSchema, UserResolvers } from 'twitch-graphql
-p, --port The port number that the server should use. Not available with yarn dev. [number]
-g, --graphiql Enables graphiql. [flag]
Command | Action |
---|---|
yarn build | Runs typescript compiler, compiles to ./dist |
yarn start | Runs ./dist statically, does not refresh |
yarn dev | Runs auto refreshing development server |
yarn dev-g | Runs auto refreshing development server with graphiql enabled |
After you have set up your twitch application, you can get all of the required keys by doing the following:
https://id.twitch.tv/oauth2/authorize?client_id=YOUR APPLICATION CLIENT ID&redirect_uri=http://localhost&response_type=code&scope=channel:read:subscriptions+channel_subscriptions+analytics:read:games+chat:read whispers:read+channel:read:redemptions+bits:read
localhost://code=SOME CODE/other-junk
copy the code only.https://id.twitch.tv/oauth2/token?client_id=YOUR CLIENT ID&client_secret=YOUR CLIENT SECRET&code=YOUR CODE&grant_type=authorization_code&redirect_uri=http://localhost
This will respond with the refresh token that you need.
Base schema, all other modules extend this.
import { SubscriberModule } from 'twitch-graphql'
extend type Query {
latestSub: Subscriber!
randomSub: Subscriber!
allSubs: [Subscriber]!
subCount: Int!
getSubscriberByDisplayName(displayName: String!): Subscriber
}
type Subscriber {
cumulativeMonths: Int!
tier: Int!
userDisplayName: String!
userId: String!
isGift: Boolean!
}
import { UserModule } from 'twitch-graphql'
Please note that using follows with a high number of maxPages will likely result in rate limiting from twitch.
type User {
displayName: String!
description: String!
id: String!
profilePictureURL: String!
views: Int!
follows(maxPages: Int!): FollowConnection
getFollowToId(userId: String!): Follow
getFollowToDisplayName(displayName: String!): Follow
followsId(userId: String!): Boolean!
followsDisplayName(displayName: String!): Boolean!
}
type Follow {
followDateUTC: String!
followDate: String!
followerUser: User!
followedUser: User!
}
type FollowConnection {
total: Int!
nodes: [Follow]
cursor: String
}
extend type Query {
getUserById(userId: String!): User
getUserByDisplayName(displayName: String!): User
}
import { UserSubscriberLinkModule } from 'twitch-graphql'
This module extends Subscriber to add the user field. Only use if both modules are being used in your application.
extend type Subscriber {
user: User!
}
extend type User {
isSubscribedToId(userId: String!): Boolean!
isSubscribedToDisplayName(displayName: String!): Boolean!
getSubscriptionToId(userId: String!): Subscriber
getSubscriptionToDisplayName(displayName: String!): Subscriber
}
import { Stream } from 'twitch-graphql'
type Stream {
language: String!
gameId: String!
id: String!
title: String!
viewers: Int!
thumbnailUrl: String!
userDisplayName: String!
userId: String!
}
type StreamConnection {
total: Int!
nodes: [Stream]
cursor: String
}
extend type Query {
getStreams(streamFilter: StreamFilter!, maxPages: Int = 1): StreamConnection
}
input StreamFilter {
gameIds: [String]
gameNames: [String]
languages: [String]
type: StreamType
userIds: [String]
userNames: [String]
}
enum StreamType {
live
none
}
import { StreamUserLinkModule } from 'twitch-graphql'
This module extends Stream to add the user field, and User to add the stream field. Only use if both modules are being used in your application.
extend type User {
stream: Stream
}
extend type Stream {
user: User
}
import { GameModule } from 'twitch-graphql'
type Game {
boxArtUrl: String!
id: String!
name: String!
}
extend type Query {
getGameByName(gameName: String!): Game
}
import { GameStreamLinkModule } from 'twitch-graphql'
This module extends Stream to add the game field. Only use if both modules are being used in your application.
extend type Stream {
game: Game
}
Twitch PubSubs are supported using GraphQL Subscriptions.
Currently these PubSub modules are in an experimental phase, since no unit tests have been written for them, and have only been tested manually.
import { RedemptionPubSubModule } from 'twitch-graphql'
type Redemption {
userId: String
id: String
channelId: String
userName: String
userDisplayName: String
redemptionDate: String
rewardId: String
rewardName: String
rewardPrompt: String
rewardCost: Int
rewardIsQueued: String
rewardImage: String
message: String
status: String
}
extend type Subscription {
newRedemption: Redemption
}
import { RedemptionUserLinkModule } from 'twitch-graphql'
extend type Redemption {
user: User
channelRedeemedAt: User
}
import { ChatPubSubModule } from 'twitch-graphql'
type Chat {
message: String
displayName: String
channel: String
userInfo: ChatUser
}
type ChatUser {
userName: String
displayName: String
color: String
userId: String
userType: String
isBroadcaster: Boolean
isSubscriber: Boolean
isFounder: Boolean
isMod: Boolean
isVip: Boolean
}
extend type Subscription {
newChat(channel: String!): Chat
}
extend type Mutation {
sendChat(channel: String!, message: String!): Boolean
}
import { ChatUserLinkModule } from 'twitch-graphql'
extend type Chat {
user: User
}
import { BitPubSubModule } from 'twitch-graphql'
type Bits {
userId: String
userName: String
message: String
bits: Int
totalBits: Int
isAnonymous: Boolean
}
extend type Subscription {
newBits: Bits
}
import { BitPubSubModule } from 'twitch-graphql'
extend type Bit {
user: User
}
import { SubscriptionPubSubModule } from 'twitch-graphql'
type SubscriptionMessage {
userId: String
userName: String
userDisplayName: String
streakMonths: Int
cumulativeMonths: Int
months: Int
time: String
subPlan: String
isResub: Boolean
isGift: Boolean
isAnonymous: Boolean
gifterId: String
gifterName: String
gifterDisplayName: String
giftDuration: Int
}
extend type Subscription {
newSubscription: SubscriptionMessage
}
import { SubscriptionPubSubChatLinkModule } from 'twitch-graphql'
extend type SubscriptionMessage {
message: Chat
}
import { SubscriptionPubSubUserLinkModule } from 'twitch-graphql'
extend type SubscriptionMessage {
user: User
gifterUser: User
}
import { FollowPubSubModule } from 'twitch-graphql'
type FollowSubscription {
followDate: String
followedUserDisplayName: String
followedUserId: String
userDisplayName: String
userId: String
}
extend type Subscription {
newFollow: FollowSubscription
}
import { FollowPubSubUserLinkModule } from 'twitch-graphql'
extend type FollowSubscription {
followedUser: User
followerUser: User
}