Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

GraphQL Bangkok Meetup 2.0

GraphQL Bangkok Meetup slides including the resolver pattern presentation during September 2018 event.

Sponsored by:
https://www.apollographql.com/
https://www.prisma.io/
https://www.nimbl3.com/

Organised by:
https://www.linkedin.com/in/lucasnmunhoz/
https://www.linkedin.com/in/junereycasuga/
https://www.linkedin.com/in/meixnertobias/

  • Login to see the comments

  • Be the first to like this

GraphQL Bangkok Meetup 2.0

  1. 1. GraphQL BKK 2.0 Meetup, Bangkok, 27th September 2018
  2. 2. Nice to meet you! Tobias  2 Lucas Junerey
  3. 3. GraphQL BKK Roadmap 3 past 1.0 Setup Continuous Deployment Ongoing Meetups every 2 months GraphQL Asia Conference in Bangkok 2019
  4. 4. Partnerships Apollo Prisma BrikL 4 GraphQL Day AppSync workshop coming up... You Your Company? Talk to Us Nimbl3 Gatsby
  5. 5. ● GraphQL Introduction ● Nimbl3 ● Gatsby (Tony) ● Resolver patterns (Lucas) ● Connect, Talk, Hack until 10pm No breaks  5 ~ 8.30pm
  6. 6. 6    1 2 3 Your GraphQL?
  7. 7. GraphQL Introduction
  8. 8. GraphQL - A Query Language for APIs not databases 8 … and more … and more … and more
  9. 9. 9 Declarative data fetching Exposes a single endpoint and responds to queries New API standard GraphQL
  10. 10. 10
  11. 11. GraphQL is NOT only for React Developers ● Facebook started using GraphQL in 2012 in their native mobile apps ● It can be used with any programming languages and framework 11 Everywhere where client communicates with API!
  12. 12. REST ● Increased mobile usage creates need for efficient data loading ● Variety of different frontend frameworks and platforms ● Fast development expectation for rapid feature development 12 ?REST has been a popular way to expose data from a server
  13. 13. Who’s using GraphQL? 13
  14. 14. 14
  15. 15. 15 Get started? Apollo Boost + Apollo Launchpad ?
  16. 16. Gatsby
  17. 17. Resolver PatternsLucas Munhoz Tobias Meixner
  18. 18. Backend Tasks 19 - Authentication - Authorization - Database access - Caching - Tracing - Logging - Error handling
  19. 19. 20  Middleware Route Controller Data Layer HTTP Request /payments HTTP Response REST isAuthenticated getUserPayments db.payments.find 1 request === 1 response Status Code 200, OK { data } [ { id: tr_10340J2eZvKYlo2Cg42HilbB, amount: 5687, arrival_date: 1386374400, automatic: true, balance_transaction: txn_19XJJ02eZvKYlo2ClwuJ1rbA, created: 1386212146, currency: usd, failure_balance_transaction: null, failure_code: null, method: standard, source_type: card, status: paid, type: bank_account }, { … } ] async function isAuthenticated(req, res, next) { const user = await getUserViaJWT(req.headers.authorization) if (!user){ return res.status(401).end() } // Inject user in the request object req.user = user next() // Call next middleware in the chain } app.post('/login’, controller.login) app.get('/payments', isAuthenticated, controller.getUserPayments)
  20. 20. isAdmin 21  Middleware Route Controller Data Layer HTTP Request /users/:id/ban HTTP Response REST isAuthenticated banUser db.users.update 1 request === 1 response Status Code 200, OK { data } async function isAuthenticated(req, res, next) { … } async function isAdmin(req, res, next) { const user = req.user if (!user.roles.includes('admin')){ return res.status(401).end() } next() // Call next middleware in the chain } app.put('/users/:id/ban', [isAuthenticated, isAdmin], controller.banUser)
  21. 21. 22  Resolver Data Layer HTTP Request /graphql HTTP Response GraphQL ... Status Code 200, OK { data } query { payments { id amount currency source_type status } } async function payments(parent, args, context, info) { const userId = context.user.id const payments = await context.db.getUserPayments({ userId }) return payments } { data: { payments: [ { id: tr_10340J2eZvKYlo2Cg42HilbB, amount: 5687, currency: usd, source_type: card, status: paid, }, { ... } ] }
  22. 22. 23 query { payments { id amount currency source_type status } } { data: { payments: [ { id: tr_10340J2eZvKYlo2Cg42HilbB, amount: 5687, currency: usd, source_type: card, status: paid, }, { ... } ] } }
  23. 23. 24 type Payment { id: UUID! amount: Float! currency: String! source_type: String! invoice: Invoice! } # Root Type type Query { payments(status: String): [Payment!]! } query { payments { id amount currency source_type status } } Schema Definition Execution Scalar Type # Schema definition ./src/schema.graphql scalar UUID # Root Resvolver ./src/resolvers/uuid.js const GraphQLUUID = new GraphQLScalarType({ name: 'UUID', description: 'The `UUID` scalar type represents UUID values as specified by [RFC 4122].', serialize: value = { if (!isUUID(value)) { throw new TypeError(`UUID cannot represent non-UUID value: ${value}`); } return value.toLowerCase(); }, parseValue: value = { if (!isUUID(value)) { throw new TypeError(`UUID cannot represent non-UUID value: ${value}`); }
  24. 24. 25 type Payment { ... invoice: Invoice! } type Invoice { id: UUID! issued_at: DateTime! payment: Payment url: String! } type Query { … } query { payments { id status invoice { id issued_at url } } } Schema Definition Execution Field Type # Type resolver ./src/resolvers/payment.js export default { Payment: { invoice: async (parent, args, context, info) = { const invoice = await context.db.getInvoiceForPayment({ paymentId: parent.id }) return invoice } } } { data: { payments: [ { id: ..., status: paid, invoice: { id: ..., issued_at: Thu Sep 27 2018..., url: https://invoice/download }, }, ... }
  25. 25. “ Each field in a GraphQL schema is backed by a resolver. 26
  26. 26. 27 query { payments { id invoice { id payment { id invoice { payment { invoice { payment { invoice { id issued_at } } ...
  27. 27. “GraphQL queries can traverse related objects and their fields, letting clients fetch lots of related data in one request, instead of making several roundtrips as one would need in a classic REST architecture. 28
  28. 28. 29 ● Access control must be moved to resolver or data layer (no more middleware) 29 Challenges ● Free data traversing of types and relationships adds more complexity to access control and data fetching ● Request and response must be multiplex (query batching) query { payments(status: paid) { id status } subscribers { id firstName lastName subscribed_at } } { data: { payments: [{ id: ..., status: paid, }], subscribers: [{ id: ..., firstName: James, lastName: Porlante, subscribed_at: ..., }] } }
  29. 29. 30 Context Injection async function payments(parent, args, context, info) { const userId = context.user.id const payments = await context.db.getUserPayments({ userId }) return payments } const resolvers = require('./resolvers') const repositories = require('./repositories') // - Data Layer const server = new GraphQLServer({ typeDefs: importSchema('./src/schema.graphql'), resolvers, context: (request) = ({ ...request, db: repositories, user: getUserViaJWT(req.headers.authorization), sentry: Sentry }) })
  30. 30. 31 ● Clients don’t rely more on status code, so errors must be parsed and formatted for every query ● Code must be DRY 31 Challenges
  31. 31. 32 Resolver Chain + Error Handling import { createResolver } from 'apollo-resolvers'; import { createError, isInstance } from 'apollo-errors'; const UnknownError = createError('UnknownError', { message: 'An unknown error has occurred! Please try again later' }); export const baseResolver = createResolver( //incoming requests will pass through this resolver like a no-op null, (root, args, context, error) = isInstance(error) ? error : new UnknownError() ); const ForbiddenError = createError('ForbiddenError', { message: 'You are not allowed to do this' }); const AuthenticationRequiredError = createError('AuthenticationRequiredError', { message: 'You must be logged in to do this' }); export const isAuthenticatedResolver = baseResolver.createResolver( (root, args, context, info) = { if (!context.user) throw new AuthenticationRequiredError(); } ); const isAdminResolver = isAuthenticatedResolver.createResolver( // Extract the user from the context (root, args, { user }, info) = { if (!user.roles.includes('admin')) throw new ForbiddenError(); } ) // ./src/resolvers/query.ts export default { subscribers: isAdminResolver.createResolver( (root, args, context, info) = { return context.db.getAllSubscribers() } }) }
  32. 32. 33 graphql-middleware export const logInput = async (resolve, root, args, context, info) = { console.log(`1. logInput: ${JSON.stringify(args)}`) const result = await resolve(root, args, context, info) console.log(`5. logInput`) return result } export const logResult = async (resolve, root, args, context, info) = { console.log(`2. logResult`) const result = await resolve(root, args, context, info) console.log(`4. logResult: ${JSON.stringify(result)}`) return result } const { logInput, logOutput } = require('./middlewares') const server = new GraphQLServer({ typeDefs, resolvers, middlewares: [logInput, logResult], }) server.start(() = console.log('Server is running on http://localhost:4000'))
  33. 33. 34 Schema Directives directive @rest(url: String) on FIELD_DEFINITION type Query { people: [Person] @rest(url: /api/v1/people) } class RestDirective extends SchemaDirectiveVisitor { public visitFieldDefinition(field) { const { url } = this.args; field.resolve = () = fetch(url); } } const schema = makeExecutableSchema({ typeDefs, schemaDirectives: { rest: RestDirective } });
  34. 34. 35 Schema Directives from Prisma type Payment { id: ID! @unique status: @default(value: “not-paid”) invoice: Invoice! @relation(name: Payment_Invoice) }
  35. 35. 36 Backend Database as a Service
  36. 36. 37
  37. 37. and others... Prisma 38 Hasura
  38. 38. Patterns 39 ● Root resolvers ● Type Field resolvers ● Context injection ● Apollo Resolvers ● GraphQL Middleware ● Schema directives ● Database / Backend as a service A B S T R A C T I O N Personal flavour and opinion Legacy APIs Project size Complexity Apply depending on Use case 
  39. 39. Packages to extend GraphQL native resolvers 40 ● graphql-tools ● apollo-server ● graphql-compose ● graphql-shield ● graphql-middleware ● graphql-sequelize ● mongoose-schema-to-graphql ● ...
  40. 40. Resolver Cheat Sheet 41 Auth Authz ORM Datasource Logging Caching Tracing Type Field resolvers Modular resolvers Root resolvers GraphQL Middleware Context injection Schema directives DaaS / BaaS
  41. 41. Resolver patterns @ BrikL 42 Auth Authz ORM Data Logging Caching Tracing Type Field resolvers Modular resolvers Root resolvers GraphQL Middleware Context injection Schema directives DaaS / BaaS
  42. 42. Resolver patterns @ BrikL 43 Auth Authz ORM Data Logging Caching Tracing Type Field resolvers Modular resolvers Root resolvers GraphQL Middleware Context injection Schema directives DaaS / BaaS Schema
  43. 43. Resolver patterns @ Envisioning 44 Auth Authz ORM Data Logging Caching Tracing Type Field resolvers Modular resolvers Root resolvers GraphQL Middleware Context injection Schema directives DaaS / BaaS
  44. 44. 45 Thanks! Any questions? Connect, Talk, Hack until 10pm See you in November
  45. 45. References 46 https://www.prisma.io/docs/tutorials/build-graphql-servers/misc/resolver-patterns-eifeecahx4/ https://www.apollographql.com/docs/graphql-tools/resolvers https://blog.apollographql.com/layering-graphql-on-top-of-rest-569c915083ad https://www.prisma.io/blog/graphql-server-basics-the-schema-ac5e2950214e/ https://www.prisma.io/blog/graphql-middleware-zie3iphithxy/ https://medium.com/@colecodes/structuring-data-with-apollo-server-e114a302aec7 https://itnext.io/graphql-resolvers-the-injector-pattern-a7e1d880c512 https://blog.apollographql.com/reusable-graphql-schema-directives-131fb3a177d1
  46. 46. Credits (Slidetheme) Special thanks to all the people who made and released these awesome resources for free: ● Presentation template by SlidesCarnival ● Photographs by Unsplash 47

×