Node.js

Servidor Graphql con node y couchbase, ottoman y spatial view

Graphql es un lenguaje de consulta para APIs, fue desarrollado por facebook y fue lanzado en 2015. Está diseñado para construir aplicaciones cliente proporcionando una sintaxis y un sistema intuitivo y flexible para describir sus requisitos de datos e interacciones. O

Comparte este artículo

José Navarro es un desarrollador full stack en FAMOCO en Bruselas, Bélgica. Ha estado trabajando durante los últimos 3 años como desarrollador web. desarrollador con Node.js, Java, AngularJS y ReactJS, y tiene un profundo interés en el desarrollo web y las tecnologías móviles.

Vamos a desarrollar un Graphql servidor en nodejs con express. Graphql es un lenguaje de consulta para APIs, fue desarrollado por facebook y fue lanzado en 2015. Está diseñado para construir aplicaciones cliente proporcionando una sintaxis y un sistema intuitivo y flexible para describir sus requisitos de datos e interacciones. Una de las mayores diferencias con las APIs REST es que sólo tienes un endpoint de entrada para todos los recursos, en lugar de un endpoint para cada recurso; Y con graphql especificas qué atributos quieres para cada petición, en lugar de recibir lo que te devuelva el servicio de la API REST, por lo que podemos estar seguros de que tenemos todos los datos que necesitamos y reducir el tamaño de nuestras peticiones.

Facebook ha estado utilizando durante unos años en su aplicación móvil, por ejemplo, en la aplicación de iOS, y la versión antigua de su aplicación móvil sigue funcionando sin problemas porque el graphql no cambió, porque es un punto final con el mismo esquema, esto probablemente no sería posible con las API REST porque cuando se libera una nueva versión de la API, su punto final probablemente va a cambiar, por lo que los clientes tienen que adaptarse al nuevo punto final y los datos. También Github abrir su servidor graphql, para que los usuarios puedan consultar sus servicios utilizando graphql, en lugar de los servicios de las API REST..

Con el servidor vamos a consultar y crear Lugares. Para almacenar los datos vamos a utilizar couchbase y vamos a utilizar vistas espaciales para consultar los Lugares por su ubicación geográfica. Escribí un Correo electrónico: sobre node y couchbase, así que me saltaré la configuración de la db que escribí en el post anterior.

Requisitos

Necesitas tenerlo instalado en tu ordenador:

- nodejs

- servidor couchbase

Encontrará el código en la página repositorio github.

Vista espacial

En primer lugar tenemos que crear la vista espacial. Vamos a la página de administración, en mi caso http://localhost:8091/ e iniciar sesión con mi usuario y contraseña. A continuación, haga clic en Cubos de datos y crear un cubo, lo llamé graphql. Después hacemos clic en Very, a continuación, hacemos clic en Crear vista espacial de desarrolloy escribimos los valores.

Create Spatial Development View

Utilicé lugar_por_localización en ambos Nombre del documento de diseño y Ver nombre. Ahora haga clic en editary añada el código siguiente


function (doc) {

if (doc._type === 'Lugar' && doc.location) {

emit([{

"tipo": "Punto",

"coordenadas": [doc.location.lon, doc.location.lat]

}], doc);

}

}
y haga clic en Guardar.

Aquí también puede probar la vista con los documentos del cubo.

Lugar Modelo

Para nuestros lugares, vamos a almacenar el nombre del lugar y una descripción como cadena.

Puesto que queremos utilizar el SpatialView que Couchbase proporciona, vamos a almacenar la localización del lugar en un objeto llamado *location* donde vamos a almacenar la latitud y la longitud.

También estableceremos por defecto la fecha de creación cuando añadamos el lugar.


const PlaceModel = otomana.model('Lugar', {

nombre: 'cadena',

descripción: "cadena",

ubicación: {

lat: 'número',

lon: "número

},

creado: {

tipo: 'Fecha',

por defecto: Date.now

}

});
Para la consulta espacial, vamos a definir una función que realizará una consulta espacial. Para ello vamos a crear una consulta espacial utilizando el paquete couchbase.


const queryByLocation = (bbox = [0, 0, 0, 0], next) => {

const query = couchbase.SpatialQuery.from('dev_place_by_location', 'place_by_location').bbox(bbox);

bucket.query(query, next);

}
En la función *from*, tenemos que proporcionar el *design document name* y el *view name*. A continuación, en el bbox(bounding box), tenemos que proporcionar la matriz de 4 floats **[ min Longitud , min Latitud , max Longitud , max Latitud ]**.

El último paso es realizar la consulta en el cubo.

Servidor Graphql

Vamos a utilizar un servidor express y el paquete express-graphql.

Importamos el esquema de nuestro servidor graphql que vamos a definir más adelante.


const express = require('express');

const graphqlHTTP = require('express-graphql');

const PUERTO = proceso.entorno.PUERTO || 5000;

const schema = require('./schemas');

const app = express();

app.use('/graphql', graphqlHTTP({

esquema: esquema,

graphiql: true,

}));

// iniciar servidor

const servidor = app.listen(PORT, () => {

console.log(Servidor iniciado en ${ server.address().port });

});
En el servidor graphql, vamos a utilizar la ruta */graphql*. Y vamos a establecer alguna opción, como graphiql, que nos proporcionará una interfaz gráfica para ejecutar consultas.

El último paso es iniciar nuestro servidor express.

Esquemas Graphql

Las consultas y mutaciones de Graphql se basan en los esquemas que definimos. Así que tenemos que crear un esquema para nuestro objeto Place.

Primero vamos a definir un esquema para Ubicación.


const {

GraphQLObjectType,

GraphQLFloat,

GraphQLNonNull,

} = require('graphql');

const LocationSchema = new GraphQLObjectType({

nombre: "Ubicación",

descripción: "Localización geográfica",

campos: {

lat: {

tipo: new GraphQLNonNull(GraphQLFloat),

descripción: "Latitud",

},

lon: {

tipo: new GraphQLNonNull(GraphQLFloat),

descripción: "Longitud",

},

}

});

module.exports = EsquemaDeLocalización;

Tenemos que importar los tipos de graphql paquete. En nuestro esquema podemos definir un nombre y una descripción, estos campos son útiles para documentar nuestras consultas, para que el usuario pueda entender lo que significa ese campo.

Entonces tenemos que definir camposdonde vamos a especificar los campos dentro de nuestro esquema, en este caso, hemos definido lat y lon. En cada campo tenemos que especificar el tipo, en este caso estos campos son valores float, y son obligatorios, por lo que usamos GraphQLNonNull  y el tipo GraphQLFloat. Añadimos una descripción para saber a qué se refieren.

Ahora vamos a definir el esquema Lugar.

Aquí vamos a importar los tipos de graphql y el esquema Ubicación que hemos definido.


const {

GraphQLObjectType,

GraphQLString,

GraphQLNonNull,

} = require('graphql');

const LocationSchema = require('./location');

const PlaceSchema = new GraphQLObjectType({

nombre: "Lugar",

descripción: "Descripción del lugar",

campos: {

id: {

tipo: GraphQLString,

resolve(lugar) {

devolver lugar._id;

}

},

nombre: {

tipo: GraphQLString,

},

descripción:{

tipo: GraphQLString,

},

ubicación: {

tipo: LocationSchema,

},

creado: {

tipo: GraphQLString,

}

}

});

module.exports = PlaceSchema;

Estamos comparando los campos del modelo, por lo que no tenemos que proporcionar la función de resolución. Sólo para el campo idporque couchbase devuelve el valor en el campo _id.

Consulta Graphql

La consulta es la forma en que recuperamos datos para el servidor.

El objeto de consulta también es un esquema, como los anteriores. En este caso los campos son las consultas que permitimos realizar al usuario. Vamos a definir 3 tipos de consulta.

