Skip to content

Instantly share code, notes, and snippets.

@JayBee007
Last active January 14, 2024 15:48
Show Gist options
  • Save JayBee007/63daeb5e0efa40fddce724267ea18254 to your computer and use it in GitHub Desktop.
Save JayBee007/63daeb5e0efa40fddce724267ea18254 to your computer and use it in GitHub Desktop.
Trying to Setup GraphQL subscriptions on the backend and frontend
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;
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);
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;
}
}),
},
};
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!
}
`;
/* 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',
});
});
});
// });
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