restql-query-builder v1.0.1
restQL Query Builder
A helper library to create dynamic restQL ad-hoc queries in Javascript.
Usage
Instalation
yarn add restql-query-builder
npm install --save restql-query-builder
Importing the lib
// Chainable utility
import queryBuilder from 'restql-query-builder';
// Pointfree funtions
import { from, as, only, ... } from 'restql-query-builder';
Motivation
restQL is a powerful and early adopted technology that brings a maintanable and robust solution for microservices orchestration. However, it has some limitations in terms of how one can create a query, such as lack of conditionals, dynamic response's fields filtering and optional parameters.
In order to address these and others constrains and to focus on the developer experience the restQL Query Builder attemps to bring the flexibility of a full fledged language such as Javascript and a multi-paradigm approach exposing two interfaces, a fluent builder and a pointfree.
To achieve these goals it leverages the Ad-Hoc Query feature, which allow the execution of external queries send to the restQL server, and a functional design, that enables declarative and adaptable implementation of the interfaces.
Recipes
This package offers two styles for building queries: chainable and pointfree. Therefore, all examples will use both.
Simple Query for an Endpoint
// FROM heroes AS hero
const chainableHeroQuery = queryBuilder()
.from('heroes')
.as('hero')
.toString();
// FROM heroes AS hero
const pointfreeHeroQuery = compose(
toString,
as('hero'),
from('heroes')
)();
Simple Query for multiple Endpoints
// FROM heroes AS hero
const chainableHeroQuery = queryBuilder()
.from('heroes')
.as('hero')
// FROM sidekicks AS sidekick
const chainableSidekickQuery = queryBuilder()
.from('sidekicks')
.as('sidekick');
// FROM heroes
// AS Hero
// FROM sidekicks
// AS sidekick
const chainableFinalQuery = toString([chainableHeroQuery, chainableSidekickQuery]);
// FROM heroes AS hero
const pointfreeHeroQuery = compose(as('hero'), from('heroes'))();
// FROM sidekicks AS sidekick
const pointfreeSidekickQuery = compose(as('sidekick'), from('sidekicks'));
// FROM heroes
// AS Hero
// FROM sidekicks
// AS sidekick
const pointfreeFinalQuery = toString([pointfreeHeroQuery, pointfreeSidekickQuery]);
Note: an utility function to join queries is planned to be added soon.
Query an Endpoint passing query parameters
const heroName = 'Link';
// FROM heroes AS hero WITH name = "Link"
const chainableHeroQuery = queryBuilder()
.from('heroes')
.as('hero')
.withClause('name', heroName)
.toString();
// FROM heroes AS hero WITH name = "Link"
const pointfreeHeroQuery = compose(toString,
with('name', heroName),
as('hero'),
from('heroes'))();
This function supports all RestQL data types, including reference types, as showed below:
// FROM sidekicks AS sidekick WITH heroId = hero.id
const chainableSidekickQuery = queryBuilder()
.from('sidekicks')
.as('sidekick')
.with('heroId', 'hero.id');
// FROM sidekicks AS sidekick WITH heroId = hero.id
const pointfreeSidekickQuery = compose(toString,
withClause('heroId', 'hero.id')
as('sidekick'),
from('sidekicks'));
So the queries will be run in sequence and and the id
field from the result of the hero query will be used in the sidekick query.
Query an Endpoint only for the requested fields
const fieldsRequired = ['name', 'stats'];
// FROM heroes AS hero WITH name = "Link" ONLY name, stats
const chainableHeroQuery = queryBuilder()
.from('heroes')
.as('hero')
.with('name', 'Link'),
.only(fieldsRequired)
.toString();
// FROM heroes AS hero WITH name = "Link" ONLY name, stats
const pointfreeHeroQuery = compose(toString,
only(fieldsRequired),
withClause('name', 'Link'),
as('hero'),
from('heroes'))();
Query multiple Endpoints but not returning one of them's response
// FROM heroes AS hero HIDDEN
const chainableHeroQuery = queryBuilder()
.from('heroes')
.as('hero')
.hidden()
.toString();
// FROM heroes AS hero HIDDEN
const pointfreeHeroQuery = compose(
toString,
hidden(),
as('hero'),
from('heroes')
)();
Query an Endpoint passing a header
const headers = [['accept', 'application/json']];
// OR
const headerObj = {
accept: 'application/json'
}
// FROM heroes AS hero HEADERS accept = "application/json"
const chainableheroQuery = queryBuilder()
.from('heroes')
.as('hero')
.headers(headers)
.toString();
// FROM heroes AS hero HEADERS accept = "application/json"
const pointfreeHeroQuery = compose(toString,
headers(headers),
as('hero'),
from('heroes'))();
Query an Endpoint and ignore error response
// FROM heroes AS hero IGNORE-ERRORS
const chainableheroQuery = queryBuilder()
.from('heroes')
.as('hero')
.ignoreErrors()
.toString();
// FROM heroes AS hero IGNORE-ERRORS
const pointfreeHeroQuery = compose(toString,
ignoreErrors(),
as('hero'),
from('heroes'))();
Apply functions or enconders to Query arguments
const weapons = ['name', 'stats'];
// FROM heroes AS hero
// WITH weapons = ["sword", "shield"] -> flatten
// ONLY name -> match(^"Knight")
const chainableHeroQuery = queryBuilder()
.from('heroes')
.as('hero')
.with('weapons', weapons)
.apply('flatten')
.only("name")
.apply('match("^Knight")')
.toString();
// FROM heroes AS hero
// WITH weapons = ["sword", "shield"] -> flatten
// ONLY name -> match("^Knight")
const pointfreeHeroQuery = compose(toString,
apply('match("^Knight")')
only("name"),
apply('flatten'),
withClause('weapons', weapons),
as('hero'),
from('heroes'))();
Query an Endpoint and set modifiers
// USE use=cache = 600 FROM heroes AS hero IGNORE-ERRORS
const chainableheroQuery = queryBuilder()
.use([['use-cache', 600]])
.from('heroes')
.as('hero')
.toString();
// USE use=cache = 600 FROM heroes AS hero IGNORE-ERRORS
const pointfreeHeroQuery = compose(toString,
as('hero'),
from('heroes'),
use([['use-cache', 600]]))();
API Reference
Although the restQL Query Builder exposes two interfaces they share a common schema, depicted below:
from
Arguments
- resource (String): the registered endpoint name that the query will target. Note that the resource must be mapped on the restQL server that will run the query.
as
Arguments
- alias (String): the name that you would like to reference the request defined by the query.
withClause
Disclaimer: this function should be callend with
, but as it is a reserved keyword on Javascript other name was choosen.
Arguments
- paramaterName (String): the query parameter key expected by your microservice or the placeholder name defined on the mapping between the microservice's URL and the resource name.
- paramterValue (any): the value that will be send to the microservice. Could be a string, number, array, map/object or restQL reference type.
only
Arguments
- fields (Array): the response's field that you'd like to receive.
headers
Arguments
- headers (Object | Array<Pair<String, any>>): the collection of headers to be send.
hidden
Arguments
- shouldBeHidden (Boolean): optional, flag to indicate wheter the
hidden
clasue should be present on the query.
ignoreErrors
Arguments
- shouldIgnoreErrors (Boolean): optional, flag to indicate wheter the
ignore-errors
clasue should be present on the query.
timeout
Arguments
- timeoutValue (Integer): time to be set on query in order to wait for the microservice response.
use
Arguments
- modifiers (Array<Pair<String, any>>): commonly used to set cache control.
Setup
After cloning this project one can follow the steps below:
yarn install
yarn start
, will run the tests for the package.yarn test:w
, will watch the files and run the tests.