todosLugares

En esta consulta vamos a consultar todas las plazas de la base de datos, y las vamos a ordenar por el campo creado, de forma que devolvemos primero las plazas más nuevas.

Como vamos a devolver un array de Lugares, asignamos a tipo el tipo GraphQLList y proporcionamos el Esquema del lugar


...

const PlaceSchema = require('./place');

const Lugar = require('../modelos/lugar');

allPlaces: {

tipo: new GraphQLList(PlaceSchema),

descripción: "Consulta de todas las plazas",

resolve(root, args) {

return new Promise((resolver, rechazar) => {

Place.find({}, {

ordenar: {

creado: -1

},

}, (err, places) => {

if (err) {

rechazar(err);

}

resolver(lugares);

})

});

}

}

También añadimos una descripción, este campo es opcional.

El último parámetro es una función, que es la función resolver, que especificará cómo vamos a recuperar los datos para nuestra base de datos. Como nuestras llamadas a la base de datos son asíncronas, vamos a devolver una promesa, que va a utilizar el modelo Place que definimos con ottoman. Con el modelo usamos find para consultar documentos, y pasamos como primer parámetro un objeto vacío, porque queremos consultar todos los documentos; El segundo parámetro son las opciones de nuestra consulta, en este caso vamos a ordenar por el campo creado en orden descendente. Finalmente proporcionamos la función callback que resolverá la promesa con los valores, o la rechazará en caso de error.

Lugares

En esta consulta vamos a consultar utilizando la vista espacial, por lo que tenemos que pasar los puntos bbox en el parámetro de la consulta.


...

const queryByLocation = require('../models/place').queryByLocation;

Lugares: {

tipo: new GraphQLList(PlaceSchema),

descripción: 'Consulta de todos los lugares dentro del cuadro delimitador',

args: {

minLon: {

tipo: new GraphQLNonNull(GraphQLFloat),

descripción: 'Longitud mínima de la caja límite',

},

maxLon: {

tipo: new GraphQLNonNull(GraphQLFloat),

descripción: 'Longitud máxima de la caja límite',

},

minLat: {

tipo: new GraphQLNonNull(GraphQLFloat),

descripción: 'Latitud mínima de la caja límite',

},

maxLat: {

tipo: new GraphQLNonNull(GraphQLFloat),

descripción: 'Latitud máxima de la caja límite',

},

},

resolve(root, args) {

// bbox = [ min Longitud , min Latitud , max Longitud , max Latitud ]

const bbox = [

args.minLon,

args.minLat,

args.maxLon,

args.maxLat,

];

return new Promise((resolver, rechazar) => {

queryByLocation(bbox, (err, places) => {

if (err) {

rechazar(err);

}

resolve(lugares.map((lugar) => lugar.valor));

})

});

}

}

Primero importamos la función que hemos definido para realizar la consulta espacial.

Como en la consulta anterior, definimos el tipo como un array de lugares, y añadimos una descripción.

En esta consulta, necesitamos algún parámetro, por lo que definimos los args, que se refiere al parámetro; Cada valor dentro de args corresponden con los parámetros, en este caso definimos 4, minLon, maxLon, minLat, maxLat y para todos ellos vamos a definir el tipo como requerido y floats.

En este caso, la función resolver es también una promesa. Primero construimos el array bbox para pasar la función queryByLocation. En caso de error, rechazaremos la promesa con un error; En caso de éxito, necesitamos mapear el objeto desde la db, porque la vista espacial devuelve el geopunto y el valor, donde estamos devolviendo el documento completo, cambiará si definimos una vista espacial diferente.

Lugar

La última consulta que vamos a definir, es la de buscar un lugar por su id.


