Skip to content

Instantly share code, notes, and snippets.

@shammelburg
Last active December 8, 2021 17:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shammelburg/4ab6d7318157fedf3dbab015fda71ade to your computer and use it in GitHub Desktop.
Save shammelburg/4ab6d7318157fedf3dbab015fda71ade to your computer and use it in GitHub Desktop.
express-graphql-api
const jwt = require('jsonwebtoken');
const authMiddleware = (req, res, next) => {
const authHeader = req.get('Authorization')
if (!authHeader) {
req.error = "No authentication header found."
req.isAuth = false
return next()
}
let decoded
try {
const token = authHeader.split(' ')[1]
decoded = jwt.verify(token, process.env.JWT_KEY)
} catch (error) {
req.isAuth = false
req.error = error.message
return next()
}
if (!decoded) {
req.isAuth = false
req.error = "Unable to decode jwt"
return next()
}
req.isAuth = true
req.user = decoded
req.error = null
next()
}
module.exports = authMiddleware
const DataLoader = require('dataloader')
const students = [
{ id: 1, studentName: 'Sander' },
{ id: 2, studentName: 'Mark' },
{ id: 3, studentName: 'Greg' },
{ id: 4, studentName: 'Chris' },
]
const classes = [
{ id: 1, className: 'a0', studentId: 1 },
{ id: 2, className: 'a1', studentId: 1 },
{ id: 3, className: 'a2', studentId: 2 },
{ id: 4, className: 'a3', studentId: 1 },
{ id: 5, className: 'a4', studentId: 3 },
{ id: 6, className: 'a5', studentId: 4 },
{ id: 7, className: 'a6', studentId: 1 },
{ id: 8, className: 'a7', studentId: 3 },
{ id: 9, className: 'a8', studentId: 3 },
]
const studentRepo = () => {
return {
getClassesByStudentId: ids => {
return classes.filter(c => ids.includes(c.studentId))
}
}
}
const batchStudentIds = async ids => {
// Logging batched student ids
console.log(ids)
// Get all classes by student id
const classes = studentRepo().getClassesByStudentId(ids)
// Now group the classes by ids as they're passed into the batch function
const groupedById = ids.map(id => classes.filter(c => c.studentId === id))
return Promise.resolve(groupedById)
}
const dataLoader = new DataLoader(batchStudentIds);
const result = Promise.all(students.map(s => dataLoader.load(s.id)))
result.then(console.log)
// Result
// [
// [
// { id: 1, className: 'a0', studentId: 1 },
// { id: 2, className: 'a1', studentId: 1 },
// { id: 4, className: 'a3', studentId: 1 },
// { id: 7, className: 'a6', studentId: 1 }
// ],
// [{ id: 3, className: 'a2', studentId: 2 }],
// [
// { id: 5, className: 'a4', studentId: 3 },
// { id: 8, className: 'a7', studentId: 3 }
// ],
// [{ id: 6, className: 'a5', studentId: 4 }]
// ]
const express = require('express')
const { graphqlHTTP } = require('express-graphql')
// Subscriptions
const ws = require('ws')
const { useServer } = require('graphql-ws/lib/use/ws');
const { execute, subscribe } = require('graphql');
const app = express()
const schema = require('./src/schema')
app.get('/', (req, res) => res.send('GraphQL Server is running'))
app.use('/graphql', graphqlHTTP(req => ({
schema,
graphiql: {
headerEditorEnabled: true
},
// ...
})))
const server = app.listen(4000, () => {
console.log(`GraphQL Server running on http://localhost:4000/graphql`)
// create and use the websocket server
const wsServer = new ws.Server({
server,
path
});
useServer(
{
schema,
execute,
subscribe,
onConnect: (ctx) => {
console.log('Connect');
},
onSubscribe: (ctx, msg) => {
console.log('Subscribe');
},
onNext: (ctx, msg, args, result) => {
console.debug('Next');
},
onError: (ctx, msg, errors) => {
console.error('Error');
},
onComplete: (ctx, msg) => {
console.log('Complete');
},
},
wsServer
);
console.log(`WebSockets listening on ws://localhost:4000/subscriptions`)
});
const { makeExecutableSchema } = require('graphql-tools')
const merge = require('lodash.merge');
const userSchema = require('./user')
const roleSchema = require('./role')
// Multiple files to keep your project modularised
const schema = makeExecutableSchema({
typeDefs: [
userSchema.typeDefs, // First defines the type Query
roleSchema.typeDefs, // Others extends type Query
],
resolvers: merge(
userSchema.resolvers,
roleSchema.resolvers,
)
})
module.exports = schema
const users = require('../data/users.json')
module.exports = (context, fieldName) => {
// These fields require the requester to be authenticated
const fieldsThatRequireAuth = [
'createUser'
]
if (fieldsThatRequireAuth.includes(fieldName) && !context.isAuth) {
// if not authenticated
}
// Authorization for user with "Admin" role only
const hasAdminRole = context.user && context.user.roles.includes('Admin')
return {
// users query
getUsers: () => users,
// createUser mutation
insertUser: user => {
if (!hasAdminRole) return new Error(`Access denied to "${fieldName}" field.`)
// insert...
}
}
}
const pubsub = require('../../helpers/pubsub')
const NEW_USER_EVENT = 'NEW_USER_EVENT'
const resolvers = {
Query: {
// ...
},
Mutation: {
createUser: (root, { input }, context, info) => {
const id = userRepo(context, info.fieldName).insertUser(input)
pubsub.publish(NEW_USER_EVENT, { newUser: { ...input, id } })
return { ...input, id }
},
},
Subscription: {
newUser: {
subscribe: (parent, args, context) => pubsub.asyncIterator(NEW_USER_EVENT)
}
}
}
module.exports = resolvers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment