├── .gitignore ├── LICENSE ├── README.md └── ex ├── 01 └── README.md ├── 02 ├── README.md └── src │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── schema.js ├── 03 ├── README.md └── src │ ├── hnclient │ └── index.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── schema │ └── index.js ├── 04 ├── README.md └── src │ ├── hnclient │ └── index.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── schema │ └── index.js ├── 05 ├── README.md └── src │ ├── hnclient │ └── index.js │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── schema │ ├── index.js │ ├── resolvers │ ├── Item.js │ ├── Query.js │ ├── User.js │ ├── index.js │ └── loaders │ │ └── index.js │ └── types │ ├── ITEM_TYPE.js │ ├── Item.js │ ├── Query.js │ ├── User.js │ ├── index.js │ └── schema.js └── 06 ├── README.md └── src ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── App.css ├── App.js ├── App.test.js ├── index.css ├── index.js ├── logo.svg └── registerServiceWorker.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 @ianaya89 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taller de GraphQL 2 | 3 | ## Materiales 4 | 5 | - [Documentación oficial de referencia](http://graphql.org/learn/) 6 | - [Cheat sheet para definir tipos](https://wehavefaces.net/graphql-shorthand-notation-cheatsheet-17cd715861b6) 7 | - [Librerías de JavaScript](http://graphql.org/code/#javascript) para crear servidores 8 | 9 | ## Requisitos 10 | - Node >= 7 y npm >=5 11 | - Un editor de texto 12 | - Un navegador moderno 13 | 14 | - Conocimiento de JavaScript y HTML 15 | - Conocimiento de cómo funcionan las aplicaciones Web (protocolo HTTP, AJAX, etc.) 16 | - Features de JavaScript como Promises, funciones asincrónicas 17 | - Alguna noción de React puede ser útil pero no es obligatorio. 18 | 19 | ## Temas 20 | 21 | - [Introducción a GraphQL](ex/01) 22 | - [Creando un servidor simple](ex/02) 23 | - [Conectándose a fuentes de datos](ex/03) 24 | - [El _schema_: tipos y resolvers](ex/04) 25 | - [_Batching_ y _caching_ con `dataloader`](ex/05) 26 | - [Un cliente GraphQL simple con Apollo Client](ex/06) 27 | - Paginación y _optimistic UI_ (work in progress) 28 | 29 | ## Créditos 30 | La estructura de este taller está basada en el excelente [taller de Vue.js de ianaya89](https://github.com/ianaya89/workshop-vuejs). 31 | 32 | ## Licencia 33 | 34 | Licencia MIT 35 | -------------------------------------------------------------------------------- /ex/01/README.md: -------------------------------------------------------------------------------- 1 | # Introducción a GraphQL 2 | 3 | GraphQL es un lenguaje y un _runtime_ para ejecutar consultas a orígenes de datos. Esos orígenes de datos pueden ser virtualmente cualquier cosa: bases de datos, APIs HTTP, archivos de texto, etc. 4 | 5 | Las consultas se realizan usando el protocolo HTTP, es decir que GraphQL está diseñado para la comunicación entre aplicaciones Web o mobile nativas con sus APIs. Acá se verá una y otra vez un contrapunto con las APIs REST: es lógico, pues el caso de uso de ambos es prácticamente el mismo. 6 | 7 | El lenguaje está estandarizado y [existen _runtimes_ en varias plataformas](http://graphql.org/code/), como Node, Ruby, Java y Go, entre otras. Nosotros en este taller vamos a usar [`graphql-js`](https://github.com/graphql/graphql-js), la librería hecha por Facebook para crear servidores GraphQL en Node. 8 | 9 | ## El lenguaje 10 | Como mencioné antes, GraphQL es un lenguaje. Particularmente uno diseñado para consultar una estructura de datos que tiene pinta de árbol. 11 | 12 | Veamos: 13 | 14 | ```graphql 15 | { 16 | allStarships { 17 | totalCount 18 | starships { 19 | name 20 | id 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | Esto es una consulta. Acá estamos pidiendo una entidad que se llama `allStarships`, que sabemos que tiene _nodos hijos_ denominados `totalCount` y `starships`. A la vez `starships` tiene otros dos nodos hijos: `name` y `id`. 27 | 28 | El servidor GraphQL va a responder con lo siguiente: 29 | 30 | ```json 31 | { 32 | "data": { 33 | "allStarships": { 34 | "totalCount": 36, 35 | "starships": [ 36 | { 37 | "name": "CR90 corvette", 38 | "id": "c3RhcnNoaXBzOjI=" 39 | }, 40 | { 41 | "name": "Star Destroyer", 42 | "id": "c3RhcnNoaXBzOjM=" 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | ``` 49 | 50 | El servidor GraphQL devuelve un JSON que tiene una estructura similar a lo que fue la consulta, metido dentro de una propiedad `"data"`. 51 | 52 | Si yo hubiera pedido más datos, el servidor me hubiera respondido con ellos. Y acá se ve una de las características importantes de GraphQL: sólo me trae los datos que le pido. Esto es algo bastante común en lenguajes de consulta a bases de datos como SQL, pero en consultas en el mundo de las aplicaciones Web no lo es tanto. En arquitecturas REST, una consulta a un endpoint siempre devuelve la misma estructura de datos, independientemente de si el cliente decide utilizar todos o algunos de ellos. Con lo cual usando GraphQL cada cliente puede controlar la cantidad de información que se transferirá. 53 | 54 | ## El _schema_ 55 | 56 | Uno podría preguntarse, entonces, ¿qué le puedo pedir a ese servidor GraphQL además de `starships`? Y de cada `starship`, ¿qué puedo saber además de su nombre y ID? 57 | 58 | Desde ya que no es arbitraria la forma en la que puedo estructurar mis queries, así como no es arbitrario qué puedo poner en un `SELECT` de SQL. Los datos en el servidor están estructurados de una forma y las consultas que yo pueda hacer están determinadas por esa estructura. Dicha estructura es lo que llamamos _schema_. 59 | 60 | Un _schema_ es una representación de los datos disponibles en mi servidor GraphQL. A diferencia de las bases de datos donde yo tengo tablas con campos y relaciones entre ellas, la estructura en GraphQL tiene forma de **grafo**. Cada uno de los nodos de ese grafo es algo que yo puedo consultar y tiene un **nombre**, **tipo** y una **descripción**, además de una relación con su nodo padre y sus hijos. 61 | 62 | Afortunadamente, el _schema_ es algo que también le puedo consultar a un servidor GraphQL con lo que se llama [_introspection queries_](http://graphql.org/learn/introspection/). Como corolario, los servidores GraphQL son autodocumentados: con una simple consulta puedo obtener el schema completo, con el nombre de cada nodo, su tipo y descripción. 63 | 64 | ___ 65 | 66 | - [Siguiente](../02) 67 | -------------------------------------------------------------------------------- /ex/02/README.md: -------------------------------------------------------------------------------- 1 | # Creando un servidor simple 2 | 3 | Ir de cero a "algo que responda queries GraphQL" requiere de cuatro simples pasos: 4 | 5 | 1. Set up de nuestra aplicación Node 6 | 2. Hacer que nuestra app responda requests HTTP con `express` 7 | 3. Crear un schema 8 | 4. Atar todos los cabos y probar 9 | 10 | ## Set up de nuestra aplicación Node 11 | 12 | Primero necesitamos generar el archivo `package.json`, donde `npm` (el package manager de Node) va a ir guardando un registro de las dependencias de nuestra aplicación. 13 | 14 | Una forma fácil de hacerlo es con el comando `npm init`. Al ejecutarlo, nos irá pidiendo datos de nuestra aplicación, como nombre, versión, URL del repositorio, etc. Probablemente sean cosas que todavía no queremos pensar, porque lo único que queremos es asegurarnos de que nuestro set up de GraphQL funciona. 15 | 16 | Aprovechando que el `package.json` lo podemos editar en cualquier momento, podemos ejecutar lo siguiente: 17 | 18 | ``` 19 | npm init --yes 20 | ``` 21 | 22 | Y listo, tenemos nuestro `package.json` básico generado. 23 | 24 | ## Express 25 | 26 | Lo que tenemos que hacer a continuación es incluir `express` como dependencia y empezar a codear nuestro server. 27 | 28 | Ejecutamos: 29 | 30 | ``` 31 | npm install --save express body-parser 32 | ``` 33 | 34 | Esto debería agregar en nuestro `package.json` a `express` y `body-parser` en la entrada `dependencies`. Además se debería haber creado un directorio `node_modules`, que es donde las dependencias que vamos instalando se guardan. 35 | 36 | Dicho esto, ya podemos codear un "hello world" de `express` y ver que todo marche bien. En el directorio raíz creamos un archivo `index.js` con el siguiente contenido: 37 | 38 | ```javascript 39 | // index.js 40 | const express = require('express') 41 | const app = express() 42 | 43 | app.get('/hello', function (req, res) { 44 | res.status(200).send('world!') 45 | }) 46 | 47 | app.listen(3000, function () { 48 | console.log('Our Node server is up and running on port 3000! Try http://localhost:3000/hello') 49 | }) 50 | ``` 51 | 52 | Guardamos el archivo y de vuelta en la terminal ejecutamos: 53 | 54 | ``` 55 | node index 56 | ``` 57 | 58 | Si no hay errores, debería aparecer el mensaje que escribimos en el `console.log`. Esto significa que nuestra app de Node está escuchando requests HTTP en el puerto 3000. 59 | 60 | En tu navegador escribí `http://localhost:3000/hello` en la barra de direcciones. Si aparece la palabra `world!` todo está funcionando correctamente. 61 | 62 | :warning: **Hint**: En todo momento podés presionar Ctrl+C para detener el servidor. 63 | 64 | ## Ahora sí, GraphQL 65 | 66 | En la terminal ejecutamos: 67 | 68 | ``` 69 | npm install --save graphql 70 | ``` 71 | 72 | La versión actual al momento de escribir este ejercicio es la 0.10.3. 73 | 74 | Con eso ya tenemos `graphql`, el paquete que necesitamos para hacer correr nuestro servidor GraphQL con Node. 75 | 76 | Antes de poder permitir a nuestro servidor aceptar y responder consultas GraphQL necesitamos definir un schema. Sin el schema los clientes no sabrían qué datos pueden consultar. 77 | 78 | El schema se define en puro JavaScript, utilizando objetos literales. Arranquemos con algo simple: 79 | 80 | ```js 81 | // schema.js 82 | const { GraphQLObjectType, GraphQLSchema, GraphQLString } = require('graphql') 83 | 84 | module.exports = new GraphQLSchema({ 85 | query: new GraphQLObjectType({ 86 | name: 'RootQueryType', 87 | fields: { 88 | hello: { 89 | type: GraphQLString, 90 | resolve: function () { 91 | return 'world' 92 | } 93 | } 94 | } 95 | }) 96 | }) 97 | ``` 98 | 99 | ¡Listo! Ya tenemos un schema. 100 | 101 |  102 | 103 | ¿Fui muy rápido? Muy bien, explicaré un poco qué es eso que acabamos de hacer. 104 | 105 | El resultado final es un árbol que describe nuestro schema. Ese árbol tiene esta pinta: 106 | 107 | ``` 108 | +------------+ 109 | | query | 110 | | (Object) | 111 | +------------+ 112 | | 113 | | 114 | +------------+ 115 | | hello | 116 | | (String) | 117 | +------------+ 118 | ``` 119 | 120 | Es decir que si yo quisiera consultar el nodo `hello` tendría que recorrer esa jerarquía en mi query: 121 | 122 | ```graphql 123 | query { 124 | hello 125 | } 126 | ``` 127 | 128 | `query` siempre es el nodo raíz y puedo no escribirlo: 129 | 130 | ```graphql 131 | { 132 | hello 133 | } 134 | ``` 135 | 136 | Usted estará pensando que el código en JavaScript que define el schema es demasiado complejo para definir una estructura tan simple. 137 | 138 | Veamos el mismo código paso a paso. Después de entenderlo quizás el código no le parezca tan complejo ni la estructura tan simple. 139 | 140 | ```js 141 | // schema.js 142 | // Importamos las clases del paquete `graphql` que nos ayudarán a definir nuestro schema. Hay muchas más. 143 | const { GraphQLObjectType, GraphQLSchema, GraphQLString } = require('graphql') 144 | 145 | // El punto de entrada es crear una instancia de `GraphQLSchema`. Al constructor se le pasa un objeto literal. 146 | module.exports = new GraphQLSchema({ 147 | // Ese objeto literal tiene como única entrada a `query`, que es una 148 | // instancia de `GraphQLObjectType`. 149 | query: new GraphQLObjectType({ 150 | // Query tiene un name y fields: un nombre que describe a este tipo 151 | // y los campos que puedo consultar. 152 | name: 'RootQueryType', 153 | fields: { 154 | // El único campo que definimos por ahora es `hello`, que es de tipo 155 | // String y tiene una función `resolve`... 156 | hello: { 157 | type: GraphQLString, 158 | // Hasta ahora no hablamos de las funciones `resolve`. 159 | // Básicamente son funciones que se ejecutan cuando el campo 160 | // al que pertenece es consultado. Si no se consulta, no se 161 | // ejecuta. El valor que devuelve la función es lo que devolverá 162 | // el campo. Estas funciones son centrales en lo que es la 163 | // potencia de GraphQL y las veremos en detalle más adelante. 164 | resolve: function () { 165 | return 'world' 166 | } 167 | } 168 | } 169 | }) 170 | }) 171 | ``` 172 | 173 | ## Juntando todo 174 | 175 | Ya tenemos nuestro server y nuestro schema. Lo que tenemos que hacer ahora es que nuestro server utilice el schema para responder consultas GraphQL. 176 | 177 | Volvemos a `index.js` y le hacemos un par de modificaciones. Tiene que quedar así: 178 | 179 | ```js 180 | const express = require('express') 181 | // Incluimos este middleware de `express` 182 | const bodyParser = require('body-parser') 183 | // ... y la función `graphql` 184 | const { graphql } = require('graphql') 185 | // Además importamos el schema que acabamos de definir 186 | const schema = require('./schema') 187 | 188 | const app = express() 189 | 190 | // Este middleware nos va a facilitar el parsing del request 191 | app.use(bodyParser.json()) 192 | 193 | // Le decimos a `express` que cuando llegue un request POST al endpoint /graphql 194 | // utilice esta función para resolverlo. Nótese que la función es asincrónica. 195 | app.post('/graphql', async function (req, res) { 196 | // Utilizamos la función `graphql` para ejecutar el query. El primer parámetro 197 | // es el schema y el segundo el query que viene en el request. 198 | const result = await graphql(schema, req.body.query) 199 | // Devolvemos el resultado del query (y lo formateamos para que el output sea 200 | // legible) 201 | res.send(JSON.stringify(result, null, 2)) 202 | }) 203 | 204 | app.listen(3000, function () { 205 | console.log('Our Node server is up and running on port 3000! Try http://localhost:3000/hello') 206 | }) 207 | ``` 208 | 209 | Volvemos a la terminal y escribimos `node index`. 210 | 211 | Ahora nuestro server atiende consultas GraphQL, podemos hacerle una usando `curl`: 212 | 213 | ``` 214 | curl -XPOST -d '{"query": "{hello}"}' -H 'Content-Type: application/json' http://localhost:3000/graphql 215 | ``` 216 | 217 | Debería devolver esto: 218 | ``` 219 | { 220 | "data": { 221 | "hello": "world" 222 | } 223 | } 224 | ``` 225 | 226 | Si así fue, ya puede poner en su currículum que sabe de GraphQL. 227 | 228 | ___ 229 | 230 | - [Siguiente](../03) 231 | - [Código](src) 232 | -------------------------------------------------------------------------------- /ex/02/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const bodyParser = require('body-parser') 3 | const { graphql } = require('graphql') 4 | const schema = require('./schema') 5 | const app = express() 6 | 7 | app.use(bodyParser.json()) 8 | 9 | app.get('/hello', function (req, res) { 10 | res.status(200).send('world!') 11 | }) 12 | 13 | app.post('/graphql', async function (req, res) { 14 | const result = await graphql(schema, req.body.query) 15 | res.send(JSON.stringify(result, null, 2)) 16 | }) 17 | 18 | app.listen(3000, function () { 19 | console.log('Our Node server is up and running on port 3000! Try http://localhost:3000/hello') 20 | }) 21 | -------------------------------------------------------------------------------- /ex/02/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "accepts": { 7 | "version": "1.3.3", 8 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 9 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" 10 | }, 11 | "array-flatten": { 12 | "version": "1.1.1", 13 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 14 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 15 | }, 16 | "body-parser": { 17 | "version": "1.17.2", 18 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 19 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=" 20 | }, 21 | "bytes": { 22 | "version": "2.4.0", 23 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 24 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 25 | }, 26 | "content-disposition": { 27 | "version": "0.5.2", 28 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 29 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 30 | }, 31 | "content-type": { 32 | "version": "1.0.2", 33 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 34 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 35 | }, 36 | "cookie": { 37 | "version": "0.3.1", 38 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 39 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 40 | }, 41 | "cookie-signature": { 42 | "version": "1.0.6", 43 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 44 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 45 | }, 46 | "debug": { 47 | "version": "2.6.7", 48 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 49 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 50 | }, 51 | "depd": { 52 | "version": "1.1.0", 53 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 54 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 55 | }, 56 | "destroy": { 57 | "version": "1.0.4", 58 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 59 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 60 | }, 61 | "ee-first": { 62 | "version": "1.1.1", 63 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 64 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 65 | }, 66 | "encodeurl": { 67 | "version": "1.0.1", 68 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 69 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 70 | }, 71 | "escape-html": { 72 | "version": "1.0.3", 73 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 74 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 75 | }, 76 | "etag": { 77 | "version": "1.8.0", 78 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 79 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 80 | }, 81 | "express": { 82 | "version": "4.15.3", 83 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 84 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" 85 | }, 86 | "finalhandler": { 87 | "version": "1.0.3", 88 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 89 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" 90 | }, 91 | "forwarded": { 92 | "version": "0.1.0", 93 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 94 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 95 | }, 96 | "fresh": { 97 | "version": "0.5.0", 98 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 99 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 100 | }, 101 | "graphql": { 102 | "version": "0.10.3", 103 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.10.3.tgz", 104 | "integrity": "sha1-wxOv1VGOZzNRvuGPtj4qDkh0B6s=" 105 | }, 106 | "http-errors": { 107 | "version": "1.6.1", 108 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 109 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" 110 | }, 111 | "iconv-lite": { 112 | "version": "0.4.15", 113 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 114 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 115 | }, 116 | "inherits": { 117 | "version": "2.0.3", 118 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 119 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 120 | }, 121 | "ipaddr.js": { 122 | "version": "1.3.0", 123 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 124 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 125 | }, 126 | "iterall": { 127 | "version": "1.1.1", 128 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.1.tgz", 129 | "integrity": "sha1-9/CvEemgTsZCYmD1AZ2fzKTVAhQ=" 130 | }, 131 | "media-typer": { 132 | "version": "0.3.0", 133 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 134 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 135 | }, 136 | "merge-descriptors": { 137 | "version": "1.0.1", 138 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 139 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 140 | }, 141 | "methods": { 142 | "version": "1.1.2", 143 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 144 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 145 | }, 146 | "mime": { 147 | "version": "1.3.4", 148 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 149 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 150 | }, 151 | "mime-db": { 152 | "version": "1.27.0", 153 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 154 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 155 | }, 156 | "mime-types": { 157 | "version": "2.1.15", 158 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 159 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" 160 | }, 161 | "ms": { 162 | "version": "2.0.0", 163 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 164 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 165 | }, 166 | "negotiator": { 167 | "version": "0.6.1", 168 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 169 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 170 | }, 171 | "on-finished": { 172 | "version": "2.3.0", 173 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 174 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 175 | }, 176 | "parseurl": { 177 | "version": "1.3.1", 178 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 179 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 180 | }, 181 | "path-to-regexp": { 182 | "version": "0.1.7", 183 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 184 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 185 | }, 186 | "proxy-addr": { 187 | "version": "1.1.4", 188 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 189 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" 190 | }, 191 | "qs": { 192 | "version": "6.4.0", 193 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 194 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 195 | }, 196 | "range-parser": { 197 | "version": "1.2.0", 198 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 199 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 200 | }, 201 | "raw-body": { 202 | "version": "2.2.0", 203 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 204 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" 205 | }, 206 | "send": { 207 | "version": "0.15.3", 208 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 209 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" 210 | }, 211 | "serve-static": { 212 | "version": "1.12.3", 213 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 214 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" 215 | }, 216 | "setprototypeof": { 217 | "version": "1.0.3", 218 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 219 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 220 | }, 221 | "statuses": { 222 | "version": "1.3.1", 223 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 224 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 225 | }, 226 | "type-is": { 227 | "version": "1.6.15", 228 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 229 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 230 | }, 231 | "unpipe": { 232 | "version": "1.0.0", 233 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 234 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 235 | }, 236 | "utils-merge": { 237 | "version": "1.0.0", 238 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 239 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 240 | }, 241 | "vary": { 242 | "version": "1.1.1", 243 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 244 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /ex/02/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.17.2", 14 | "express": "^4.15.3", 15 | "graphql": "^0.10.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ex/02/src/schema.js: -------------------------------------------------------------------------------- 1 | const { GraphQLObjectType, GraphQLSchema, GraphQLString } = require('graphql') 2 | 3 | module.exports = new GraphQLSchema({ 4 | query: new GraphQLObjectType({ 5 | name: 'RootQueryType', 6 | fields: { 7 | hello: { 8 | type: GraphQLString, 9 | resolve: function () { 10 | return 'world' 11 | } 12 | } 13 | } 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /ex/03/README.md: -------------------------------------------------------------------------------- 1 | # Conectándose a una API externa 2 | 3 | Vamos a suponer el caso en el que necesitamos _wrappear_ una API existente. Como dijimos en la introducción, GraphQL puede conectarse a prácticamente cualquier fuente de datos y, schema mediante, darle la posibilidad a un cliente de consultarlo. 4 | 5 | Nuestro backend podría ser una combinación de una base de datos, archivos de texto, APIs REST... lo que se nos ocurra. El arte acá está en que toda esa información que yo puedo consultar desde múltiples orígenes y formatos debería poder representarla en un _schema_, es decir, en un árbol. Con lo cual tiene sentido que esos datos tengan algo que ver entre sí, conceptual y estructuralmente. 6 | 7 | Dicho esto, vamos a empezar con algo sencillo. Hacker News publicó [una API](https://github.com/HackerNews/API) desde la que se puede consumir sus historias. 8 | Esta API consiste en algunos endpoints HTTP, de los cuales mencionaré algunos. 9 | 10 | ## La API de Hacker News 11 | 12 | ### Stories 13 | 14 | En `/v0/topstories.json` puedo consultar los "top stories" de Hacker News. Lo que me va a devolver este endpoint es un JSON parecido a este: 15 | 16 | ```json 17 | [ 18 | 14754772, 19 | 14757629, 20 | 14752392, 21 | ... 22 | ] 23 | ``` 24 | 25 | (esto recortando el output, puede traer más de 300 items). 26 | 27 | Cada uno de esos números representa el ID de un _item_. Cómo obtener el detalle de cada item lo explico a continuación. 28 | 29 | ### Items 30 | 31 | En `/v0/item/{id}.json` puedo obtener un item (en el esquema de datos de HN una historia y un comentario son la misma cosa, un "item", pero cada item puede tener items hijos). 32 | Este endpoint responderá con un JSON, que tiene esta pinta: 33 | 34 | ```json 35 | { 36 | "by" : "tel", 37 | "descendants" : 16, 38 | "id" : 121003, 39 | "kids" : [ 121016, 121109, 121168 ], 40 | "score" : 25, 41 | "text" : "or HN: the Next Iteration
I get ...", 42 | "time" : 1203647620, 43 | "title" : "Ask HN: The Arc Effect", 44 | "type" : "story", 45 | "url" : "" 46 | } 47 | ``` 48 | 49 | Nótese que `kids` es un array de IDs de otras historias, `by` es el ID del autor y `type` dice qué tipo de item es. 50 | 51 | ### Users 52 | 53 | Por último, cada item (story, comment, etc.) tiene un autor, una persona que lo generó. La entidad que representa a esa persona es un `user` y se consulta en el endpoint `/v0/user/{id}.json` donde `id` es el ID del usuario, por ejemplo `tel` en el ejemplo de arriba. 54 | 55 | La respuesta de ese endpoint tiene esta pinta: 56 | 57 | ```json 58 | { 59 | "about" : "jspha.com / @sdbo / me@jspha.com / joseph@reifyhealth.com
[ my public key: https://keybase.io/tel; my proof: https://keybase.io/tel/sigs/hy256UGUMMtc9NhfOsck4vuCiby6UYNLw7VXBj4fGR8 ]", 60 | "created" : 1187238300, 61 | "id" : "tel", 62 | "karma" : 9560, 63 | "submitted" : [ 14644912, 14628669, 14606196 ... ] 64 | } 65 | ``` 66 | 67 | En el array `submitted` hay IDs de items. `created` tiene la fecha de creación en formato epoch. 68 | 69 | ## El cliente 70 | 71 | Conocer la API que vamos a _wrappear_ es importante. Si esta es consistente, enseguida se nos va a ir ocurriendo cómo diseñar el schema, que es la pieza más importante del sistema que estamos creando. 72 | 73 | En este caso, en una primera aproximación a la API de HN vimos que tenemos dos entidades importantes, que se relacionan entre sí: items y users. Los items pueden ser stories o comentarios. Los usuarios crean stories y además comentan stories de otros usuarios. 74 | 75 | Lo que conviene antes de empezar a diseñar el schema es escribir un poco de código de plomería: necesitamos desde nuestra aplicación Node poder consultar la API de HN. 76 | Esa consulta va a ser por HTTP, por lo tanto nos viene bien un cliente HTTP simple: 77 | 78 | ``` 79 | npm install --save request request-promise-native 80 | ``` 81 | 82 | Para mantener el boliche ordenado, vamos a crear un directorio `hnclient` y dentro un archivo `index.js`. Dentro de ese archivo escribiremos nuestros clientes: 83 | 84 | ```js 85 | // hnclient/index.js 86 | const request = require('request-promise-native') 87 | 88 | const baseURL = 'https://hacker-news.firebaseio.com/v0' 89 | 90 | module.exports.getItem = function (id) { 91 | return request.get(`${baseURL}/item/${id}.json`, { json: true }) 92 | } 93 | 94 | module.exports.getTopStories = function () { 95 | return request.get(`${baseURL}/topstories.json`, { json: true }) 96 | } 97 | 98 | module.exports.getUser = function (id) { 99 | return request.get(`${baseURL}/user/${id}.json`, { json: true }) 100 | } 101 | ``` 102 | 103 | Acá creé tres funciones que me van a ser de ayuda para consultar la API de HN. Vamos a arrancar con estas tres y si es necesario más adelante agregamos alguna más. Algo importante es que cada una de las funciones me devuelve una `Promise`. 104 | 105 | Por otro lado, para probar rápidamente que las funciones andan bien, podemos crear un endpoint de prueba (también podríamos crear unit tests, lo recomiendo, pero para este proyecto nos alcanza con ver que no tenemos problemas conectándonos a la API). 106 | 107 | ```js 108 | // index.js 109 | // 110 | // [ omito el resto del archivo por brevedad] 111 | 112 | const { getTopStories, getItem, getUser } = require('./hnclient') 113 | 114 | app.get('/test', async function (req, res, next) { 115 | try { 116 | // Como las funciones devuelven `Promise`s, tengo que anteponerles `await` a cada invocación. 117 | const stories = await getTopStories() 118 | // Tomo el ID de la primer story y obtengo todos sus datos 119 | const story = await getItem(stories[0]) 120 | // y de esa story obtengo el autor y todos sus datos 121 | const author = await getUser(story.by) 122 | res.status(200).send({ 123 | stories, 124 | story, 125 | author 126 | }) 127 | } catch (e) { 128 | // Esto es un patrón de `express` que nos permite capturar un error, si hubiera, y mostrarlo en la respuesta 129 | next(e) 130 | } 131 | }) 132 | ``` 133 | 134 | Luego, reiniciando el server y conectándonos a `http://localhost:3000/test` deberíamos ver una respuesta en JSON (y no un error). 135 | 136 | Si todo marcha bien, significa que nuestro código de plomería funciona y que tenemos conexión a la API que vamos a wrappear. 137 | 138 | ¿Qué sigue? Sí, adivinaron: crear el schema. 139 | 140 | ___ 141 | 142 | - [Siguiente](../04) 143 | - [Código](src) 144 | -------------------------------------------------------------------------------- /ex/03/src/hnclient/index.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise-native') 2 | 3 | const baseURL = 'https://hacker-news.firebaseio.com/v0' 4 | 5 | module.exports.getItem = function (id) { 6 | return request.get(`${baseURL}/item/${id}.json`, { json: true }) 7 | } 8 | 9 | module.exports.getTopStories = function () { 10 | return request.get(`${baseURL}/topstories.json`, { json: true }) 11 | } 12 | 13 | module.exports.getUser = function (id) { 14 | return request.get(`${baseURL}/user/${id}.json`, { json: true }) 15 | } 16 | -------------------------------------------------------------------------------- /ex/03/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const bodyParser = require('body-parser') 3 | const { graphql } = require('graphql') 4 | 5 | const { getTopStories, getItem, getUser } = require('./hnclient') 6 | const schema = require('./schema') 7 | 8 | const app = express() 9 | 10 | app.use(bodyParser.json()) 11 | 12 | app.post('/graphql', async function (req, res) { 13 | const result = await graphql(schema, req.body.query) 14 | res.send(JSON.stringify(result, null, 2)) 15 | }) 16 | 17 | app.get('/test', async function (req, res, next) { 18 | try { 19 | const stories = await getTopStories() 20 | // Tomo el ID de la primer story y obtengo todos sus datos 21 | const story = await getItem(stories[0]) 22 | // y de esa story obtengo el autor y todos sus datos 23 | const author = await getUser(story.by) 24 | res.status(200).send({ 25 | stories, 26 | story, 27 | author 28 | }) 29 | } catch (e) { 30 | next(e) 31 | } 32 | }) 33 | 34 | app.listen(3000, function () { 35 | console.log('Our Node server is up and running on port 3000!') 36 | }) 37 | -------------------------------------------------------------------------------- /ex/03/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "accepts": { 7 | "version": "1.3.3", 8 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 9 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" 10 | }, 11 | "ajv": { 12 | "version": "4.11.8", 13 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", 14 | "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=" 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "asn1": { 22 | "version": "0.2.3", 23 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 24 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 25 | }, 26 | "assert-plus": { 27 | "version": "0.2.0", 28 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", 29 | "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" 30 | }, 31 | "asynckit": { 32 | "version": "0.4.0", 33 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 34 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 35 | }, 36 | "aws-sign2": { 37 | "version": "0.6.0", 38 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", 39 | "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" 40 | }, 41 | "aws4": { 42 | "version": "1.6.0", 43 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 44 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 45 | }, 46 | "bcrypt-pbkdf": { 47 | "version": "1.0.1", 48 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 49 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 50 | "optional": true 51 | }, 52 | "body-parser": { 53 | "version": "1.17.2", 54 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 55 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=" 56 | }, 57 | "boom": { 58 | "version": "2.10.1", 59 | "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", 60 | "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" 61 | }, 62 | "bytes": { 63 | "version": "2.4.0", 64 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 65 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 66 | }, 67 | "caseless": { 68 | "version": "0.12.0", 69 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 70 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 71 | }, 72 | "co": { 73 | "version": "4.6.0", 74 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 75 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 76 | }, 77 | "combined-stream": { 78 | "version": "1.0.5", 79 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 80 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" 81 | }, 82 | "content-disposition": { 83 | "version": "0.5.2", 84 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 85 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 86 | }, 87 | "content-type": { 88 | "version": "1.0.2", 89 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 90 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 91 | }, 92 | "cookie": { 93 | "version": "0.3.1", 94 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 95 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 96 | }, 97 | "cookie-signature": { 98 | "version": "1.0.6", 99 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 100 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 101 | }, 102 | "cryptiles": { 103 | "version": "2.0.5", 104 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", 105 | "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" 106 | }, 107 | "dashdash": { 108 | "version": "1.14.1", 109 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 110 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 111 | "dependencies": { 112 | "assert-plus": { 113 | "version": "1.0.0", 114 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 115 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 116 | } 117 | } 118 | }, 119 | "debug": { 120 | "version": "2.6.7", 121 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 122 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 123 | }, 124 | "delayed-stream": { 125 | "version": "1.0.0", 126 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 127 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 128 | }, 129 | "depd": { 130 | "version": "1.1.0", 131 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 132 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 133 | }, 134 | "destroy": { 135 | "version": "1.0.4", 136 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 137 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 138 | }, 139 | "ecc-jsbn": { 140 | "version": "0.1.1", 141 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 142 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 143 | "optional": true 144 | }, 145 | "ee-first": { 146 | "version": "1.1.1", 147 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 148 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 149 | }, 150 | "encodeurl": { 151 | "version": "1.0.1", 152 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 153 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 154 | }, 155 | "escape-html": { 156 | "version": "1.0.3", 157 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 158 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 159 | }, 160 | "etag": { 161 | "version": "1.8.0", 162 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 163 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 164 | }, 165 | "express": { 166 | "version": "4.15.3", 167 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 168 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" 169 | }, 170 | "extend": { 171 | "version": "3.0.1", 172 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 173 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 174 | }, 175 | "extsprintf": { 176 | "version": "1.0.2", 177 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", 178 | "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" 179 | }, 180 | "finalhandler": { 181 | "version": "1.0.3", 182 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 183 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" 184 | }, 185 | "forever-agent": { 186 | "version": "0.6.1", 187 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 188 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 189 | }, 190 | "form-data": { 191 | "version": "2.1.4", 192 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", 193 | "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" 194 | }, 195 | "forwarded": { 196 | "version": "0.1.0", 197 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 198 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 199 | }, 200 | "fresh": { 201 | "version": "0.5.0", 202 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 203 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 204 | }, 205 | "getpass": { 206 | "version": "0.1.7", 207 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 208 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 209 | "dependencies": { 210 | "assert-plus": { 211 | "version": "1.0.0", 212 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 213 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 214 | } 215 | } 216 | }, 217 | "graphql": { 218 | "version": "0.10.3", 219 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.10.3.tgz", 220 | "integrity": "sha1-wxOv1VGOZzNRvuGPtj4qDkh0B6s=" 221 | }, 222 | "har-schema": { 223 | "version": "1.0.5", 224 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", 225 | "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" 226 | }, 227 | "har-validator": { 228 | "version": "4.2.1", 229 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", 230 | "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=" 231 | }, 232 | "hawk": { 233 | "version": "3.1.3", 234 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", 235 | "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" 236 | }, 237 | "hoek": { 238 | "version": "2.16.3", 239 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", 240 | "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" 241 | }, 242 | "http-errors": { 243 | "version": "1.6.1", 244 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 245 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" 246 | }, 247 | "http-signature": { 248 | "version": "1.1.1", 249 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", 250 | "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" 251 | }, 252 | "iconv-lite": { 253 | "version": "0.4.15", 254 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 255 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 256 | }, 257 | "inherits": { 258 | "version": "2.0.3", 259 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 260 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 261 | }, 262 | "ipaddr.js": { 263 | "version": "1.3.0", 264 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 265 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 266 | }, 267 | "is-typedarray": { 268 | "version": "1.0.0", 269 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 270 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 271 | }, 272 | "isstream": { 273 | "version": "0.1.2", 274 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 275 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 276 | }, 277 | "iterall": { 278 | "version": "1.1.1", 279 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.1.tgz", 280 | "integrity": "sha1-9/CvEemgTsZCYmD1AZ2fzKTVAhQ=" 281 | }, 282 | "jsbn": { 283 | "version": "0.1.1", 284 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 285 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 286 | "optional": true 287 | }, 288 | "json-schema": { 289 | "version": "0.2.3", 290 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 291 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 292 | }, 293 | "json-stable-stringify": { 294 | "version": "1.0.1", 295 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 296 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=" 297 | }, 298 | "json-stringify-safe": { 299 | "version": "5.0.1", 300 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 301 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 302 | }, 303 | "jsonify": { 304 | "version": "0.0.0", 305 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 306 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" 307 | }, 308 | "jsprim": { 309 | "version": "1.4.0", 310 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", 311 | "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", 312 | "dependencies": { 313 | "assert-plus": { 314 | "version": "1.0.0", 315 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 316 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 317 | } 318 | } 319 | }, 320 | "lodash": { 321 | "version": "4.17.4", 322 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 323 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 324 | }, 325 | "media-typer": { 326 | "version": "0.3.0", 327 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 328 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 329 | }, 330 | "merge-descriptors": { 331 | "version": "1.0.1", 332 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 333 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 334 | }, 335 | "methods": { 336 | "version": "1.1.2", 337 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 338 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 339 | }, 340 | "mime": { 341 | "version": "1.3.4", 342 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 343 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 344 | }, 345 | "mime-db": { 346 | "version": "1.27.0", 347 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 348 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 349 | }, 350 | "mime-types": { 351 | "version": "2.1.15", 352 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 353 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" 354 | }, 355 | "ms": { 356 | "version": "2.0.0", 357 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 358 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 359 | }, 360 | "negotiator": { 361 | "version": "0.6.1", 362 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 363 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 364 | }, 365 | "oauth-sign": { 366 | "version": "0.8.2", 367 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 368 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 369 | }, 370 | "on-finished": { 371 | "version": "2.3.0", 372 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 373 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 374 | }, 375 | "parseurl": { 376 | "version": "1.3.1", 377 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 378 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 379 | }, 380 | "path-to-regexp": { 381 | "version": "0.1.7", 382 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 383 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 384 | }, 385 | "performance-now": { 386 | "version": "0.2.0", 387 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", 388 | "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" 389 | }, 390 | "proxy-addr": { 391 | "version": "1.1.4", 392 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 393 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" 394 | }, 395 | "punycode": { 396 | "version": "1.4.1", 397 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 398 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 399 | }, 400 | "qs": { 401 | "version": "6.4.0", 402 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 403 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 404 | }, 405 | "range-parser": { 406 | "version": "1.2.0", 407 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 408 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 409 | }, 410 | "raw-body": { 411 | "version": "2.2.0", 412 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 413 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" 414 | }, 415 | "request": { 416 | "version": "2.81.0", 417 | "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", 418 | "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=" 419 | }, 420 | "request-promise-core": { 421 | "version": "1.1.1", 422 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", 423 | "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=" 424 | }, 425 | "request-promise-native": { 426 | "version": "1.0.4", 427 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.4.tgz", 428 | "integrity": "sha1-hpiOyO7kCORVefzoO/0Fs635oVU=" 429 | }, 430 | "safe-buffer": { 431 | "version": "5.1.1", 432 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 433 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 434 | }, 435 | "send": { 436 | "version": "0.15.3", 437 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 438 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" 439 | }, 440 | "serve-static": { 441 | "version": "1.12.3", 442 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 443 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" 444 | }, 445 | "setprototypeof": { 446 | "version": "1.0.3", 447 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 448 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 449 | }, 450 | "sntp": { 451 | "version": "1.0.9", 452 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", 453 | "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" 454 | }, 455 | "sshpk": { 456 | "version": "1.13.1", 457 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", 458 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", 459 | "dependencies": { 460 | "assert-plus": { 461 | "version": "1.0.0", 462 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 463 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 464 | } 465 | } 466 | }, 467 | "statuses": { 468 | "version": "1.3.1", 469 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 470 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 471 | }, 472 | "stealthy-require": { 473 | "version": "1.1.1", 474 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 475 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 476 | }, 477 | "stringstream": { 478 | "version": "0.0.5", 479 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", 480 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" 481 | }, 482 | "tough-cookie": { 483 | "version": "2.3.2", 484 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", 485 | "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=" 486 | }, 487 | "tunnel-agent": { 488 | "version": "0.6.0", 489 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 490 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" 491 | }, 492 | "tweetnacl": { 493 | "version": "0.14.5", 494 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 495 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 496 | "optional": true 497 | }, 498 | "type-is": { 499 | "version": "1.6.15", 500 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 501 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 502 | }, 503 | "unpipe": { 504 | "version": "1.0.0", 505 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 506 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 507 | }, 508 | "utils-merge": { 509 | "version": "1.0.0", 510 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 511 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 512 | }, 513 | "uuid": { 514 | "version": "3.1.0", 515 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 516 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 517 | }, 518 | "vary": { 519 | "version": "1.1.1", 520 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 521 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 522 | }, 523 | "verror": { 524 | "version": "1.3.6", 525 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", 526 | "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" 527 | } 528 | } 529 | } 530 | -------------------------------------------------------------------------------- /ex/03/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.17.2", 14 | "express": "^4.15.3", 15 | "graphql": "^0.10.3", 16 | "request": "^2.81.0", 17 | "request-promise-native": "^1.0.4" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ex/03/src/schema/index.js: -------------------------------------------------------------------------------- 1 | const { GraphQLObjectType, GraphQLSchema, GraphQLString } = require('graphql') 2 | 3 | module.exports = new GraphQLSchema({ 4 | query: new GraphQLObjectType({ 5 | name: 'RootQueryType', 6 | fields: { 7 | items: { 8 | type: GraphQLString, 9 | resolve: function () { 10 | return 'world' 11 | } 12 | } 13 | } 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /ex/04/README.md: -------------------------------------------------------------------------------- 1 | # El _schema_ 2 | 3 | Quizás la parte más importante de diseñar una API de GraphQL sea crear el _schema_. Es importante pues una vez que nuestra API esté en producción, un cambio en el schema podría romper la compatibilidad con los clientes que ya estén consumiendo la API. O sea, no podemos andar improvisando mucho acá si queremos estar en producción lo antes posible. 4 | 5 | Diseñar el _schema_ requiere tener un entendimiento cabal del dominio de datos que vamos a exponer. En nuestro caso, estamos hablando de la [API de Hacker News](https://github.com/HackerNews/API). Es sencilla y está bien documentada, por lo cual con una primer lectura conscienzuda ya hay cosas que van saltando a la vista. 6 | 7 | Un _schema_ es básicamente la declaración de los tipos de cada una de nuestras entidades. Cada tipo tiene campos, que pueden ser de uno de los tipos primitivos (`Boolean`, `Int`, etc.) o de otro tipo que hayamos definido. Además cada uno de los tipos puede (o no) tener un _resolver_, que es una función de JavaScript que indica cómo obtener el dato desde el backend. 8 | 9 | Por último, el _schema_ debe sí o sí tener un tipo `Query` que indica los puntos de entrada de un query. 10 | 11 | ## La API de Hacker News 12 | 13 | Entonces, la entidad más importante parece ser los [_items_](https://github.com/HackerNews/API#items): pueden ser stories, comments, etc., es decir, tanto las entradas que se publican como los comentarios que se hacen en las mismas. 14 | 15 | Un item tiene `id`, `deleted`, `type`, `by`, etc. Tanto `by` como `parent` y `kids` son IDs de otras entidades: `by` es el ID del usuario que creó el _item_ y `parent` y `kids` apuntan a otros _items_. 16 | 17 | Entonces, un `Item` podría tener esta pinta: 18 | 19 | ```graphql 20 | type Item { 21 | id: Int! 22 | deleted: Boolean 23 | type: String! 24 | by: User 25 | time: Int! 26 | text: String 27 | dead: Boolean 28 | parent: Item 29 | kids: [Item] 30 | url: String 31 | score: Int 32 | title: String 33 | } 34 | ``` 35 | 36 | Acá ya estamos introduciendo un poco del lenguaje que vamos a utilizar para definir tipos en GraphQL. Básicamente le damos un nombre al tipo, definimos los campos que va a tener y los tipos de cada uno. Nótense estos casos: 37 | 38 | - `parent` es del mismo tipo `Item` 39 | - Lo que está entre corchetes (`[]`) implica que es un array 40 | - Lo que tiene `!` implica que ese campo es no-nulo, o sea que lo que *vuelva del backend* no puede ser nulo, caso contrario la consulta devolverá error. 41 | 42 | Habrán notado que el campo `by` es de tipo `User`, pero todavía no lo definimos. Hagámoslo. 43 | 44 | ```graphql 45 | type User { 46 | id: String! 47 | about: String 48 | created: Int 49 | delay: Int, 50 | karma: Int! 51 | submitted: [Item] 52 | } 53 | ``` 54 | 55 | Con `Item` y `User` definidos podemos mapear casi todo el dominio de datos de la API de Hacker News. Juntamos todo y definimos el tipo `Query`, así: 56 | 57 | ```graphql 58 | type User { 59 | about: String 60 | created: Int 61 | delay: Int, 62 | id: String! 63 | karma: Int! 64 | submitted: [Item] 65 | } 66 | 67 | enum ITEM_TYPE { 68 | JOB 69 | STORY 70 | COMMENT 71 | POLL 72 | POLLOPT 73 | } 74 | 75 | type Item { 76 | id: Int! 77 | by: User! 78 | kids: [Item] 79 | score: Int! 80 | time: Int 81 | title: String 82 | type: ITEM_TYPE 83 | url: String 84 | text: String 85 | dead: Boolean 86 | } 87 | 88 | type Query { 89 | item(id: Int!): Item 90 | stories(page: Int, count: Int): [Item] 91 | user(id: String!): User 92 | } 93 | 94 | schema { 95 | query: Query 96 | } 97 | ``` 98 | 99 | Para incluir nuestros tipos en la aplicación, vamos a hacer unos pequeños cambios. Dado que el paquete `graphql` sólo permite definir el _schema_ usando la notación de objetos de JavaScript, vamos a usar otros paquetes que nos permiten definirla con la notación que usamos más arriba, que es muchísimo más clara. 100 | 101 | Ejecutamos lo siguiente en nuestra Terminal: 102 | 103 | ``` 104 | npm install --save graphql-tools graphql-server-express 105 | ``` 106 | 107 | Hacemos un poco de limpieza en `index.js` y lo dejamos así: 108 | 109 | ```js 110 | // index.js 111 | const express = require('express') 112 | const { graphqlExpress, graphiqlExpress } = require('graphql-server-express') 113 | const bodyParser = require('body-parser') 114 | const schema = require('./schema') 115 | 116 | const app = express() 117 | 118 | // Este endpoint utilizará el schema para parsear las consultas que lleguen por POST a /graphql 119 | app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })) 120 | 121 | // Este endpoint disponibiliza GraphiQL en el endpoint /graphiql 122 | app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) 123 | 124 | app.listen(3000, function () { 125 | console.log('Our Node server is up and running on port 3000!') 126 | }) 127 | ``` 128 | 129 | En `schema/index.js` incluimos los tipos en el _schema_: 130 | 131 | ```js 132 | // schema/index.js 133 | const { makeExecutableSchema } = require('graphql-tools') 134 | const { getItem, getUser, getTopStories } = require('../hnclient') 135 | 136 | const typeDefs = [` 137 | type User { 138 | about: String 139 | created: Int 140 | delay: Int, 141 | id: String! 142 | karma: Int! 143 | submitted: [Item] 144 | } 145 | 146 | enum ITEM_TYPE { 147 | JOB 148 | STORY 149 | COMMENT 150 | POLL 151 | POLLOPT 152 | } 153 | 154 | type Item { 155 | id: Int! 156 | by: User! 157 | kids: [Item] 158 | score: Int! 159 | time: Int 160 | title: String 161 | type: ITEM_TYPE 162 | url: String 163 | text: String 164 | dead: Boolean 165 | } 166 | 167 | type Query { 168 | item(id: Int!): Item 169 | stories(page: Int, count: Int): [Item] 170 | user(id: String!): User 171 | } 172 | 173 | schema { 174 | query: Query 175 | } 176 | `] 177 | 178 | const resolvers = {} 179 | 180 | module.exports = makeExecutableSchema({ typeDefs, resolvers }) 181 | ``` 182 | 183 | Usamos la función `makeExecutableSchema` que toma como parámetro un objeto con dos propiedades: `typeDefs` (los tipos) y `resolvers`, las funciones que resuelven cada campo. 184 | 185 | ## Resolvers 186 | 187 | Los campos que sí o sí van a necesitar resolvers son `item`, `stories` y `user` del tipo `Query`. 188 | 189 | - `item` recibe como parámetro un número y devuelve algo de tipo `Item`. Utilizaremos la función `getItem` de `hnclient` para devolver ese dato. 190 | - `stories` devuelve un array de `Item`s, y recibe opcionalmente un número de página y la cantidad de items a devolver. 191 | - `user` recibe el ID de un usuario que es un string y devuelve algo de tipo `User`. 192 | 193 | Los implementamos así: 194 | 195 | ```js 196 | const resolvers = { 197 | Query: { 198 | item (_, { id }) { 199 | // 200 | }, 201 | 202 | stories (_, { page, count }) { 203 | // 204 | }, 205 | 206 | user (_, { id }) { 207 | // 208 | } 209 | } 210 | } 211 | ``` 212 | 213 | Es decir, `resolvers` será un objeto que mapeará la forma que tienen los tipos. Para los resolvers del tipo `Query` se creará una entrada con la key `Query` que será un objeto cuyas keys serán los nombres de los campos del tipo `Query`: `item`, `stories` y `user`. El segundo parámetro de cada función es un objeto que contiene los valores de los parámetros que se pasan en el query. Los desestructuramos para ganar en legibilidad. 214 | 215 | Ahora agregamos la implementación de cada función: 216 | 217 | ```js 218 | const resolvers = { 219 | Query: { 220 | item (_, { id }) { 221 | return getItem(id) 222 | }, 223 | 224 | async stories (_, { page, count }) { 225 | const stories = await getTopStories() 226 | if (page && count) { 227 | return stories 228 | .slice(page * count, (page * count) + count) 229 | .map(getItem) 230 | } 231 | 232 | return stories.map(getItem) 233 | }, 234 | 235 | user (_, { id }) { 236 | return getUser(id) 237 | } 238 | } 239 | } 240 | ``` 241 | 242 | De los otros tipos, `Item` y `User`, también hay campos que requieren de _resolvers_. 243 | 244 | Por ejemplo, `submitted` de `User` devuelve un array de `Item`, pero el backend devuelve sólo los IDs, con lo cual ese campo tiene que tener un resolver que por cada ID resuelva el `Item` y lo devuelva. 245 | 246 | Tendría esta pinta: 247 | 248 | ```js 249 | { 250 | // ... 251 | User: { 252 | submitted ({ submitted }) { 253 | // `submitted` es un array de IDs, y tengo que devolver un array de una 254 | // estructura que sea mappeable al tipo Item. 255 | // Es decir, por cada ID llamo al backend de Hacker News para obtener el item. 256 | return submitted.map(id => getItem(id)) 257 | } 258 | } 259 | } 260 | ``` 261 | 262 | Nótese que usamos el primer parámetro de la función. Este no tiene los argumentos, sino que tiene una referencia al nodo padre del query, de quien obtenemos el array de IDs y lo reemplazamos por el array de `Item`s. 263 | 264 | Agregando los resolvers que faltan y juntando todo quedaría así: 265 | 266 | ```js 267 | const resolvers = { 268 | Query: { 269 | item (_, { id }) { 270 | return getItem(id) 271 | }, 272 | 273 | async stories (_, { page, count }) { 274 | const stories = await getTopStories() 275 | if (page && count) { 276 | return stories 277 | .slice(page * count, (page * count) + count) 278 | .map(getItem) 279 | } 280 | 281 | return stories.map(getItem) 282 | }, 283 | 284 | user (_, { id }) { 285 | return getUser(id) 286 | } 287 | }, 288 | Item: { 289 | by ({ by }) { 290 | return getUser(by) 291 | }, 292 | kids ({ kids }) { 293 | return kids.map(getItem) 294 | }, 295 | type ({ type }) { 296 | return type.toUpperCase() 297 | } 298 | }, 299 | User: { 300 | submitted ({ submitted }) { 301 | return submitted.map(getItem) 302 | } 303 | } 304 | } 305 | ``` 306 | 307 | ___ 308 | 309 | - [Siguiente](../05) 310 | - [Código](src) 311 | -------------------------------------------------------------------------------- /ex/04/src/hnclient/index.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise-native') 2 | 3 | const baseURL = 'https://hacker-news.firebaseio.com/v0' 4 | 5 | module.exports.getItem = function (id) { 6 | return request.get(`${baseURL}/item/${id}.json`, { json: true }) 7 | } 8 | 9 | module.exports.getTopStories = function () { 10 | return request.get(`${baseURL}/topstories.json`, { json: true }) 11 | } 12 | 13 | module.exports.getUser = function (id) { 14 | return request.get(`${baseURL}/user/${id}.json`, { json: true }) 15 | } 16 | -------------------------------------------------------------------------------- /ex/04/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { graphqlExpress, graphiqlExpress } = require('graphql-server-express') 3 | const bodyParser = require('body-parser') 4 | const schema = require('./schema') 5 | 6 | const app = express() 7 | 8 | app.use('/graphql', bodyParser.json(), graphqlExpress({ schema })) 9 | app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) 10 | 11 | app.listen(3000, function () { 12 | console.log('Our Node server is up and running on port 3000!') 13 | }) 14 | -------------------------------------------------------------------------------- /ex/04/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@types/express": { 7 | "version": "4.0.36", 8 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.36.tgz", 9 | "integrity": "sha512-bT9q2eqH/E72AGBQKT50dh6AXzheTqigGZ1GwDiwmx7vfHff0bZOrvUWjvGpNWPNkRmX1vDF6wonG6rlpBHb1A==", 10 | "optional": true 11 | }, 12 | "@types/express-serve-static-core": { 13 | "version": "4.0.49", 14 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.49.tgz", 15 | "integrity": "sha512-b7mVHoURu1xaP/V6xw1sYwyv9V0EZ7euyi+sdnbnTZxEkAh4/hzPsI6Eflq+ZzHQ/Tgl7l16Jz+0oz8F46MLnA==" 16 | }, 17 | "@types/graphql": { 18 | "version": "0.9.4", 19 | "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-0.9.4.tgz", 20 | "integrity": "sha512-ob2dps4itT/Le5DbxjssBXtBnloDIRUbkgtAvaB42mJ8pVIWMRuURD9WjnhaEGZ4Ql/EryXMQWeU8Y0EU73QLw==", 21 | "optional": true 22 | }, 23 | "@types/mime": { 24 | "version": "1.3.1", 25 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.1.tgz", 26 | "integrity": "sha512-rek8twk9C58gHYqIrUlJsx8NQMhlxqHzln9Z9ODqiNgv3/s+ZwIrfr+djqzsnVM12xe9hL98iJ20lj2RvCBv6A==", 27 | "optional": true 28 | }, 29 | "@types/node": { 30 | "version": "8.0.12", 31 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.12.tgz", 32 | "integrity": "sha512-bOHB8JVXCDgAkPWF0LWy4Qeuh75ZlpolWSB2mmwoEPtvCgYurWMvTzHNkfs/sCBxmDYavFJX7gniRFa6Jb/gEw==" 33 | }, 34 | "@types/serve-static": { 35 | "version": "1.7.31", 36 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz", 37 | "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho=", 38 | "optional": true 39 | }, 40 | "accepts": { 41 | "version": "1.3.3", 42 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 43 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" 44 | }, 45 | "ajv": { 46 | "version": "4.11.8", 47 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", 48 | "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=" 49 | }, 50 | "array-flatten": { 51 | "version": "1.1.1", 52 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 53 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 54 | }, 55 | "asn1": { 56 | "version": "0.2.3", 57 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 58 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 59 | }, 60 | "assert-plus": { 61 | "version": "0.2.0", 62 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", 63 | "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" 64 | }, 65 | "asynckit": { 66 | "version": "0.4.0", 67 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 68 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 69 | }, 70 | "aws-sign2": { 71 | "version": "0.6.0", 72 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", 73 | "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" 74 | }, 75 | "aws4": { 76 | "version": "1.6.0", 77 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 78 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 79 | }, 80 | "bcrypt-pbkdf": { 81 | "version": "1.0.1", 82 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 83 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 84 | "optional": true 85 | }, 86 | "body-parser": { 87 | "version": "1.17.2", 88 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 89 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=" 90 | }, 91 | "boom": { 92 | "version": "2.10.1", 93 | "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", 94 | "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" 95 | }, 96 | "bytes": { 97 | "version": "2.4.0", 98 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 99 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 100 | }, 101 | "caseless": { 102 | "version": "0.12.0", 103 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 104 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 105 | }, 106 | "co": { 107 | "version": "4.6.0", 108 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 109 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 110 | }, 111 | "combined-stream": { 112 | "version": "1.0.5", 113 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 114 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" 115 | }, 116 | "content-disposition": { 117 | "version": "0.5.2", 118 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 119 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 120 | }, 121 | "content-type": { 122 | "version": "1.0.2", 123 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 124 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 125 | }, 126 | "cookie": { 127 | "version": "0.3.1", 128 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 129 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 130 | }, 131 | "cookie-signature": { 132 | "version": "1.0.6", 133 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 134 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 135 | }, 136 | "cryptiles": { 137 | "version": "2.0.5", 138 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", 139 | "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" 140 | }, 141 | "dashdash": { 142 | "version": "1.14.1", 143 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 144 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 145 | "dependencies": { 146 | "assert-plus": { 147 | "version": "1.0.0", 148 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 149 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 150 | } 151 | } 152 | }, 153 | "debug": { 154 | "version": "2.6.7", 155 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 156 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 157 | }, 158 | "delayed-stream": { 159 | "version": "1.0.0", 160 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 161 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 162 | }, 163 | "depd": { 164 | "version": "1.1.0", 165 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 166 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 167 | }, 168 | "deprecated-decorator": { 169 | "version": "0.1.6", 170 | "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz", 171 | "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=" 172 | }, 173 | "destroy": { 174 | "version": "1.0.4", 175 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 176 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 177 | }, 178 | "ecc-jsbn": { 179 | "version": "0.1.1", 180 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 181 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 182 | "optional": true 183 | }, 184 | "ee-first": { 185 | "version": "1.1.1", 186 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 187 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 188 | }, 189 | "encodeurl": { 190 | "version": "1.0.1", 191 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 192 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 193 | }, 194 | "escape-html": { 195 | "version": "1.0.3", 196 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 197 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 198 | }, 199 | "etag": { 200 | "version": "1.8.0", 201 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 202 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 203 | }, 204 | "express": { 205 | "version": "4.15.3", 206 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 207 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" 208 | }, 209 | "extend": { 210 | "version": "3.0.1", 211 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 212 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 213 | }, 214 | "extsprintf": { 215 | "version": "1.0.2", 216 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", 217 | "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" 218 | }, 219 | "finalhandler": { 220 | "version": "1.0.3", 221 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 222 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" 223 | }, 224 | "forever-agent": { 225 | "version": "0.6.1", 226 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 227 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 228 | }, 229 | "form-data": { 230 | "version": "2.1.4", 231 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", 232 | "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" 233 | }, 234 | "forwarded": { 235 | "version": "0.1.0", 236 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 237 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 238 | }, 239 | "fresh": { 240 | "version": "0.5.0", 241 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 242 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 243 | }, 244 | "getpass": { 245 | "version": "0.1.7", 246 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 247 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 248 | "dependencies": { 249 | "assert-plus": { 250 | "version": "1.0.0", 251 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 252 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 253 | } 254 | } 255 | }, 256 | "graphql": { 257 | "version": "0.10.3", 258 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.10.3.tgz", 259 | "integrity": "sha1-wxOv1VGOZzNRvuGPtj4qDkh0B6s=" 260 | }, 261 | "graphql-server-core": { 262 | "version": "1.0.0", 263 | "resolved": "https://registry.npmjs.org/graphql-server-core/-/graphql-server-core-1.0.0.tgz", 264 | "integrity": "sha1-JdkrYnmoEmPwcUE2Y2pUkQwfr+A=" 265 | }, 266 | "graphql-server-express": { 267 | "version": "1.0.0", 268 | "resolved": "https://registry.npmjs.org/graphql-server-express/-/graphql-server-express-1.0.0.tgz", 269 | "integrity": "sha1-A4tzVSJV9hgMUac77o1/nWjF4gE=" 270 | }, 271 | "graphql-server-module-graphiql": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/graphql-server-module-graphiql/-/graphql-server-module-graphiql-1.0.0.tgz", 274 | "integrity": "sha1-GzAkgUI/g7PA9DyzC/3DC/49CoI=" 275 | }, 276 | "graphql-tools": { 277 | "version": "1.1.0", 278 | "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-1.1.0.tgz", 279 | "integrity": "sha1-jYbqaZew3qMIm2LcZV5HFGpmPrs=" 280 | }, 281 | "har-schema": { 282 | "version": "1.0.5", 283 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", 284 | "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" 285 | }, 286 | "har-validator": { 287 | "version": "4.2.1", 288 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", 289 | "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=" 290 | }, 291 | "hawk": { 292 | "version": "3.1.3", 293 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", 294 | "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" 295 | }, 296 | "hoek": { 297 | "version": "2.16.3", 298 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", 299 | "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" 300 | }, 301 | "http-errors": { 302 | "version": "1.6.1", 303 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 304 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" 305 | }, 306 | "http-signature": { 307 | "version": "1.1.1", 308 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", 309 | "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" 310 | }, 311 | "iconv-lite": { 312 | "version": "0.4.15", 313 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 314 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 315 | }, 316 | "inherits": { 317 | "version": "2.0.3", 318 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 319 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 320 | }, 321 | "ipaddr.js": { 322 | "version": "1.3.0", 323 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 324 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 325 | }, 326 | "is-typedarray": { 327 | "version": "1.0.0", 328 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 329 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 330 | }, 331 | "isstream": { 332 | "version": "0.1.2", 333 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 334 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 335 | }, 336 | "iterall": { 337 | "version": "1.1.1", 338 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.1.tgz", 339 | "integrity": "sha1-9/CvEemgTsZCYmD1AZ2fzKTVAhQ=" 340 | }, 341 | "jsbn": { 342 | "version": "0.1.1", 343 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 344 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 345 | "optional": true 346 | }, 347 | "json-schema": { 348 | "version": "0.2.3", 349 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 350 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 351 | }, 352 | "json-stable-stringify": { 353 | "version": "1.0.1", 354 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 355 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=" 356 | }, 357 | "json-stringify-safe": { 358 | "version": "5.0.1", 359 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 360 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 361 | }, 362 | "jsonify": { 363 | "version": "0.0.0", 364 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 365 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" 366 | }, 367 | "jsprim": { 368 | "version": "1.4.0", 369 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", 370 | "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", 371 | "dependencies": { 372 | "assert-plus": { 373 | "version": "1.0.0", 374 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 375 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 376 | } 377 | } 378 | }, 379 | "lodash": { 380 | "version": "4.17.4", 381 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 382 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 383 | }, 384 | "media-typer": { 385 | "version": "0.3.0", 386 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 387 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 388 | }, 389 | "merge-descriptors": { 390 | "version": "1.0.1", 391 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 392 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 393 | }, 394 | "methods": { 395 | "version": "1.1.2", 396 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 397 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 398 | }, 399 | "mime": { 400 | "version": "1.3.4", 401 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 402 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 403 | }, 404 | "mime-db": { 405 | "version": "1.27.0", 406 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 407 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 408 | }, 409 | "mime-types": { 410 | "version": "2.1.15", 411 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 412 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" 413 | }, 414 | "ms": { 415 | "version": "2.0.0", 416 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 417 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 418 | }, 419 | "negotiator": { 420 | "version": "0.6.1", 421 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 422 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 423 | }, 424 | "oauth-sign": { 425 | "version": "0.8.2", 426 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 427 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 428 | }, 429 | "on-finished": { 430 | "version": "2.3.0", 431 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 432 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 433 | }, 434 | "parseurl": { 435 | "version": "1.3.1", 436 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 437 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 438 | }, 439 | "path-to-regexp": { 440 | "version": "0.1.7", 441 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 442 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 443 | }, 444 | "performance-now": { 445 | "version": "0.2.0", 446 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", 447 | "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" 448 | }, 449 | "proxy-addr": { 450 | "version": "1.1.4", 451 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 452 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" 453 | }, 454 | "punycode": { 455 | "version": "1.4.1", 456 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 457 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 458 | }, 459 | "qs": { 460 | "version": "6.4.0", 461 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 462 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 463 | }, 464 | "range-parser": { 465 | "version": "1.2.0", 466 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 467 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 468 | }, 469 | "raw-body": { 470 | "version": "2.2.0", 471 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 472 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" 473 | }, 474 | "request": { 475 | "version": "2.81.0", 476 | "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", 477 | "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=" 478 | }, 479 | "request-promise-core": { 480 | "version": "1.1.1", 481 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", 482 | "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=" 483 | }, 484 | "request-promise-native": { 485 | "version": "1.0.4", 486 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.4.tgz", 487 | "integrity": "sha1-hpiOyO7kCORVefzoO/0Fs635oVU=" 488 | }, 489 | "safe-buffer": { 490 | "version": "5.1.1", 491 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 492 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 493 | }, 494 | "send": { 495 | "version": "0.15.3", 496 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 497 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" 498 | }, 499 | "serve-static": { 500 | "version": "1.12.3", 501 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 502 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" 503 | }, 504 | "setprototypeof": { 505 | "version": "1.0.3", 506 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 507 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 508 | }, 509 | "sntp": { 510 | "version": "1.0.9", 511 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", 512 | "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" 513 | }, 514 | "sshpk": { 515 | "version": "1.13.1", 516 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", 517 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", 518 | "dependencies": { 519 | "assert-plus": { 520 | "version": "1.0.0", 521 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 522 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 523 | } 524 | } 525 | }, 526 | "statuses": { 527 | "version": "1.3.1", 528 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 529 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 530 | }, 531 | "stealthy-require": { 532 | "version": "1.1.1", 533 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 534 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 535 | }, 536 | "stringstream": { 537 | "version": "0.0.5", 538 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", 539 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" 540 | }, 541 | "tough-cookie": { 542 | "version": "2.3.2", 543 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", 544 | "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=" 545 | }, 546 | "tunnel-agent": { 547 | "version": "0.6.0", 548 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 549 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" 550 | }, 551 | "tweetnacl": { 552 | "version": "0.14.5", 553 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 554 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 555 | "optional": true 556 | }, 557 | "type-is": { 558 | "version": "1.6.15", 559 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 560 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 561 | }, 562 | "unpipe": { 563 | "version": "1.0.0", 564 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 565 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 566 | }, 567 | "utils-merge": { 568 | "version": "1.0.0", 569 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 570 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 571 | }, 572 | "uuid": { 573 | "version": "3.1.0", 574 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 575 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 576 | }, 577 | "vary": { 578 | "version": "1.1.1", 579 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 580 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 581 | }, 582 | "verror": { 583 | "version": "1.3.6", 584 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", 585 | "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" 586 | } 587 | } 588 | } 589 | -------------------------------------------------------------------------------- /ex/04/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.17.2", 14 | "express": "^4.15.3", 15 | "graphql": "^0.10.3", 16 | "graphql-server-express": "^1.0.0", 17 | "graphql-tools": "^1.1.0", 18 | "request": "^2.81.0", 19 | "request-promise-native": "^1.0.4" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ex/04/src/schema/index.js: -------------------------------------------------------------------------------- 1 | const { makeExecutableSchema } = require('graphql-tools') 2 | const { getItem, getUser, getTopStories } = require('../hnclient') 3 | 4 | const typeDefs = [` 5 | type User { 6 | about: String 7 | created: Int 8 | delay: Int, 9 | id: String! 10 | karma: Int! 11 | submitted: [Item] 12 | } 13 | 14 | enum ITEM_TYPE { 15 | JOB 16 | STORY 17 | COMMENT 18 | POLL 19 | POLLOPT 20 | } 21 | 22 | type Item { 23 | id: Int! 24 | by: User! 25 | kids: [Item] 26 | score: Int! 27 | time: Int 28 | title: String 29 | type: ITEM_TYPE 30 | url: String 31 | text: String 32 | dead: Boolean 33 | } 34 | 35 | type Query { 36 | item(id: Int!): Item 37 | stories(page: Int, count: Int): [Item] 38 | user(id: String!): User 39 | } 40 | 41 | schema { 42 | query: Query 43 | } 44 | `] 45 | 46 | const resolvers = { 47 | Query: { 48 | item (_, { id }) { 49 | return getItem(id) 50 | }, 51 | 52 | async stories (_, { page, count }) { 53 | const stories = await getTopStories() 54 | if (page && count) { 55 | return stories 56 | .slice(page * count, (page * count) + count) 57 | .map(getItem) 58 | } 59 | 60 | return stories.map(getItem) 61 | }, 62 | 63 | user (_, { id }) { 64 | return getUser(id) 65 | } 66 | }, 67 | Item: { 68 | by ({ by }) { 69 | return getUser(by) 70 | }, 71 | kids ({ kids }) { 72 | return kids.map(getItem) 73 | }, 74 | type ({ type }) { 75 | return type.toUpperCase() 76 | } 77 | }, 78 | User: { 79 | submitted ({ submitted }) { 80 | return submitted.map(getItem) 81 | } 82 | } 83 | } 84 | 85 | module.exports = makeExecutableSchema({ typeDefs, resolvers }) 86 | -------------------------------------------------------------------------------- /ex/05/README.md: -------------------------------------------------------------------------------- 1 | # _Caching_ y _batching_ con `dataloader` 2 | 3 | Cuatro de los siete resolvers que escribimos llaman a `getItem`. Algunos lo hacen muchas veces. Entonces, resulta posible que en un mismo query se hagan llamadas iguales al backend, con lo cual, al ser una operación cara, estamos malgastando recursos. 4 | 5 | Con otros resolvers puede pasar lo mismo. En principio no sabemos qué nos pueden consultar y algunas consultas pueden hacer que los resolvers se llamen muchas veces, y en ocasiones, con los mismos datos, con lo cual también estamos malgastando recursos ahí. 6 | 7 | Una forma de solucionar este problema es agregando una capa de caching. Y acá es donde `dataloader` nos da una mano. 8 | 9 | Para instalarlo, hay que correr esto en una Terminal: 10 | 11 | ``` 12 | npm install --save dataloader 13 | ``` 14 | 15 | `dataloader` tiene una API bastante simple: un método `load` y otro `loadMany`, donde se le pasan _keys_ que devuelven `Promise`s que resuelven a los valores que necesitamos. Cada loader que creamos debe wrappear una llamada al backend, que será cacheada durante el request. 16 | 17 | Por ejemplo, para wrappear la llamada a `getItem`, la key debería ser el ID del item y lo que debería devolver es una `Promise` que resuelva en el `Item` que corresponda. 18 | 19 | La forma de crear ese loader es la siguiente: 20 | 21 | ```js 22 | const itemLoader = new DataLoader(ids => Promise.all(ids.map(id => getItem()))) 23 | ``` 24 | 25 | Es decir, se llama al constructor de `DataLoader` pasándole un solo argumento, que es la función que será llamada por `load` y `loadMany` cuando le pasemos keys. Esa función recibirá un array de keys, aún cuando a la instancia se le llame a `load` y se le pida un solo ID. 26 | 27 | Lo que devolverá es una sola `Promise`, que resolverá cuando todos los IDs hayan sido obtenidos. 28 | 29 | Entonces ya podemos usar nuestro loader en el resolver: 30 | 31 | ```js 32 | { 33 | // ... 34 | Query: { 35 | item (_, { id }) { 36 | return itemLoader.load(id) 37 | }, 38 | // ... 39 | } 40 | } 41 | ``` 42 | 43 | Ahora cada vez que se consulte `item`, se va a cachear el valor. Si ese `Item` vuelve a ser pedido en algún otro resolver, se usará la versión cacheada. 44 | 45 | ## Ejercicio 46 | 47 | ```js 48 | /** 49 | * Devuelve todos los topstories, sin importar qué key se le pase 50 | */ 51 | const allTopStoriesLoader = new DataLoader(...) 52 | 53 | /** 54 | * Devuelve los topstories paginados. Las keys son objetos con la forma { page, count } 55 | */ 56 | const topStoriesLoader = new DataLoader(...) 57 | 58 | /** 59 | * Devuelve un usuario. Las keys son los IDs 60 | */ 61 | const userLoader = new DataLoader(...) 62 | ``` 63 | 64 | Escribir las funciones para los loaders planteados. Hacer las llamadas a los loaders en los resolvers que correspondan. 65 | 66 | ___ 67 | 68 | - [Siguiente](../06) 69 | - [Código](src) 70 | -------------------------------------------------------------------------------- /ex/05/src/hnclient/index.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise-native') 2 | 3 | const baseURL = 'https://hacker-news.firebaseio.com/v0' 4 | 5 | module.exports.getItem = function (id) { 6 | return request.get(`${baseURL}/item/${id}.json`, { json: true }) 7 | } 8 | 9 | module.exports.getTopStories = function () { 10 | return request.get(`${baseURL}/topstories.json`, { json: true }) 11 | } 12 | 13 | module.exports.getUser = function (id) { 14 | return request.get(`${baseURL}/user/${id}.json`, { json: true }) 15 | } 16 | -------------------------------------------------------------------------------- /ex/05/src/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const { graphqlExpress, graphiqlExpress } = require('graphql-server-express') 3 | const bodyParser = require('body-parser') 4 | const cors = require('cors') 5 | const schema = require('./schema') 6 | 7 | const app = express() 8 | 9 | app.use('/graphql', cors(), bodyParser.json(), graphqlExpress({ schema })) 10 | 11 | app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) 12 | 13 | app.listen(3001, function () { 14 | console.log('Our Node server is up and running on port 3001!') 15 | }) 16 | -------------------------------------------------------------------------------- /ex/05/src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@types/express": { 7 | "version": "4.0.36", 8 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.36.tgz", 9 | "integrity": "sha512-bT9q2eqH/E72AGBQKT50dh6AXzheTqigGZ1GwDiwmx7vfHff0bZOrvUWjvGpNWPNkRmX1vDF6wonG6rlpBHb1A==", 10 | "optional": true 11 | }, 12 | "@types/express-serve-static-core": { 13 | "version": "4.0.49", 14 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.0.49.tgz", 15 | "integrity": "sha512-b7mVHoURu1xaP/V6xw1sYwyv9V0EZ7euyi+sdnbnTZxEkAh4/hzPsI6Eflq+ZzHQ/Tgl7l16Jz+0oz8F46MLnA==" 16 | }, 17 | "@types/graphql": { 18 | "version": "0.9.4", 19 | "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-0.9.4.tgz", 20 | "integrity": "sha512-ob2dps4itT/Le5DbxjssBXtBnloDIRUbkgtAvaB42mJ8pVIWMRuURD9WjnhaEGZ4Ql/EryXMQWeU8Y0EU73QLw==", 21 | "optional": true 22 | }, 23 | "@types/mime": { 24 | "version": "1.3.1", 25 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.1.tgz", 26 | "integrity": "sha512-rek8twk9C58gHYqIrUlJsx8NQMhlxqHzln9Z9ODqiNgv3/s+ZwIrfr+djqzsnVM12xe9hL98iJ20lj2RvCBv6A==", 27 | "optional": true 28 | }, 29 | "@types/node": { 30 | "version": "8.0.12", 31 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.12.tgz", 32 | "integrity": "sha512-bOHB8JVXCDgAkPWF0LWy4Qeuh75ZlpolWSB2mmwoEPtvCgYurWMvTzHNkfs/sCBxmDYavFJX7gniRFa6Jb/gEw==" 33 | }, 34 | "@types/serve-static": { 35 | "version": "1.7.31", 36 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.7.31.tgz", 37 | "integrity": "sha1-FUVt6NmNa0z/Mb5savdJKuY/Uho=", 38 | "optional": true 39 | }, 40 | "accepts": { 41 | "version": "1.3.3", 42 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", 43 | "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=" 44 | }, 45 | "ajv": { 46 | "version": "4.11.8", 47 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", 48 | "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=" 49 | }, 50 | "array-flatten": { 51 | "version": "1.1.1", 52 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 53 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 54 | }, 55 | "asn1": { 56 | "version": "0.2.3", 57 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 58 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 59 | }, 60 | "assert-plus": { 61 | "version": "0.2.0", 62 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", 63 | "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" 64 | }, 65 | "asynckit": { 66 | "version": "0.4.0", 67 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 68 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 69 | }, 70 | "aws-sign2": { 71 | "version": "0.6.0", 72 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", 73 | "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" 74 | }, 75 | "aws4": { 76 | "version": "1.6.0", 77 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", 78 | "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" 79 | }, 80 | "bcrypt-pbkdf": { 81 | "version": "1.0.1", 82 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 83 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 84 | "optional": true 85 | }, 86 | "body-parser": { 87 | "version": "1.17.2", 88 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.2.tgz", 89 | "integrity": "sha1-+IkqvI+eYn1Crtr7yma/WrmRBO4=" 90 | }, 91 | "boom": { 92 | "version": "2.10.1", 93 | "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", 94 | "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=" 95 | }, 96 | "bytes": { 97 | "version": "2.4.0", 98 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", 99 | "integrity": "sha1-fZcZb51br39pNeJZhVSe3SpsIzk=" 100 | }, 101 | "caseless": { 102 | "version": "0.12.0", 103 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 104 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 105 | }, 106 | "co": { 107 | "version": "4.6.0", 108 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 109 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 110 | }, 111 | "combined-stream": { 112 | "version": "1.0.5", 113 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", 114 | "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=" 115 | }, 116 | "content-disposition": { 117 | "version": "0.5.2", 118 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 119 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 120 | }, 121 | "content-type": { 122 | "version": "1.0.2", 123 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz", 124 | "integrity": "sha1-t9ETrueo3Se9IRM8TcJSnfFyHu0=" 125 | }, 126 | "cookie": { 127 | "version": "0.3.1", 128 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 129 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 130 | }, 131 | "cookie-signature": { 132 | "version": "1.0.6", 133 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 134 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 135 | }, 136 | "cors": { 137 | "version": "2.8.4", 138 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", 139 | "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=" 140 | }, 141 | "cryptiles": { 142 | "version": "2.0.5", 143 | "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", 144 | "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=" 145 | }, 146 | "dashdash": { 147 | "version": "1.14.1", 148 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 149 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 150 | "dependencies": { 151 | "assert-plus": { 152 | "version": "1.0.0", 153 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 154 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 155 | } 156 | } 157 | }, 158 | "dataloader": { 159 | "version": "1.3.0", 160 | "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.3.0.tgz", 161 | "integrity": "sha1-b+xb5LMKcS5K/TC4a0M0VmuXZzs=" 162 | }, 163 | "debug": { 164 | "version": "2.6.7", 165 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.7.tgz", 166 | "integrity": "sha1-krrR9tBbu2u6Isyoi80OyJTChh4=" 167 | }, 168 | "delayed-stream": { 169 | "version": "1.0.0", 170 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 171 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 172 | }, 173 | "depd": { 174 | "version": "1.1.0", 175 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", 176 | "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=" 177 | }, 178 | "deprecated-decorator": { 179 | "version": "0.1.6", 180 | "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz", 181 | "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=" 182 | }, 183 | "destroy": { 184 | "version": "1.0.4", 185 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 186 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 187 | }, 188 | "ecc-jsbn": { 189 | "version": "0.1.1", 190 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 191 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 192 | "optional": true 193 | }, 194 | "ee-first": { 195 | "version": "1.1.1", 196 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 197 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 198 | }, 199 | "encodeurl": { 200 | "version": "1.0.1", 201 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", 202 | "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" 203 | }, 204 | "escape-html": { 205 | "version": "1.0.3", 206 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 207 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 208 | }, 209 | "etag": { 210 | "version": "1.8.0", 211 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz", 212 | "integrity": "sha1-b2Ma7zNtbEY2K1F2QETOIWvjwFE=" 213 | }, 214 | "express": { 215 | "version": "4.15.3", 216 | "resolved": "https://registry.npmjs.org/express/-/express-4.15.3.tgz", 217 | "integrity": "sha1-urZdDwOqgMNYQIly/HAPkWlEtmI=" 218 | }, 219 | "extend": { 220 | "version": "3.0.1", 221 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 222 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 223 | }, 224 | "extsprintf": { 225 | "version": "1.0.2", 226 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", 227 | "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" 228 | }, 229 | "finalhandler": { 230 | "version": "1.0.3", 231 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.3.tgz", 232 | "integrity": "sha1-70fneVDpmXgOhgIqVg4yF+DQzIk=" 233 | }, 234 | "forever-agent": { 235 | "version": "0.6.1", 236 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 237 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 238 | }, 239 | "form-data": { 240 | "version": "2.1.4", 241 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", 242 | "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=" 243 | }, 244 | "forwarded": { 245 | "version": "0.1.0", 246 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz", 247 | "integrity": "sha1-Ge+YdMSuHCl7zweP3mOgm2aoQ2M=" 248 | }, 249 | "fresh": { 250 | "version": "0.5.0", 251 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", 252 | "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" 253 | }, 254 | "getpass": { 255 | "version": "0.1.7", 256 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 257 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 258 | "dependencies": { 259 | "assert-plus": { 260 | "version": "1.0.0", 261 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 262 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 263 | } 264 | } 265 | }, 266 | "graphql": { 267 | "version": "0.10.3", 268 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.10.3.tgz", 269 | "integrity": "sha1-wxOv1VGOZzNRvuGPtj4qDkh0B6s=" 270 | }, 271 | "graphql-server-core": { 272 | "version": "1.0.0", 273 | "resolved": "https://registry.npmjs.org/graphql-server-core/-/graphql-server-core-1.0.0.tgz", 274 | "integrity": "sha1-JdkrYnmoEmPwcUE2Y2pUkQwfr+A=" 275 | }, 276 | "graphql-server-express": { 277 | "version": "1.0.0", 278 | "resolved": "https://registry.npmjs.org/graphql-server-express/-/graphql-server-express-1.0.0.tgz", 279 | "integrity": "sha1-A4tzVSJV9hgMUac77o1/nWjF4gE=" 280 | }, 281 | "graphql-server-module-graphiql": { 282 | "version": "1.0.0", 283 | "resolved": "https://registry.npmjs.org/graphql-server-module-graphiql/-/graphql-server-module-graphiql-1.0.0.tgz", 284 | "integrity": "sha1-GzAkgUI/g7PA9DyzC/3DC/49CoI=" 285 | }, 286 | "graphql-tools": { 287 | "version": "1.1.0", 288 | "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-1.1.0.tgz", 289 | "integrity": "sha1-jYbqaZew3qMIm2LcZV5HFGpmPrs=" 290 | }, 291 | "har-schema": { 292 | "version": "1.0.5", 293 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", 294 | "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=" 295 | }, 296 | "har-validator": { 297 | "version": "4.2.1", 298 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", 299 | "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=" 300 | }, 301 | "hawk": { 302 | "version": "3.1.3", 303 | "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", 304 | "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=" 305 | }, 306 | "hoek": { 307 | "version": "2.16.3", 308 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", 309 | "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" 310 | }, 311 | "http-errors": { 312 | "version": "1.6.1", 313 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", 314 | "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=" 315 | }, 316 | "http-signature": { 317 | "version": "1.1.1", 318 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", 319 | "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=" 320 | }, 321 | "iconv-lite": { 322 | "version": "0.4.15", 323 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", 324 | "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=" 325 | }, 326 | "inherits": { 327 | "version": "2.0.3", 328 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 329 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 330 | }, 331 | "ipaddr.js": { 332 | "version": "1.3.0", 333 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz", 334 | "integrity": "sha1-HgOlL9rYOou7KyXL9JmLTP/NPew=" 335 | }, 336 | "is-typedarray": { 337 | "version": "1.0.0", 338 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 339 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 340 | }, 341 | "isstream": { 342 | "version": "0.1.2", 343 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 344 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 345 | }, 346 | "iterall": { 347 | "version": "1.1.1", 348 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.1.tgz", 349 | "integrity": "sha1-9/CvEemgTsZCYmD1AZ2fzKTVAhQ=" 350 | }, 351 | "jsbn": { 352 | "version": "0.1.1", 353 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 354 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 355 | "optional": true 356 | }, 357 | "json-schema": { 358 | "version": "0.2.3", 359 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 360 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 361 | }, 362 | "json-stable-stringify": { 363 | "version": "1.0.1", 364 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 365 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=" 366 | }, 367 | "json-stringify-safe": { 368 | "version": "5.0.1", 369 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 370 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 371 | }, 372 | "jsonify": { 373 | "version": "0.0.0", 374 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 375 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" 376 | }, 377 | "jsprim": { 378 | "version": "1.4.0", 379 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", 380 | "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", 381 | "dependencies": { 382 | "assert-plus": { 383 | "version": "1.0.0", 384 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 385 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 386 | } 387 | } 388 | }, 389 | "lodash": { 390 | "version": "4.17.4", 391 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 392 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 393 | }, 394 | "media-typer": { 395 | "version": "0.3.0", 396 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 397 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 398 | }, 399 | "merge-descriptors": { 400 | "version": "1.0.1", 401 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 402 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 403 | }, 404 | "methods": { 405 | "version": "1.1.2", 406 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 407 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 408 | }, 409 | "mime": { 410 | "version": "1.3.4", 411 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", 412 | "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" 413 | }, 414 | "mime-db": { 415 | "version": "1.27.0", 416 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", 417 | "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=" 418 | }, 419 | "mime-types": { 420 | "version": "2.1.15", 421 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", 422 | "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=" 423 | }, 424 | "ms": { 425 | "version": "2.0.0", 426 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 427 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 428 | }, 429 | "negotiator": { 430 | "version": "0.6.1", 431 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 432 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 433 | }, 434 | "oauth-sign": { 435 | "version": "0.8.2", 436 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 437 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 438 | }, 439 | "object-assign": { 440 | "version": "4.1.1", 441 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 442 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 443 | }, 444 | "on-finished": { 445 | "version": "2.3.0", 446 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 447 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=" 448 | }, 449 | "parseurl": { 450 | "version": "1.3.1", 451 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz", 452 | "integrity": "sha1-yKuMkiO6NIiKpkopeyiFO+wY2lY=" 453 | }, 454 | "path-to-regexp": { 455 | "version": "0.1.7", 456 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 457 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 458 | }, 459 | "performance-now": { 460 | "version": "0.2.0", 461 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", 462 | "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=" 463 | }, 464 | "proxy-addr": { 465 | "version": "1.1.4", 466 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz", 467 | "integrity": "sha1-J+VF9pYKRKYn2bREZ+NcG2tM4vM=" 468 | }, 469 | "punycode": { 470 | "version": "1.4.1", 471 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 472 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 473 | }, 474 | "qs": { 475 | "version": "6.4.0", 476 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", 477 | "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=" 478 | }, 479 | "range-parser": { 480 | "version": "1.2.0", 481 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 482 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 483 | }, 484 | "raw-body": { 485 | "version": "2.2.0", 486 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz", 487 | "integrity": "sha1-mUl2z2pQlqQRYoQEkvC9xdbn+5Y=" 488 | }, 489 | "request": { 490 | "version": "2.81.0", 491 | "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", 492 | "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=" 493 | }, 494 | "request-promise-core": { 495 | "version": "1.1.1", 496 | "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", 497 | "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=" 498 | }, 499 | "request-promise-native": { 500 | "version": "1.0.4", 501 | "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.4.tgz", 502 | "integrity": "sha1-hpiOyO7kCORVefzoO/0Fs635oVU=" 503 | }, 504 | "safe-buffer": { 505 | "version": "5.1.1", 506 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 507 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 508 | }, 509 | "send": { 510 | "version": "0.15.3", 511 | "resolved": "https://registry.npmjs.org/send/-/send-0.15.3.tgz", 512 | "integrity": "sha1-UBP5+ZAj31DRvZiSwZ4979HVMwk=" 513 | }, 514 | "serve-static": { 515 | "version": "1.12.3", 516 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.3.tgz", 517 | "integrity": "sha1-n0uhni8wMMVH+K+ZEHg47DjVseI=" 518 | }, 519 | "setprototypeof": { 520 | "version": "1.0.3", 521 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 522 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 523 | }, 524 | "sntp": { 525 | "version": "1.0.9", 526 | "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", 527 | "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=" 528 | }, 529 | "sshpk": { 530 | "version": "1.13.1", 531 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", 532 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", 533 | "dependencies": { 534 | "assert-plus": { 535 | "version": "1.0.0", 536 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 537 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 538 | } 539 | } 540 | }, 541 | "statuses": { 542 | "version": "1.3.1", 543 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", 544 | "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" 545 | }, 546 | "stealthy-require": { 547 | "version": "1.1.1", 548 | "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", 549 | "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" 550 | }, 551 | "stringstream": { 552 | "version": "0.0.5", 553 | "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", 554 | "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" 555 | }, 556 | "tough-cookie": { 557 | "version": "2.3.2", 558 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", 559 | "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=" 560 | }, 561 | "tunnel-agent": { 562 | "version": "0.6.0", 563 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 564 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=" 565 | }, 566 | "tweetnacl": { 567 | "version": "0.14.5", 568 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 569 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 570 | "optional": true 571 | }, 572 | "type-is": { 573 | "version": "1.6.15", 574 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", 575 | "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=" 576 | }, 577 | "unpipe": { 578 | "version": "1.0.0", 579 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 580 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 581 | }, 582 | "utils-merge": { 583 | "version": "1.0.0", 584 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", 585 | "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" 586 | }, 587 | "uuid": { 588 | "version": "3.1.0", 589 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 590 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 591 | }, 592 | "vary": { 593 | "version": "1.1.1", 594 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz", 595 | "integrity": "sha1-Z1Neu2lMHVIldFeYRmUyP1h+jTc=" 596 | }, 597 | "verror": { 598 | "version": "1.3.6", 599 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", 600 | "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=" 601 | } 602 | } 603 | } 604 | -------------------------------------------------------------------------------- /ex/05/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "src", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.17.2", 14 | "cors": "^2.8.4", 15 | "dataloader": "^1.3.0", 16 | "express": "^4.15.3", 17 | "graphql": "^0.10.3", 18 | "graphql-server-express": "^1.0.0", 19 | "graphql-tools": "^1.1.0", 20 | "request": "^2.81.0", 21 | "request-promise-native": "^1.0.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ex/05/src/schema/index.js: -------------------------------------------------------------------------------- 1 | const { makeExecutableSchema } = require('graphql-tools') 2 | const typeDefs = require('./types') 3 | const resolvers = require('./resolvers') 4 | 5 | module.exports = makeExecutableSchema({ typeDefs, resolvers }) 6 | -------------------------------------------------------------------------------- /ex/05/src/schema/resolvers/Item.js: -------------------------------------------------------------------------------- 1 | const { userLoader, itemLoader } = require('./loaders') 2 | 3 | module.exports = { 4 | by ({ by }) { 5 | return userLoader.load(by) 6 | }, 7 | kids ({ kids }) { 8 | return itemLoader.loadMany(kids) 9 | }, 10 | type ({ type }) { 11 | return type.toUpperCase() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ex/05/src/schema/resolvers/Query.js: -------------------------------------------------------------------------------- 1 | const { itemLoader, topStoriesLoader, userLoader } = require('./loaders') 2 | 3 | module.exports = { 4 | item (_, { id }) { 5 | return itemLoader.load(id) 6 | }, 7 | 8 | async stories (_, { page, count }) { 9 | return topStoriesLoader.load({ page, count }) 10 | }, 11 | 12 | user (_, { id }) { 13 | return userLoader.load(id) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ex/05/src/schema/resolvers/User.js: -------------------------------------------------------------------------------- 1 | const { itemLoader } = require('./loaders') 2 | 3 | module.exports = { 4 | submitted ({ submitted }) { 5 | return itemLoader.loadMany(submitted.slice(0, 10)) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ex/05/src/schema/resolvers/index.js: -------------------------------------------------------------------------------- 1 | const Item = require('./Item') 2 | const User = require('./User') 3 | const Query = require('./Query') 4 | 5 | module.exports = { 6 | Item, 7 | User, 8 | Query 9 | } 10 | -------------------------------------------------------------------------------- /ex/05/src/schema/resolvers/loaders/index.js: -------------------------------------------------------------------------------- 1 | const DataLoader = require('dataloader') 2 | const { getItem, getUser, getTopStories } = require('../../../hnclient') 3 | 4 | const itemLoader = new DataLoader(ids => Promise.all(ids.map(getItem))) 5 | 6 | const allTopStoriesLoader = new DataLoader(keys => Promise.all(keys.map(getTopStories))) 7 | 8 | const topStoriesLoader = new DataLoader(async params => Promise.all(params.map(async ({ page, count } = {}) => { 9 | const stories = await allTopStoriesLoader.load('all') 10 | return itemLoader.loadMany(page && count 11 | ? stories.slice(page * count, (page * count) + count) 12 | : stories) 13 | }))) 14 | 15 | const userLoader = new DataLoader(ids => Promise.all(ids.map(getUser))) 16 | 17 | module.exports = { 18 | itemLoader, 19 | allTopStoriesLoader, 20 | topStoriesLoader, 21 | userLoader 22 | } 23 | -------------------------------------------------------------------------------- /ex/05/src/schema/types/ITEM_TYPE.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | enum ITEM_TYPE { 3 | JOB 4 | STORY 5 | COMMENT 6 | POLL 7 | POLLOPT 8 | } 9 | ` 10 | -------------------------------------------------------------------------------- /ex/05/src/schema/types/Item.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | type Item { 3 | id: Int! 4 | by: User! 5 | kids: [Item] 6 | score: Int! 7 | time: Int 8 | title: String 9 | type: ITEM_TYPE 10 | url: String 11 | text: String 12 | dead: Boolean 13 | } 14 | ` 15 | -------------------------------------------------------------------------------- /ex/05/src/schema/types/Query.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | type Query { 3 | item(id: Int!): Item 4 | stories(page: Int, count: Int): [Item] 5 | user(id: String!): User 6 | } 7 | ` 8 | -------------------------------------------------------------------------------- /ex/05/src/schema/types/User.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | type User { 3 | about: String 4 | created: Int 5 | delay: Int, 6 | id: String! 7 | karma: Int! 8 | submitted: [Item] 9 | } 10 | ` 11 | -------------------------------------------------------------------------------- /ex/05/src/schema/types/index.js: -------------------------------------------------------------------------------- 1 | const User = require('./User') 2 | const ITEM_TYPE = require('./ITEM_TYPE') 3 | const Item = require('./Item') 4 | const Query = require('./Query') 5 | const schema = require('./schema') 6 | 7 | module.exports = [ 8 | User, 9 | ITEM_TYPE, 10 | Item, 11 | Query, 12 | schema 13 | ] 14 | -------------------------------------------------------------------------------- /ex/05/src/schema/types/schema.js: -------------------------------------------------------------------------------- 1 | module.exports = ` 2 | schema { 3 | query: Query 4 | } 5 | ` 6 | -------------------------------------------------------------------------------- /ex/06/README.md: -------------------------------------------------------------------------------- 1 | # Un cliente simple 2 | 3 | Nuestro servidor está listo. Ahora queremos ponernos del otro lado, del lado de quien consume la API GraphQL, es decir, el cliente. 4 | 5 | ## CORS 6 | 7 | Antes vamos a hacer una pequeña modificación al servidor. Vamos a habilitar CORS para que se puedan hacer requests desde un browser en otro dominio sin problemas. 8 | 9 | ``` 10 | npm install --save cors 11 | ``` 12 | 13 | Y modificamos el archivo `index.js` para que quede así: 14 | 15 | ```js 16 | const express = require('express') 17 | const { graphqlExpress, graphiqlExpress } = require('graphql-server-express') 18 | const bodyParser = require('body-parser') 19 | // Se agrega este require 20 | const cors = require('cors') 21 | const schema = require('./schema') 22 | 23 | const app = express() 24 | 25 | // Y se antepone `cors()` a la llamada al middleware `graphqlExpress` 26 | app.use('/graphql', cors(), bodyParser.json(), graphqlExpress({ schema })) 27 | 28 | app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) 29 | 30 | app.listen(3001, function () { 31 | console.log('Our Node server is up and running on port 3001!') 32 | }) 33 | ``` 34 | 35 | ## `create-react-app` 36 | 37 | Vamos a hacer un cliente con React. Para evitar crear todo el _boilerplate_, vamos a usar la herramienta `create-react-app`. Necesitamos instalarla globalmente para poder correrla como comando: 38 | 39 | ``` 40 | npm install -g create-react-app 41 | ``` 42 | 43 | Una vez finalizada la instalación, ejecutamos el comando especificando el nombre del directorio donde se creará nuestro código. 44 | 45 | ``` 46 | create-react-app hackernews 47 | ``` 48 | 49 | Si finalizó todo correctamente, nos modemos a ese directorio con `cd hackernews` y ejecutamos `npm start`. Debería abrirse una solapa en el browser con la aplicación de ejemplo. 50 | 51 | ## Apollo Client 52 | 53 | Ya podemos detener la aplicación con Ctrl+C. Lo que vamos a necesitar ahora es instalar `apollo-client` y `graphql-tag`, dos librerías para usar desde aplicaciones Web y que sirven para hacer consultas a servidores GraphQL. 54 | 55 | ``` 56 | npm install --save apollo-client graphql-tag 57 | ``` 58 | 59 | Abrimos nuestro editor de texto, esta vez dentro del directorio `create-react-app`. Editamos el archivo `src/App.js`. Ahí está el componente de React que se muestra en la página. Vamos a hacer que haga una consulta a GraphQL y se traiga las stories y las muestre: 60 | 61 | Importamos las librerías de Apollo: 62 | 63 | ```js 64 | import ApolloClient, { createNetworkInterface } from 'apollo-client' 65 | import gql from 'graphql-tag' 66 | ``` 67 | 68 | Y agregamos las siguientes propiedades a la clase, justo antes de `render`: 69 | 70 | ```js 71 | client = new ApolloClient({ 72 | networkInterface: createNetworkInterface({ 73 | uri: 'http://localhost:3001/graphql' 74 | }) 75 | }) 76 | 77 | state = { 78 | stories: null 79 | } 80 | 81 | async componentDidMount() { 82 | const result = await this.client.query({ 83 | query: gql` 84 | query { 85 | stories(page: 1, count: 10) { 86 | title 87 | } 88 | } 89 | ` 90 | }) 91 | 92 | this.setState({ stories: result.data.stories }) 93 | } 94 | ``` 95 | 96 | Por último modificamos el markup del método `render` para mostrar las stories: 97 | 98 | ```js 99 | render() { 100 | return ( 101 |
107 | To get started, edit src/App.js
and save to reload.
108 |
47 | To get started, edit src/App.js
and save to reload.
48 |