jsonql-koa

jsonql Koa middleware

Usage no npm install needed!

<script type="module">
  import jsonqlKoa from 'https://cdn.skypack.dev/jsonql-koa';
</script>

README

NPM

jsonql Koa middleware

This is the jsonql middleware previously published as jsonql-koa, and completely rewritten with ES6

BREAKING CHANGE

@ V1.3.10

The module use named export jsonqlKoa, this will affect all existing code base. Please change accordingly.

import { jsonqlKoa } from 'jsonql-koa'

Please change all your code accordingly.

@ V1.6.0

We add a new property to the configuration call appDir, and from now on, we will expect all the code will be under this directory (instead of spread around different places). The default name of this property is jsonql. This will be a required field, and validate during start up time.

Installation

$ npm install jsonql-koa --save

or

$ yarn add jsonql-koa

This module export three methods:

  • jsonqlKoa(config = {}): this gives you the Koa middleware, just supply the config (if needed, see below)
  • composeJsonqlKoa(opts): this is NOT for general use, since you have no idea what are the options, this is like a lego for us to build with other modules
  • extendConfigCheck(extraAppProps, extraConstProps, config): this goes with the composeJsonqlKoa to create the jsonqlKoa. Again this is not for general use, we use this to build with other modules.

Basically you only need this:

import { jsonqlKoa } from 'jsonql-koa' 

In fact, we don't recommend you to use this package directly (unless you want create your own cool modules). Use @jsonql/koa instead.

Configuration options

Name Description Expected Type Default value
projectRootDir Where the root of your project is String process.cwd()
appDir The name of your project directory, the above resolverDir and contractDir will under this directory String ''
resolversDir The name of the resolvers directory String 'resolvers'
contractDir The name of the contract directory String 'contract'
enableAuth if you need to use jwt authorisation Boolean false
keysDir Where we store your RSA key to validate against the JWT token String 'keys'

More options to come later

Required Middlewares

Also you need to install middleware to parse the JSON content. Here we use koa-bodyparser.

const Koa = require('koa')
const { jsonqlKoa } = require('jsonql-koa')
const bodyparser = require('koa-bodyparser')
const { join } = require('path')

const app = new Koa()
app.use(bodyparser())
app.use(jsonqlKoa({
  resolverDir: join(__dirname, 'resolvers') // this is the default value
}))

Resolver(s)

We expect the file in dasherized name style.

For example your resolver directory is <root>/resolvers, and your resolver name fetchList, therefore the expected resolver file name will be: <root>/resolvers/query/fetch-list.js.

To properly organise your application. You should have something like this (otherwise it won't work)

/resolvers/query/name-of-your-call/index.js
                                  /lib.js
                                  /utils.js
/resolvers/mutation/name-of-your-update/index.js
...

This way you can separate the export main function to your helpers / utility what not. Also that makes the contract generator to understand your project and create the contract without your input.

And your resolver should look something like this:

// fetchList
/**
 * @param {object} params bunch of stuff
 * @return {object} modified bunch of stuff
 */
module.exports = function(params) {
    params.modified = parseInt((new Date()).getTime()/1000, 10)
    // some more things with your params
    return params;
}

The middleware is using Async/Await internally, so you could return a promise.

ES6 support

As of V1.3.0 release. You can use ES6 module syntax for your resolver. If you using ES6 then you have to use all of them as ES6 modules, it doesn't support mix and match. The reason is we have to inject a esm export script to transpire your ES6 module to common js on the fly and import into your Koa middleware.

/**
 * @param {number} id
 * @return {any} your result
 */
export default function getSomethingById(id) {
  // return your result
}

Return Data format

The result data will be wrap inside this signature

{
    data: {
      yourData: 'something'
    }
}

We will be adapting more of the JSON API shortly

What you can do with this system

There are four types within this system you can call.

  • query - Think of it as the REST API GET, where you get things
  • mutation - Use this when you want to change something
  • auth - Authorization methods
  • socket - Coming soon

Query

When you using our js client or node client

It will generate function for you based on the contract file it received.

From V.1 onward we will turn on the useDoc option, which means you are REQUIRED to write correct jsdoc for your resolvers

For example you have this resolver:

// ~/project/resolvers/query/ask-what-date.js
/**
 * @return {string} UTC Date
 */
module.exports = function() {
  return Date.UTC()
}

On your client will received what wrap inside the data property.

If you throw an error inside your function call:

/**
 * @return {object} just throw
 */
module.exports = function() {
  throw new Error("I don't know")
}

This will get catch and wrap inside the result.error field. And it will get throw on the client. But you will always received a status 200. Because this is application level error. Nothing todo with the transport.

Query can have as many arguments as you want.

/**
 * @param {number} a prop
 * @param {number} b prop
 * @param {number} c prop
 * @param {number} [d=10] prop default 10
 * @return {number} sum of them
 */
module.exports = function(a, b, c, d=10) {
  return a + b + c + d;
}

TBC: How to get the userdata


We have one built in query call helloWorld, it return Hello world!. It will always be available. And even if you use auth:true option. You do not need to login before you can access it. This is handy to ping the server and see if your set is correct. Especially when you are writing test for your application.

Mutation

The different between query and mutation is, mutation has fixed number of arguments

/**
 * For the payload it will be a bit more tricky to write jsdoc
 * @param {object} payload - the submit payload
 * @param {string} payload.username user name
 * @param {object} conditions where cause
 * @param {number} conditions.user_id to id the user
 * @return {boolean} true on success
 */
module.exports = function(payload, conditions) {
  // your code
}

Auth

There are only two authorisation methods

  1. login - basically what you do to login
  2. validator - every time the client request your API, this method will get call and validator against the token in the header.
  3. logout - it does what it said on the tin.

How you implement the login and validation, it's entirely up to you as a developer. And issuer is just a type of query. It has all the signature just like a normal query. The only different is, it will never received any additional parameter.

The naming is important, you must name your function file as /projects/resolvers/auth/issuer.js and /projects/resolvers/auth/validator.js. Any other location or file name will not be found, and middleware will throw error.

socket

This require out other module jsonql-ws-server to setup. More coming soon.

Contract

BREAKING CHANAGE

We will start using the jsdoc to capture your parameter and returns type. Therefore from V.1 release onward it will be REQUIRED to write correct jsdoc. This is important because the client side will be expecting correct type information for the validation to work. Also this solve a problem of unable to pass default parameter pass to the resolver.


Contracts are generate automatically when you start up your application. We have a separate tool jsonql-contract to generate the contract file (it just a json file contain information about your applications). For internal user, the file is call contract.json it will located in the contractDir you specify when you config the middleware, default location will be ~/project/jsonql/contracts.

There is another contract that is for your client to consume, which is name public-contract.json. It has the same structure but without some of the sensitive information, such as the file field (where your code located) and the validator field.

If you are not using auth:true option, then even if you implement the auth/login.js method, it will get remove as well.

Also whenever the client request the contract. It will look for additional json file in the same folder.

For instance, your NODE_ENV=development therefore, it will search for ~/project/jsonql/contracts/development.json and this will get merge with the stock public-contract.json. Therefore you can overwrite information (but not remove) or add additional information, such as change the return field, and write a auto validator based on the result you received.

Also you can set a password to avoid unwanted access to your contract. Just pass the contractKey:[password] to the config object.


Main website: jsonql.org

MIT (c) 2019 to1source China in collaboration with NEWBRAN LTD UK