Header Ads Widget

Ticker

6/recent/ticker-posts

Cree un servicio API GraphQL simple con Express

 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  package.json 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  index.jsEste 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  node ., luego en otra pestaña abra su navegador en  GraphQL Playground . Una vez allí, ingrese  http://localhost:4000/graphql para acceder a su servidor GraphQL.

API GraphQL

GraphQL Playground le ayudará a explorar su esquema y probar consultas. Incluso crea automáticamente cierta documentación para usted.

API GraphQL

Intente realizar una consulta para  hello utilizar la siguiente consulta:

query {
  hello
}

API GraphQL

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:

  1. 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  npm install --save-dev standard@12.0.1La mayoría de los editores podrán mostrarle advertencias y errores a medida que escribe.
    • También puede editar el  scripts objeto de su  package.json para que pueda ejecutar el linter en cualquier momento con  npm test:
      "scripts": {
        "test": "standard"
      },
  2. Reinicie automáticamente el servidor cuando realice cambios.
    • Instalar  nodemon con npm install --save-dev nodemon@1.18.4
    • Agregue otro script a  package.json, para que pueda ejecutar el servidor con  npm startCombinado con lo anterior, su  scripts objeto debería verse así:
      "scripts": {
        "test": "standard",
        "start": "nodemon ."
      },

Continúe y cierre el servidor con el que había ejecutado  node . y ahora escriba  npm start 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  Query 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  localhost URL.

API GraphQL

El explorador de esquemas es realmente útil para descubrir cómo crear su consulta. Haga clic en el SCHEMA botón verde  para ver su nuevo esquema.

API GraphQL

Necesitará alguna forma de almacenar los datos. Para hacerlo simple, use el Map 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  Person, puede encontrar todas sus publicaciones simplemente solicitándola  person.postsDado que GraphQL solo le permite solicitar los datos que desea, posts 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  rootValue) 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  rootValue.

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
  }
}

API GraphQL


O ponte raro y obtén una sola publicación por identificación, luego el autor de esa publicación y todas las publicaciones de ese autor (incluida la que acabas de consultar).

query {
  post(id: 2) {
    id
    author {
      firstName
      posts {
        id
        body
      }
    }
    body
  }
}

API GraphQL

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  .envEn é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 .

API GraphQL

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  .env 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  .env también.

OKTA_TOKEN={yourOktaAPIToken}

Cree un nuevo archivo llamado  okta.jsAquí 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  initializeApp función agrega algo de middleware para permitirle iniciar sesión con Okta. Siempre que vaya al  http://localhost:4000/access-token, 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  /access-token ruta e imprime su token de acceso actual, que será válido durante aproximadamente una hora.

El  client 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  verifier 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,  index.jsnecesitará importar este archivo y llamar a la  initializeApp función. También necesita usar una herramienta llamada  dotenv que leerá su  .env archivo y agregará las variables  process.envEn la parte superior del archivo, agregue la siguiente línea:

require('dotenv').config({ path: '.env' })

Justo después de la  app.use(cors()) 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 http://localhost:4000/access-token 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  uuidInstálelo con  npm install uuid@3.3.2, luego agréguelo  index.js con:

const uuid = require('uuid/v4')

Eso debería ir cerca de la parte superior del archivo, junto a las otras  require declaraciones.

Mientras aún está en  index.js, 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 rootValue:

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  getUserId función verificará que el authorization encabezado de la  solicitud tenga un token válido. En caso de éxito, devolverá la identificación del usuario.

La  saveUser 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  PEOPLE objeto.

Ahora agregue los siguientes resolutores a  rootValue:

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  submitPost mutación primero verifica el ID de usuario y regresa  null si no hay ningún usuario. Esto significa que no se realizará ninguna operación a menos que esté autenticado. Luego obtiene  id y  body elimina la entrada del usuario. Si no hay  id, 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  null.

Una vez que  submitPost ha determinado que el usuario puede agregar o editar esta publicación, realiza una llamada a  saveUserLa  saveUserfunción no hará nada si el usuario ya existe, pero lo agregará si no existe. A continuación,  submitPost agrega la publicación al  POSTS 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  deletePost 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,  deletePost 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  initializeData 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  null respuesta.

mutation {
  submitPost(input: {
    body: "Hello, world!"
  }) {
    id
    body
    author {
      id
      firstName
      lastName
      posts {
        id
        body
      }
    }
  }
}

API GraphQL

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  Authorization 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  HTTP HEADERSAl hacer clic en eso, se abrirá una sección que le permitirá agregar algunos encabezados como JSON. Agregue lo siguiente, asegurándose de agregarlo  Bearer al frente del token, por lo que debería verse algo así  Bearer eyJraWQ...xHUOjj_A (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:

API GraphQL

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  /access-token punto final desde una ventana de incógnito o después de cerrar la sesión de la consola del desarrollador.

API GraphQL

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  async 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 .

Publicar un comentario

0 Comentarios