Last active
January 14, 2024 15:48
-
-
Save JayBee007/63daeb5e0efa40fddce724267ea18254 to your computer and use it in GitHub Desktop.
Trying to Setup GraphQL subscriptions on the backend and frontend
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ApolloClient } from 'apollo-client'; | |
import { split } from 'apollo-link'; | |
import { HttpLink } from 'apollo-link-http'; | |
import { WebSocketLink } from 'apollo-link-ws'; | |
import { getMainDefinition } from 'apollo-utilities'; | |
import { InMemoryCache } from 'apollo-cache-inmemory'; | |
import { withClientState } from 'apollo-link-state'; | |
import { ApolloLink, Observable } from 'apollo-link'; | |
import { getUserFromLocalStorage } from './services/authService'; | |
import defaults from './state/defaults'; | |
import resolvers from './state/resolvers'; | |
import typeDefs from './state/typeDefs'; | |
const cache = new InMemoryCache(); | |
const httpLink = new HttpLink({ | |
uri: 'http://localhost:4000/graphql', | |
}) | |
const wsLink = new WebSocketLink({ | |
uri: `ws://localhost:4000/subscriptions`, | |
options: { | |
reconnect: true | |
} | |
}); | |
const link = split( | |
// split based on operation type | |
({ query }) => { | |
const { kind, operation } = getMainDefinition(query); | |
return kind === 'OperationDefinition' && operation === 'subscription'; | |
}, | |
wsLink, | |
httpLink, | |
); | |
const request = operation => { | |
const user = getUserFromLocalStorage(); | |
let headers; | |
if(user) { | |
headers = { | |
'x-token': JSON.parse(user).token | |
} | |
operation.setContext({headers}) | |
} | |
} | |
const requestLink = new ApolloLink((operation, forward) => | |
new Observable(observer => { | |
let handle; | |
Promise.resolve(operation) | |
.then(oper => request(oper)) | |
.then(() => { | |
handle = forward(operation).subscribe({ | |
next: observer.next.bind(observer), | |
error: observer.error.bind(observer), | |
complete: observer.complete.bind(observer), | |
}); | |
}) | |
.catch(observer.error.bind(observer)); | |
return () => { | |
if (handle) handle.unsubscribe(); | |
}; | |
}) | |
); | |
const client = new ApolloClient({ | |
link: ApolloLink.from([ | |
withClientState({ | |
defaults, | |
resolvers, | |
typeDefs, | |
cache, | |
}), | |
requestLink, | |
link | |
]), | |
cache, | |
}) | |
// const client = new ApolloClient({ | |
// uri: 'http://localhost:4000/graphql', | |
// request:(operation) => , | |
// clientState: { | |
// defaults, | |
// resolvers, | |
// typeDefs | |
// } | |
// }); | |
export default client; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import Grid from '@material-ui/core/Grid'; | |
import { Query } from 'react-apollo'; | |
import Typography from '@material-ui/core/Typography'; | |
import { withStyles } from '@material-ui/core/styles'; | |
import Messages from './Messages'; | |
import SendMessage from './SendMessage'; | |
import Loader from '../Loader'; | |
import { GET_MESSAGES, SUBSCRIBE_MESSAGE } from '../../services/messageService'; | |
const MessagesContainer = ({channelName, channelId, ...props}) => ( | |
<Query query={GET_MESSAGES} variables={{channelId }}> | |
{({subscribeToMore, loading, error, data: {getMessages }}) => { | |
if(loading) return <Loader /> | |
if(error) return <p>Error: {JSON.stringify(error)}</p> | |
return ( | |
<Grid container direction="column" className={props.classes.container}> | |
<Typography variant="headline" className={props.classes.channelName}> | |
#{channelName} | |
</Typography> | |
<Messages | |
channelId={channelId} | |
messages={getMessages} | |
subscribeToNewMessages={() => subscribeToMore({ | |
document: SUBSCRIBE_MESSAGE, | |
variables: { channelId }, | |
updateQuery: (prev, { subscriptionData }) => { | |
if (!subscriptionData) return prev; | |
const newMessage = subscriptionData.messageAdded; | |
return { | |
...prev, | |
messages: [...prev.messages, newMessage] | |
}; | |
}, | |
onError: (err) => { | |
console.error('err',err); | |
} | |
})} | |
/> | |
<SendMessage channelName={channelName} channelId={channelId} className={props.classes.sendMessage} /> | |
</Grid> | |
) | |
}} | |
</Query> | |
); | |
const styles = { | |
container: { | |
height: '100%', | |
flex: 'auto', | |
flexWrap: 'nowrap', | |
}, | |
channelName: { | |
flexShrink: 0 | |
}, | |
sendMessage: { | |
flexShrink: 0, | |
marginTop: 'auto', | |
paddingTop: '5px', | |
} | |
} | |
export default withStyles(styles)(MessagesContainer); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { PubSub, withFilter } from 'graphql-subscriptions'; | |
import requiresAuth from '../permissions'; | |
const pubsub = new PubSub(); | |
const NEW_MESSAGE = 'NEW_MESSAGE'; | |
export default { | |
Message: { | |
user: requiresAuth.createResolver(async ({ userId }, args, { models, user }) => models.User.findOne({ where: { id: userId } })), | |
}, | |
Subscription: { | |
messageAdded: { | |
subscribe: withFilter( | |
() => pubsub.asyncIterator(NEW_MESSAGE), | |
(payload, args) => payload.messageAdded.channelId === args.channelId, | |
), | |
}, | |
}, | |
Query: { | |
getMessages: requiresAuth.createResolver(async (parent, { channelId }, { models, user }) => models.Message.findAll({ where: { channel_id: channelId } }, { raw: true })), | |
}, | |
Mutation: { | |
createMessage: requiresAuth.createResolver(async (parent, args, { models, user }) => { | |
try { | |
const message = await models.Message.create({ | |
...args, | |
userId: user.id, | |
}); | |
pubsub.publish(NEW_MESSAGE, { | |
messageAdded: message.dataValues, | |
channelId: args.channelId, | |
}); | |
return true; | |
} catch (error) { | |
console.log(error); | |
return false; | |
} | |
}), | |
}, | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default ` | |
type Message { | |
id: Int! | |
text: String! | |
user: User! | |
channel: Channel! | |
created_at: String! | |
} | |
type Subscription { | |
messageAdded(channelId: Int!): Message | |
} | |
type Query { | |
getMessages(channelId: Int!): [Message!]! | |
} | |
type Mutation { | |
createMessage(channelId: Int!, text: String!): Boolean! | |
} | |
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint no-console: 0 */ | |
/* eslint no-new:0 */ | |
import express from 'express'; | |
import morgan from 'morgan'; | |
import { ApolloServer } from 'apollo-server-express'; | |
import path from 'path'; | |
import jwt from 'jsonwebtoken'; | |
import { fileLoader, mergeTypes, mergeResolvers } from 'merge-graphql-schemas'; | |
import { createServer } from 'http'; | |
import { SubscriptionServer } from 'subscriptions-transport-ws'; | |
import { execute, subscribe } from 'graphql'; | |
import models from './db'; | |
require('dotenv').config(); | |
const PORT = 4000; | |
const FORCE = false; | |
const typeDefs = mergeTypes(fileLoader(path.join(__dirname, './graphql/schema'))); | |
const resolvers = mergeResolvers(fileLoader(path.join(__dirname, './graphql/resolvers'))); | |
console.log('typeDefs', typeDefs); | |
const app = express(); | |
const ws = createServer(app); | |
app.use(morgan('dev')); | |
const server = new ApolloServer({ | |
typeDefs, | |
resolvers, | |
context: ({ req }) => { | |
const token = req.headers['x-token'] || ''; | |
if (token) { | |
const { id, email } = jwt.verify(token, process.env.JWT_KEY); | |
return { models, user: { id, email } }; | |
} | |
return { models }; | |
}, | |
}); | |
server.applyMiddleware({ app }); | |
models.sequelize.sync({ force: FORCE }).then(() => { | |
// app.listen(PORT, () => { | |
// console.log('Server ready'); | |
ws.listen(PORT, () => { | |
console.log(`Apollo Server is now running on http://localhost:${PORT}`); | |
// Set up the WebSocket for handling GraphQL subscriptions | |
new SubscriptionServer({ | |
onConnect: (connectionParams, webSocket, context) => { | |
console.log('new ws connection'); | |
}, | |
execute, | |
subscribe, | |
schema: typeDefs, | |
}, { | |
server: ws, | |
path: '/subscriptions', | |
}); | |
}); | |
}); | |
// }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import Grid from '@material-ui/core/Grid'; | |
import { withStyles } from '@material-ui/core/styles' | |
import Message from './Message'; | |
class Messages extends React.Component { | |
componentDidMount() { | |
this.props.subscribeToNewMessages(); | |
} | |
render() { | |
const { messages, classes } = this.props; | |
return ( | |
<Grid container direction='column' className={classes.container} > | |
{ | |
messages.map(message => ( | |
<Message key={message.id} {...message} /> | |
)) | |
} | |
</Grid> | |
) | |
} | |
} | |
const styles = { | |
container: { | |
overflowY: 'auto', | |
flexWrap: 'nowrap' | |
} | |
} | |
export default withStyles(styles)(Messages); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment