Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GraphQL - APIs mais robustas e flexíveis

Bruno Lemos
September 06, 2016

GraphQL - APIs mais robustas e flexíveis

Bruno Lemos

September 06, 2016
Tweet

Other Decks in Programming

Transcript

  1. Sobre mim ➔ Desenvolvedor Web desde ~2005 ➔ Formado na

    USP de São Carlos ➔ Full Stack Developer na startup Easy Carros ➔ Hackathons ◆ 1º lugar - Hackathon Globo 2016 ◆ 1º lugar - MasterCard Code4Inclusion Miami ◆ 2º lugar - Masters of Code São Paulo ◆ 1º lugar - Destination Hack ◆ 1º lugar - API Hackday SP @brunolemos
  2. O que iremos abordar? 1. Motivação // que problemas resolve?

    2. Características // query language, ... 3. Queries & Mutations // query { user(id: 1) { name } } 4. Na prática // adicionando GraphQL à uma API já existente 5. Autenticação // segurança 6. Client & Libs // relay, apollo, … 7. Próximos passos // o que não abordamos + futuro do graphql
  3. Imagine uma aplicação na qual você pode: 1. Adicionar amigos

    2. Publicar posts 3. Curtir páginas 1. Motivação
  4. Usando REST GET /v1/me { _id: 1, friends: [2, 3,

    4,5] } GET /v1/users/2/posts/last {_id: ‘post_2a’} GET /v1/users/3/posts/last {_id: ‘post_3a’} GET /v1/users/4/posts/last {_id: ‘post_4a’} GET /v1/users/5/posts/last {_id: ‘post_5a’} 1. Motivação Muitas requisições… Já sei, vou criar um endpoint para isso.
  5. GET /v1/myFriendsLastPosts [{_id: 1, lastPost: {...}, {_id: 2, lastPost: {...}]

    Usando “REST” E se eu quiser obter as páginas que meus amigos curtiram? 1. Motivação
  6. E se eu quiser obter as páginas que meus amigos

    curtiram e os últimos posts? GET /v1/myFriendsLikedPages [{_id: 1, pages: [...]}, {_id: 2, pages: [...]}] Usando “REST” 1. Motivação
  7. GET /v1/myFriendsLikedPages [{_id: 1, pages: [...]}, {_id: 2, pages: [...]}]

    GET /v1/myFriendsLastPosts [{_id: 1, lastPost: {...}}, {_id: 2, lastPost: {...}}] Usando “REST” 1. Motivação // faço o merge dos resultados no client [{_id: 1, lastPost: {...}, pages: [...]}, ...] // já sei! que tal um novo endpoint? GET /v1/myFriendsLastPostsAndPages
  8. No REST, é fácil você se encontrar criando endpoints para

    retornos específicos. Isto não é escalável. 1. Motivação
  9. Além disso… Ao fazer um GET em um endpoint, que

    dados serão retornados? #surprise Como descobrir: 1. Fazer uma requisição de teste 2. Ler a documentação (pode estar desatualizada) 3. Ler o código Dados retornados: 1. Provavelmente muito mais do que você precisa 1. Motivação
  10. “Analisamos alternativas, como o REST. (...) Ficamos frustados com as

    diferenças entre os dados que queríamos e as requests que eram necessárias para obtê-los.” 2012 Lee Byron, Facebook Software Engineer 1. Motivação
  11. O client declara os dados que precisa e a resposta

    é um espelho da entrada “Retorne isto. Nada mais, nada menos.” Declarative query language //REQUEST query { user(_id: “xxx”) { _id name email } } //RESPONSE { "data": { "user": { "_id": "xxx", "name": "Bruno Lemos", "email": "[email protected]" } } } Características
  12. Funções “resolve” Visão geral Características Resposta em JSON no mesmo

    formato da entrada Entrada dos dados que precisa Camada do GraphQL Diferentes clients Bancos de dados Servidor Retorno da função “resolve” com os dados desejados
  13. Na função resolve, você é livre para pegar o dado

    de onde quiser // Funções “resolve” são as responsáveis por dizer onde pegar os dados. // Podem retornar dados de qualquer lugar, desde que retorne o valor final ou uma Promise. // Exemplos para query { user(_id: “xxx”) { name } } resolve: (root, args, context) => ({ name: ‘Bruno Lemos’, outroCampo: ‘X’ }), // Dado arbitrário resolve: (root, args, context) => User.findById(args._id), // Método que retorna uma promise resolve: (root, args, context) => fetch(‘http://api.site.com/v1/user’), // API externa Características Funções “resolve”
  14. Query Queries são como o GET do REST: Você usa

    para obter dados, não podendo fazer mutações.
  15. Query Sintaxe query { user(_id: “xxx”) { _id name }

    } { "data": { "user": { "_id": "xxx", "name": "Bruno Lemos" } } }
  16. Query: Várias ao mesmo tempo Sintaxe query { user(_id: “xxx”)

    { _id name } vehicle(_id: “tesla_model_s”) { brand model } } { "data": { "user": { "_id": "xxx", "name": "Bruno Lemos" }, "vehicle": { "brand": "Tesla", "model": "Model S" } } }
  17. Query: Várias ao mesmo tempo Sintaxe query { user(_id: “xxx”)

    { _id name } user(_id: “xxx”) { github } } { "data": { ? } }
  18. Query: Várias ao mesmo tempo Sintaxe query { user(_id: “xxx”)

    { _id name } user(_id: “xxx”) { github } } { "data": { "user": { "_id": "xxx", "name": "Bruno Lemos", "github": "brunolemos" }, } }
  19. Query: Várias ao mesmo tempo Sintaxe query { user(_id: “xxx”)

    { _id name } user(_id: “xxx_2”) { github } } { "data": { ? } }
  20. Query: Várias ao mesmo tempo Sintaxe query { user(_id: “xxx”)

    { _id name } user(_id: “xxx_2”) { github } } { "errors": [{ "message": "Fields \"user\" conflict because they have differing arguments. use different aliases on the fields to fetch both if this was intentional." }] }
  21. Query: Alias Sintaxe query { dan: user(_id: “dan_id”) { _id

    name } arunoda: user(_id: “arunoda_id”) { _id name } } { "data": { "dan": { "_id": "dan_id", "name": "Dan Abramov", }, "arunoda": { "_id": "arunoda_id", "name": "Arunoda Susiripala", } } }
  22. Query: Nested Sintaxe query { user(_id: “xxx”) { _id name

    friends(limit: 1) { name } } } { "data": { "user": { "_id": "xxx", "name": "Bruno Lemos", "friends": [{ "name": "Dan Abramov" ]} } } }
  23. Query: Nested!!! Sintaxe query { user(_id: “xxx”) { _id name

    friends(limit: 1) { name friends(limit: 1) { name } } } } { "data": { "user": { "_id": "xxx", "name": "Bruno Lemos", "friends": [{ "name": "Dan Abramov", "friends": [{ "name": "Arunoda Susiripala" }], }], }, } }
  24. Query: Nested (exemplo inicial) Sintaxe { "data": { "user": {

    "name": "Bruno Lemos", "friends": [{ "name": "Sashko Stubailo", "latestPost": { "title": "GraphQL is the future" }, "pages": [{ "name": "Apollo Client" }], }], }, } } query { user(_id: “xxx”) { name friends { name latestPost { title } pages { name } } } }
  25. Query: Nested + Utils Sintaxe query { user(_id: “xxx”) {

    thumbnail: image(size: 100) { url width height } fullPicture: image { url width height } } } { "data": { "user": { "thumbnail": { "url": "thumbnail_100x100.jpg", "width": 100, "height": 100 }, "fullPicture": { "url": "picture.jpg", "width": 2048, "height": 2048 } } } }
  26. Query: Nested + Utils Sintaxe query { user(_id: “xxx”) {

    createdAt { format(format: "DD/MM/YYYY HH:mm") timezone iso timestamp } } } { "data": { "user": { "createdAt": { "format": "01/09/2016 19:30", "timezone": "America/Sao_Paulo", "iso": "2016-09-01T22:30:00.000Z", "timestamp": "1472769000000" } } } }
  27. Query: Nested + Utils Sintaxe query { user(_id: “xxx”) {

    createdAt(timezone: “America/New_York”) { format(format: "DD/MM/YYYY HH:mm") timezone } } } { "data": { "user": { "createdAt": { "format": "01/09/2016 18:30", "timezone": "America/New_York" } } } }
  28. Query Ok, o campo name é sempre String, o campo

    age é sempre Int, … E se eu tiver um campo que possa retornar mais de um tipo? Exemplo: campo user que pode ser tanto do tipo User quanto Admin
  29. query { me { __typename ... on Admin { name

    } ... on User { name age } } } Query: Múltiplos tipos Sintaxe { "data": { "me": { "__typename": "Admin", "name": "Bruno Lemos" } } } A query ‘me’ pode retornar um tipo diferente dependendo de quem está logado no momento
  30. query { me { __typename ... on Admin { name

    } ... on User { name age } } } Query: Múltiplos tipos Sintaxe { "data": { "me": { "__typename": "User", "name": "Bruno Lemos", "age": 23 } } } A query ‘me’ pode retornar um tipo diferente dependendo de quem está logado no momento
  31. Ok, chega de query Se as queries são como o

    GET do REST, como fazer o POST / PUT / DELETE?
  32. Mutation Mutations são como o POST / PUT / DELETE

    do REST: Você usa quando haverá alteração nos dados. [POST] /v1/users [PUT] /v1/users/1 [DELETE] /v1/users/1 addUser(name: “Mateus”) updateUser(_id: 1, name: “Matheus”) deleteUser(_id: 1)
  33. Mutation Sintaxe mutation { addUser(name: “Bruno Lemos”) { _id name

    } } { "data": { "addUser": { "_id": "xxx_2", "name": "Bruno Lemos" } } }
  34. Mutation Sintaxe mutation { deleteUser(_id: “id_nao_existente”) } { "data": {

    "deleteUser": null }, "errors": [ { "message": "Usuário não encontrado.", "path": [ "deleteUser" ], } ] }
  35. Variáveis Sintaxe mutation($name: String!) { addUser(name: $name) { _id name

    } } //QUERY VARIABLES { "name": "Bruno Lemos" } { "data": { "addUser": { "_id": "xxx_3", "name": "Bruno Lemos" } } }
  36. Fragment Sintaxe { "data": { "user": { "_id": "xxx", "name":

    "Bruno Lemos", "github": "brunolemos" } } } query { user(_id: “xxx”) { _id ...RetornoPadrao } } fragment RetornoPadrao on User { name github }
  37. Vamos criar uma query que receba um argumento _id e

    retorne o usuário correspondente. Na prática
  38. // Vamos usar Node.js // Dependências: $ npm i -S

    express graphql express-graphql Antes de tudo... Servidor
  39. index.js const express = require('express'); const app = express(); const

    server = app.listen(process.env.PORT || 3000, () => { const { address, port } = server.address(); console.log(`Running at http://${address}:${port}`); }); Criando o servidor const graphqlHTTP = require('express-graphql'); const schema = require('./schema'); //será criado em breve app.use('/graphql', graphqlHTTP({ schema, graphiql: true }));
  40. ./user/type.js export default new GraphQLObjectType({ name: 'User', fields: { _id:

    { type: new GraphQLNonNull(GraphQLID) }, name: { type: GraphQLString }, }, }); Precisamos criar o tipo Usuário, que será o retorno da query Tipos já existentes: ID, String, Int, Boolean, Object, Enum, List, … (ver lista completa) Criando o servidor
  41. ./user/query.js Cada Query é um objeto comum Define o tipo

    de retorno, os argumentos de entrada e a função “resolve” Criando o servidor // bluebird converte uma funcao que pussui callback em uma promise const getUserAsync = Bluebird.promisify(myMethodFromOldApiThatUsesCallback); export default { type: UserType, // arquivo criado anteriormente args: { _id: { type: new GraphQLNonNull(GraphQLID) }, }, resolve: (root, args, context) => getUserAsync(args._id), // onde a mágica acontece };
  42. schema.js export default new GraphQLSchema({ query: new GraphQLObjectType({ name: 'RootQuery',

    fields: { user: UserQuery, // arquivo criado anteriormente // users: ..., // aqui iria as outras queries // posts: ..., }, }), mutation: ..., // definirá todas as mutations existentes (mesma sintaxe acima) }); Schemas possuem uma RootQuery e uma RootMutation Criando o servidor
  43. Autenticação query { login(email: “[email protected]”, password: “123”) { token _id

    name } } { "data": { "login": { "token": "ABCDEF", "_id": "xxx", "name": "Bruno Lemos" } } } Autenticação
  44. Autenticação query { viewer(token: “ABCDEF”) { me { _id name

    } } } { "data": { "viewer": { "me": { "_id": "xxx", "name": "Bruno Lemos" } } } } Autenticação
  45. Autenticação mutation { viewer(token: “ABCDEF”) { deleteUser(_id: “xxx”) } }

    { "data": { "viewer": { "deleteUser": true } } } Autenticação
  46. Autenticação mutation { viewer(token: “ABCDEF”) { deleteUser(_id: “id_de_outro_usuario”) } }

    { "data": { "viewer": { "deleteUser": null } }, "errors": [ { "message": "Não autorizado.", "path": [ "deleteUser" ], } ] } Autenticação
  47. ./viewer/query.js export default { ViewerRootQuery, // todas as queries que

    estarão dentro da query viewer args: { token: { type: GraphQLString }, }, resolve: (root, { token }, context) => { // context é global, acessível de todas as queries context.token = token; try { context.user = jwt.verify(token, config.jwtSecret) || {}; } catch (err) { context.user = {}; } return {}; }, }; Autenticação
  48. GraphiQL = Documentação automática, execução de queries, autocomplete, … É

    como ter um Graph API Explorer próprio! https://developers.facebook.com/tools/explorer
  49. No React, cada parte da aplicação é um Component Cada

    Component sabe os dados que precisa Como obter estes dados do GraphQL? Client
  50. Client: Relay class UserProfile extends Component { render() { var

    { name, avatar } = this.props.user; return ( <div> <img src={avatar}/> <p>{name}</p> </div> ); } } // continua... Relay
  51. Client: Relay Relay UserProfile = Relay.createContainer(UserProfile, { fragments: { user:

    () => Relay.QL` fragment on User { name, avatar, } `, }, });
  52. Server GraphQL não é apenas para Node.js. Existem implementações para

    Ruby, PHP, Go, Python, Haskell, ... Client Relay (react) Apollo (react, angular, ios swift, ...) Libs
  53. Libs Utils Graffiti (usar schema do Mongoose) Model Visualizer (converta

    seu schema em um diagrama) Services Reindex (backend as a service) Lista completa: Awesome GraphQL
  54. Não abordamos: • Cache / DataLoader Do GraphQL: • Subscriptions

    / realtime • Directives (@defer, @export, …) Próximos passos