GraphQL se ha convertido en una alternativa inmensamente popular a las API REST. La flexibilidad que obtiene al usar GraphQL hace que sea más fácil para los desarrolladores obtener la información que necesitan para una aplicación, y solo la información que necesitan para esa parte de la aplicación. Eso le da la sensación de una API muy personalizada y puede ayudar a reducir el ancho de banda.
En este tutorial, le mostraré cómo escribir una API GraphQL personalizada usando Node y Express. También le mostraré cómo proteger partes de la API mientras que otras partes se abren al público.
Cree la API GraphQL con Express
Si tiene prisa y prefiere ir al grano, puede encontrar el código de muestra final en GitHub . Sin embargo, si desea seguir adelante para obtener más detalles y ver cómo se armó el código, siga leyendo.
Para crear la API, comience creando una nueva carpeta y creando un archivo para administrar sus dependencias. También necesitará instalar algunas dependencias para que GraphQL con Express esté en funcionamiento:
mkdir graphql-express cd graphql-express npm init -y npm install express@2.8.4 express-graphql@0.6.12 graphql@14.0.2 graphql-tag@2.9.2 cors@2.8.4 |
Ahora cree un archivo llamado . Este será su principal punto de entrada:
const express = require( 'express' ) const cors = require( 'cors' ) const graphqlHTTP = require( 'express-graphql' ) const gql = require( 'graphql-tag' ) const { buildASTSchema } = require( 'graphql' ) const app = express() app.use(cors()) const schema = buildASTSchema(gql` type Query { hello: String } `) const rootValue = { hello: () => 'Hello, world' } app.use( '/graphql' , graphqlHTTP({ schema, rootValue })) const port = process.env.PORT || 4000 app.listen(port) console.log(`Running a GraphQL API server at localhost:${port}/graphql`) |
Esto es tan simple como lo es un servidor GraphQL. Todo lo que hace es devolver "Hola, mundo" cuando consulta "hola", pero es un comienzo. Para probarlo, ejecútelo , luego en otra pestaña abra su navegador en GraphQL Playground . Una vez allí, ingrese para acceder a su servidor GraphQL.
GraphQL Playground le ayudará a explorar su esquema y probar consultas. Incluso crea automáticamente cierta documentación para usted.
Intente realizar una consulta para utilizar la siguiente consulta:
query { hello } |
Mejore su experiencia de desarrollador GraphQL
Aquí hay un par de consejos rápidos que le ayudarán a mejorar un poco su experiencia de desarrollo:
- Instale un linter para ayudar a detectar errores en su editor. Esto ayudará a mantener su estilo uniforme y detectará cualquier error que se pueda evitar fácilmente.
- Para instalar StandardJS , escriba . La mayoría de los editores podrán mostrarle advertencias y errores a medida que escribe.
- También puede editar el objeto de su para que pueda ejecutar el linter en cualquier momento con :
"scripts"
: {
"test"
:
"standard"
},
- Reinicie automáticamente el servidor cuando realice cambios.
- Instalar con
- Agregue otro script a , para que pueda ejecutar el servidor con . Combinado con lo anterior, su objeto debería verse así:
"scripts"
: {
"test"
:
"standard"
,
"start"
:
"nodemon ."
},
Continúe y cierre el servidor con el que había ejecutado y ahora escriba para reiniciar el servidor de desarrollo. A partir de ahora, cualquier cambio que realice reiniciará automáticamente el servidor.
Crear las consultas GraphQL
Para obtener algo un poco más útil, creemos un editor de publicaciones. GraphQL está fuertemente tipado, lo que le permite crear un tipo para cada objeto y conectarlos. Un escenario común podría ser tener una publicación con algún texto, escrito por una persona. Actualice su esquema para incluir estos tipos. También puede actualizar su tipo para utilizar estos nuevos tipos.
type Query { posts: [Post] post(id: ID): Post authors: [Person] author(id: ID): Person } type Post { id: ID author: Person body: String } type Person { id: ID posts: [Post] firstName: String lastName: String } |
Aunque los resolutores no están configurados, ya puede volver a GraphQL Playground y actualizar el esquema haciendo clic en el icono de flecha circular junto a la URL.
El explorador de esquemas es realmente útil para descubrir cómo crear su consulta. Haga clic en el botón verde para ver su nuevo esquema.
Necesitará alguna forma de almacenar los datos. Para hacerlo simple, use el objeto de JavaScript para el almacenamiento en memoria. También puede crear algunas clases que ayudarán a conectar los datos de un objeto a otro.
const PEOPLE = new Map() const POSTS = new Map() class Post { constructor (data) { Object.assign( this , data) } get author () { return PEOPLE.get( this .authorId) } } class Person { constructor (data) { Object.assign( this , data) } get posts () { return [...POSTS.values()].filter(post => post.authorId === this .id) } } |
Ahora, si tiene una instancia de a , puede encontrar todas sus publicaciones simplemente solicitándola . Dado que GraphQL solo le permite solicitar los datos que desea, nunca se llamará al getter a menos que lo solicite, lo que podría acelerar la consulta si es una operación costosa.
También deberá actualizar sus resolutores (las funciones en ) para adaptarse a estos nuevos tipos.
const rootValue = { posts: () => POSTS.values(), post: ({ id }) => POSTS.get(id), authors: () => PEOPLE.values(), author: ({ id }) => PEOPLE.get(id) } |
Esto es genial, pero aún no hay datos. Por ahora, inserte algunos datos falsos. Puede agregar esta función y la llamada a ella inmediatamente después de la asignación a .
const initializeData = () => { const fakePeople = [ { id: '1' , firstName: 'John' , lastName: 'Doe' }, { id: '2' , firstName: 'Jane' , lastName: 'Doe' } ] fakePeople.forEach(person => PEOPLE.set(person.id, new Person(person))) const fakePosts = [ { id: '1' , authorId: '1' , body: 'Hello world' }, { id: '2' , authorId: '2' , body: 'Hi, planet!' } ] fakePosts.forEach(post => POSTS.set(post.id, new Post(post))) } initializeData() |
Ahora que tiene todas sus consultas configuradas y algunos datos almacenados, regrese a GraphQL Playground y juegue un poco. Intente obtener todas las publicaciones u obtenga todos los autores y publicaciones asociadas con cada una.
query { posts { id author { id firstName lastName } body } } |
query { post(id: 2) { id author { firstName posts { id body } } body } } |
Agregue autenticación de usuario a su API Express + GraphQL
Una forma sencilla de agregar autenticación a su proyecto es con Okta. Okta es un servicio en la nube que permite a los desarrolladores crear, editar y almacenar de forma segura cuentas de usuario y datos de cuentas de usuario, y conectarlos con una o varias aplicaciones. Si aún no tiene una, regístrese para obtener una cuenta de desarrollador gratuita para siempre .
Necesitará guardar cierta información para usarla en la aplicación. Cree un nuevo archivo llamado . En él, ingrese la URL de su organización.
HOST_URL=http: //localhost :4000 OKTA_ORG_URL=https: // {yourOktaOrgUrl} |
También necesitará una cadena aleatoria para usar como una aplicación secreta para las sesiones. Puede generar esto con los siguientes comandos:
npm install -g uuid-cli echo "APP_SECRET=`uuid`" >> . env |
A continuación, inicie sesión en su consola de desarrollador, vaya a Aplicaciones y haga clic en Agregar aplicación . Seleccione Web , luego haga clic en Siguiente .
La página a la que llega después de crear una aplicación tiene más información que necesita para guardar en su archivo. Copie el ID del cliente y el secreto del cliente.
OKTA_CLIENT_ID={yourClientId} OKTA_CLIENT_SECRET={yourClientSecret} |
La última información que necesita de Okta es un token de API. En su consola de desarrollador, vaya a API -> Tokens , luego haga clic en Crear token . Puede tener muchos tokens, así que déle a este un nombre que le recuerde para qué sirve, como “GraphQL Express”. Recibirás una ficha que solo puedes ver ahora. Si pierde el token, tendrá que crear otro. Agregue esto también.
OKTA_TOKEN={yourOktaAPIToken} |
Cree un nuevo archivo llamado . Aquí es donde creará algunas funciones de utilidad, además de inicializar la aplicación para Okta. Cuando se autentica a través de Okta, su aplicación se autenticará a través de un token de acceso mediante JWT. Puede usar esto para determinar quién es un usuario. Para evitar lidiar directamente con la autenticación en su aplicación, un usuario iniciaría sesión en los servidores de Okta y luego le enviaría un JWT que puede verificar.
okta.js
const session = require( 'express-session' ) const OktaJwtVerifier = require( '@okta/jwt-verifier' ) const verifier = new OktaJwtVerifier({ clientId: process.env.OKTA_CLIENT_ID, issuer: `${process.env.OKTA_ORG_URL}/oauth2/ default ` }) const { Client } = require( '@okta/okta-sdk-nodejs' ) const client = new Client({ orgUrl: process.env.OKTA_ORG_URL, token: process.env.OKTA_TOKEN }) const { ExpressOIDC } = require( '@okta/oidc-middleware' ) const oidc = new ExpressOIDC({ issuer: `${process.env.OKTA_ORG_URL}/oauth2/ default `, client_id: process.env.OKTA_CLIENT_ID, client_secret: process.env.OKTA_CLIENT_SECRET, redirect_uri: `${process.env.HOST_URL}/authorization-code/callback`, scope: 'openid profile' }) const initializeApp = (app) => { app.use(session({ secret: process.env.APP_SECRET, resave: true , saveUninitialized: false })) app.use(oidc.router) app.use( '/access-token' , oidc.ensureAuthenticated(), async (req, res, next) => { res.send(req.userContext.tokens.access_token) }) } module.exports = { client, verifier, initializeApp } |
La función agrega algo de middleware para permitirle iniciar sesión con Okta. Siempre que vaya al , primero comprobará que ha iniciado sesión. Si no lo está, primero le enviará a los servidores de Okta para autenticarse. Una vez que la autenticación es exitosa, lo regresa a la ruta e imprime su token de acceso actual, que será válido durante aproximadamente una hora.
El que está exportando le permite ejecutar algunas llamadas administrativas en su servidor. Lo usará más adelante para obtener más información sobre un usuario según su ID.
el es lo que se utiliza para verificar que un JWT es válida, y le da alguna información básica acerca de un usuario, como su ID de usuario y dirección de correo electrónico.
Ahora, necesitará importar este archivo y llamar a la función. También necesita usar una herramienta llamada que leerá su archivo y agregará las variables . En la parte superior del archivo, agregue la siguiente línea:
require( 'dotenv' ).config({ path: '.env' }) |
Justo después de la línea, agregue lo siguiente:
const okta = require( './okta' ) okta.initializeApp(app) |
Para que todo esto funcione, también necesitará instalar algunas dependencias nuevas:
npm i @okta /jwt-verifier @0.0.12 @okta /oidc-middleware @1.0.0 @okta /okta-sdk-nodejs @1.2.0 dotenv@6.0.0 express-session@1.15.6 |
Ahora debería poder acceder a para iniciar sesión y obtener un token de acceso. Si solo estaba en su consola de desarrollador, probablemente encontrará que ya ha iniciado sesión. Puede cerrar la sesión de su consola de desarrollador para asegurarse de que el flujo funcione correctamente.
Crear mutaciones GraphQL
Ahora es el momento de utilizar datos reales. Puede que existan algunos verdaderos John y Jane Does, pero es probable que aún no tengan una cuenta en su aplicación. A continuación, le mostraré cómo agregar algunas mutaciones que usarán su usuario actual para crear, editar o eliminar una publicación.
Para generar ID para una publicación, puede usar . Instálelo con , luego agréguelo con:
const uuid = require( 'uuid/v4' ) |
Eso debería ir cerca de la parte superior del archivo, junto a las otras declaraciones.
Mientras aún está en , agregue los siguientes tipos a su esquema:
type Mutation { submitPost(input: PostInput!): Post deletePost(id: ID!): Boolean } input PostInput { id: ID body: String! } |
Para verificar al usuario y guardarlo como una nueva persona, necesitará dos nuevas funciones de utilidad. Agregue estos justo antes :
const getUserId = async ({ authorization }) => { try { const accessToken = authorization.trim().split( ' ' )[1] const { claims: { uid } } = await okta.verifier.verifyAccessToken(accessToken) return uid } catch (error) { return null } } const saveUser = async (id) => { try { if (!PEOPLE.has(id)) { const { profile: { firstName, lastName } } = await okta.client.getUser(id) PEOPLE.set(id, new Person({ id, firstName, lastName })) } } catch (ignore) { } return PEOPLE.get(id) } |
La función verificará que el encabezado de la solicitud tenga un token válido. En caso de éxito, devolverá la identificación del usuario.
La función comprueba que el usuario aún no esté guardado. Si es así, simplemente devuelve el valor almacenado en caché. De lo contrario, buscará el nombre y apellido del usuario y lo almacenará en el objeto.
Ahora agregue los siguientes resolutores a :
submitPost: async ({ input }, { headers }) => { const authorId = await getUserId(headers) if (!authorId) return null const { id = uuid(), body } = input if (POSTS.has(id) && POSTS.get(id).authorId !== authorId) return null await saveUser(authorId) POSTS.set(id, new Post({ id, authorId, body })) return POSTS.get(id) }, deletePost: async ({ id }, { headers }) => { if (!POSTS.has(id)) return false const userId = await getUserId(headers) if (POSTS.get(id).authorId !== userId) return false POSTS. delete (id) if (PEOPLE.get(userId).posts.length === 0) { PEOPLE. delete (userId) } return true } |
La mutación primero verifica el ID de usuario y regresa si no hay ningún usuario. Esto significa que no se realizará ninguna operación a menos que esté autenticado. Luego obtiene y elimina la entrada del usuario. Si no hay , generará uno nuevo. Si ya hay una publicación con la identificación proporcionada, verifica que sea propiedad del usuario que intenta editarla. Si no, vuelve de nuevo .
Una vez que ha determinado que el usuario puede agregar o editar esta publicación, realiza una llamada a . La función no hará nada si el usuario ya existe, pero lo agregará si no existe. A continuación, agrega la publicación al objeto y devuelve el valor en caso de que el cliente quiera consultar la publicación agregada (para obtener la identificación, por ejemplo).
La mutación solo te permitirá eliminar una publicación si eres el usuario que la creó. Después de eliminar con éxito una publicación, verifica si el usuario tiene otras publicaciones. Si esa fue su única publicación, también eliminará a ese usuario del conjunto de datos para borrar algo de memoria (una cantidad bastante pequeña de).
También puede deshacerse de la función ahora que tiene la capacidad de agregar datos reales.
Pruebe las nuevas mutaciones GraphQL
Intente realizar una llamada a la nueva mutación y cree una publicación. Como no está autenticado, debería obtener una respuesta.
mutation { submitPost(input: { body: "Hello, world!" }) { id body author { id firstName lastName posts { id body } } } } |
Por lo general, una aplicación de algún tipo, ya sea una aplicación web o una aplicación nativa, manejará la interfaz de usuario para la autenticación y luego pasará sin problemas el encabezado a la API. En este caso, dado que solo nos estamos enfocando en la API, hice que implementaras un punto final para tomar el token de autenticación manualmente.
Dirígete a http: // localhost: 4000 / access-token para iniciar sesión con Okta y obtener un token de acceso. Copie el token de acceso, luego regrese a GraphQL Playground. En la parte inferior de la página, hay un enlace que dice . Al hacer clic en eso, se abrirá una sección que le permitirá agregar algunos encabezados como JSON. Agregue lo siguiente, asegurándose de agregarlo al frente del token, por lo que debería verse algo así (aunque el token real será mucho más largo):
{ "authorization" : "Bearer {yourAccessToken}" } |
Ahora debería estar autenticado y la misma mutación devolverá una publicación válida:
Si desea jugar con otros usuarios, puede agregar personas desde la consola del desarrollador navegando a Usuarios -> Personas , luego haciendo clic en Agregar persona . A continuación, puede visitar el punto final desde una ventana de incógnito o después de cerrar la sesión de la consola del desarrollador.
Obtenga más información sobre GraphQL, Express y Okta
Intente jugar un poco con la API y vea qué cosas divertidas puede hacer con ella. Creo que verá rápidamente qué puede hacer que GraphQL sea mucho más poderoso que una API REST tradicional, y cómo puede ser divertido trabajar con él incluso si solo está usando Playground. Vea si puede encontrar puntos de datos para conectarse u obtener datos de fuentes externas. Dado que los resolutores son simplemente funciones, puede obtener datos de una API externa o de una base de datos con la misma facilidad. Tu imaginación es el limite.
Si desea ver el código de muestra final, puede encontrarlo en GitHub .
0 Comentarios
Dejanos tu comentario para seguir mejorando!