Lugar: {

tipo: PlaceSchema,

descripción: 'Consulta de un lugar por el id de lugar',

args: {

id: {

tipo: new GraphQLNonNull(GraphQLString),

descripción: 'Lugar id',

}

},

resolve(root, args) {

return new Promise((resolver, rechazar) => {

Place.getById(args.id, (err, place) => {

if (err) {

rechazar(err);

}

resolver(lugar);

});

});

}
En este caso, el tipo es el Esquema del lugaren los args, sólo tenemos que definir el parámetro id y lo configuramos como string y required.

La función resolver, de nuevo es una promesa, en este caso vamos a utilizar la función queryById del modelo, y pasamos el id del objeto args.

Mutación Graphql

Con las mutaciones podemos modificar los datos en nuestro servidor. Como la consulta, el objeto mutaciones son esquemas. Así que tenemos que definir los mismos campos que los esquemas anteriores.

Cuando realizamos la consulta de mutación proporcionamos los valores entre paréntesis, y al igual que las consultas, proporcionamos los valores que queremos recuperar del objeto modificado.

Aquí vamos a realizar la creación, actualización y eliminación de Lugares.

crearLugar

En esta mutación vamos a crear un nuevo lugar.

En el tipo del esquema vamos a definirlo como Esquema de lugarporque vamos a devolver el Lugar creado.


createPlace: {

tipo: PlaceSchema,

descripción: "Crear un lugar",

args: {

nombre: {

tipo: new GraphQLNonNull(GraphQLString),

descripción: "Nombre del lugar",

},

descripción: {

tipo: new GraphQLNonNull(GraphQLString),

description: "Descripción del lugar",

},

latitud: {

tipo: new GraphQLNonNull(GraphQLFloat),

descripción: 'Latitud del lugar',

},

longitud: {

tipo: new GraphQLNonNull(GraphQLFloat),

descripción: "Longitud del lugar",

}

},

resolve(source, args) {

return new Promise((resolver, rechazar) => {

const lugar = nuevo Lugar({

nombre: args.nombre,

descripción: args.description,

ubicación: {

lat: args.latitude,

lon: args.longitud,

},

});

place.save((err) => {

if (err) {

rechazar(err);

}

resolver(lugar);

})

});

}

}
Al igual que en las consultas, definimos los args con los valores que requerimos para crear un nuevo Lugar. En este caso requerimos nombre y descripción como strings, y latitud y longitud como floats, todos los campos van a ser requeridos.

En la función resolver, vamos a devolver una promesa. Dentro de la promesa vamos a crear el lugar con los valores de la consulta dentro de args. A continuación vamos a realizar guardar sobre el objeto lugar. Por último, en caso de error al guardar el lugar, vamos a rechazar la promesa con el error, o vamos a resolver la promesa con los datos del lugar.

updatePlace

Como en el crearLugar mutación, la updatePlace es similar, las diferencias son que en este caso, todos los valores en los args no son requeridos, y el campo id es una cadena requerida; Y en la función resolve, primero vamos a buscar el objeto por el campo id, luego comprobamos los campos proporcionados por el usuario y actualizamos el lugar, y finalmente lo guardamos, y devolvemos el nuevo objeto


updatePlace: {

tipo: PlaceSchema,

descripción: "Actualizar un lugar",

args: {

id: {

tipo: new GraphQLNonNull(GraphQLString),

descripción: "Id del lugar",

},

nombre: {

tipo: GraphQLString,

descripción: "Nombre del lugar",

},

descripción: {

tipo: GraphQLString,

description: "Descripción del lugar",

},

latitud: {

tipo: GraphQLFloat,

descripción: 'Latitud del lugar',

},

longitud: {

tipo: GraphQLFloat,

descripción: "Longitud del lugar",

}

},

resolve(source, args) {

return new Promise((resolver, rechazar) => {

Place.getById(args.id, (err, place) => {

if (err) {

rechazar(err);

} else {

if (args.name) {

place.name = args.name;

}

if (args.description) {

place.name = args.name;

}

if (args.latitude) {

place.location.lat = args.latitude;

}

if (args.longitud) {

place.location.lon = args.longitude;

}

place.save((err) => {

if (err) {

rechazar(err);

}

resolver(lugar);

});

}

})

});

}

}

deletePlace

La última mutación es la supresión, aquí definimos un tipo de Esquema de lugarporque vamos a devolver el objeto que borramos.

En los args, sólo tenemos que definir los parámetros id del lugar a eliminar.

En la función resolver, vamos a devolver una promesa que va a buscar el lugar por id, y el realizar la eliminación. Vamos a rechazar la promesa en caso de que el lugar no se encuentra o si hay un error al eliminar; O vamos a resolver la promesa con los datos de lugar en caso de que eliminar con éxito.


deletePlace: {

tipo: PlaceSchema,

descripción: "Borrar un lugar",

args: {

id: {

tipo: new GraphQLNonNull(GraphQLString),

descripción: "Id del lugar",

},

},

resolve(source, args) {

return new Promise((resolver, rechazar) => {

Place.getById(args.id, (err, place) => {

if (err) {

rechazar(err);

} else {

place.remove((err) => {

if (err) {

rechazar(err);

}

resolver(lugar);

});

}

})

});

}

}

Prueba

Para probar nuestra app, vamos a utilizar Graphiql, que permitimos en nuestro servidor, para ello tenemos que visitar http://localhost:5000/graphql

graphiql

En esta página podemos realizar las consultas y mutaciones que hemos definido anteriormente.

Cree


mutación {

crearLugar(

nombre: "testplace"

descripción: "testdescription"

latitud: 1,36

longitud: 18.36

) {

id

}

}

mutation create

Actualización


mutación {

updatePlace(

id: “41133f98-18e8-4979-89e0-7af012b0e14f”

nombre: "updateplace"

descripción: "updatedescription"

latitud: 2,36

longitud: 15.96

) {

id

nombre

descripción

}

}

mutation update

Borrar


mutación {

deletePlace(id: "41133f98-18e8-4979-89e0-7af012b0e14f") {

id

}

}
mutation delete

Consultar todo


consulta {

todosLugares {

id

nombre

ubicación {

lat

lon

}

}

}

query all

Consulta por cuadro delimitador


consulta {

Lugares(

minLon: 3

maxLon: 5

minLat: 49

maxLat: 51

) {

nombre

ubicación {

lat

lon

}

}

}
query bbox

Consultar un lugar por id


mutación {

deletePlace(id: "41133f98-18e8-4979-89e0-7af012b0e14f") {

id

}

}


query id

Conclusión

Graphql es un buen lenguaje de consulta que nos permite consultar sólo la información que definimos, por lo que podemos evitar underfetching o overfetching, y podemos estar seguros de que siempre tenemos los datos.

En un servidor Graphql, los clientes sólo utilizan un único endpoint, por lo que oculta la complejidad y la lógica del backend, por lo que el servidor puede conectarse a diferentes backends, o utilizar diferentes bases de datos, y si cambian, la lógica de los clientes no tienen que cambiar porque el endpoint es el mismo.

También hemos visto como realizar consultas geográficas en nuestros datos con couchbase.

Referencias

Comparte este artículo
Recibe actualizaciones del blog de Couchbase en tu bandeja de entrada
Este campo es obligatorio.

Autor

Publicado por Laura Czajkowski, Directora de la Comunidad de Desarrolladores, Couchbase

Laura Czajkowski es la Snr. Developer Community Manager en Couchbase supervisando la comunidad. Es responsable de nuestro boletín mensual para desarrolladores.

Deja un comentario

¿Listo para empezar con Couchbase Capella?

Empezar a construir

Consulte nuestro portal para desarrolladores para explorar NoSQL, buscar recursos y empezar con tutoriales.

Utilizar Capella gratis

Ponte manos a la obra con Couchbase en unos pocos clics. Capella DBaaS es la forma más fácil y rápida de empezar.

Póngase en contacto

¿Quieres saber más sobre las ofertas de Couchbase? Permítanos ayudarle.