Skip to content
/ graphqj Public

Create Graphql server from json, yml, ts, js, graphql and more advanced Schema definitions

License

Notifications You must be signed in to change notification settings

rxdi/graphqj

Repository files navigation

@rxdi/graphqj

Create easy Graphql server from json, yml, graphql, js or ts files

Features

  • Graphql Voyager available
  • Helps with prototyping MVP
  • In about a minute you have working graphql API
  • Graphiql included in the pack for easy development
  • Watch and rebuild GQL Schema dynamically without restarting server
  • It provides HotRealod of Graphql Schema without rebuilding or restarting application.Everyting is happening on Runtime.

What is @rxdi/graphqj

  • Tool for creating Graphql backend from Different source for testing purposes and Client MVP's

What is not @rxdi/graphqj

  • A Production ready server (it is created only for MVP's)

For production ready server check @gapi/core

Installation

npm i -g @rxdi/graphqj

Configuration

Define gj.json or execute gj init,

gj init by default creates basic configuration with json

Available config templates:

gj init {advanced | es6 | typescript | jml}

Basic configuration

{
  "$mode": "basic",
  "$resolvers": {
    "findUser": {
      "name": "Kristiyan Tachev",
      "email": "test@gmail.com",
      "phone": 414141,
      "arrayOfNumbers": [515151, 412414],
      "arrayOfStrings": ["515151", "412414"]
    }
  }
}

Advanced configuration

{
  "$mode": "advanced",
  "$types": {
    "User": {
      "name": "String",
      "email": "String",
      "phone": "number",
      "arrayOfNumbers": "number[]",
      "arrayOfStrings": "string[]"
    }
  },
  "$args": {
    "UserPayload": {
      "userId":"String!",
      "userId2":"String",
      "userId3":"String!",
      "userId4":"String",
    }
  },
  "$resolvers": {
    "findUser": {
      "type": "User",
      "args": {
        "userId":"String!",
        "userId":"String",
      },
      "resolve": {
        "name": "Kristiyan Tachev",
        "email": "test@gmail.com",
        "phone": 414141,
        "arrayOfNumbers": [515151, 412414],
        "arrayOfStrings": ["515151", "412414"]
      }
    },
    "findUserWithPayloadRequired": {
      "type": "User",
      "args": {
        "payload":"UserPayload!",
      },
      "resolve": {
        "name": "Kristiyan Tachev",
        "email": "test@gmail.com",
        "phone": 414141,
        "arrayOfNumbers": [515151, 412414],
        "arrayOfStrings": ["515151", "412414"]
      }
    },
  }
}

Schema:

type Query {
  findUser: User
  status: StatusQueryType
}

type StatusQueryType {
  status: String
}

type User {
  name: String
  email: String
  phone: Int
  arrayOfNumbers: [Int]
}

Query:

query {
  findUser {
    name
    email
    phone
    arrayOfNumbers
    arrayOfStrings
  }
}

Result:

{
  "data": {
    "findUser": {
      "name": "Kristiyan Tachev",
      "email": "test@gmail.com",
      "phone": 414141,
      "arrayOfNumbers": [
        515151,
        412414
      ],
      "arrayOfStrings": [
        "515151",
        "412414"
      ]
    }
  }
}

Starting server

This command will look for gj.{json | js | ts | yml} configuration inside working directory

gj

Changing port

Default port is 9000

gj --port 5000

Hot reload of Bundles (Beta)

gj --hot-reload

Build client side application inside Configuration file (Beta)

gj --client

Generating schema.graphql from JSON

gj --generate

Spawn random PORT on every start

gj --random

Try experimental HOT Module reload when developing client side application

gj --client --hot-reload

Advanced configuration

Typescript

To be able to run config with typescript you need to install @gapi/cli globally This will transpile our typescript file to javascript and load it automatically

npm i -g @gapi/cli

Filename: gj.ts

export default {
  $mode: 'advanced',
  $types: {
    user: {
      name: 'String',
      email: 'String',
      phone: 'Number',
      arrayOfNumbers: 'Number[]',
      arrayOfStrings: 'String[]'
    }
  },
  $resolvers: {
    findUser: {
      type: 'user',
      args: {
        userId: "String!",
        userId2: "String",
      },
      resolve: async (root, payload: { userId: string; userId2?: string }) => ({
        name: 'Kristiyan Tachev',
        email: 'test@gmail.com',
        phone: 4141423,
        arrayOfNumbers: [515151, 412414],
        arrayOfStrings: ['515151', '412414']
      })
    }
  }
};

ES6

Filename: gj.js

export default {
  $mode: 'advanced',
  $types: {
    user: {
      name: 'String',
      email: 'String',
      phone: 'Number',
      arrayOfNumbers: 'Number[]',
      arrayOfStrings: 'String[]'
    }
  },
  $resolvers: {
    findUser: {
      type: 'user',
      args: {
        userId: "String!",
        userId2: "String",
      },
      resolve: async (root, payload: { userId: string; userId2?: string }) => ({
        name: 'Kristiyan Tachev',
        email: 'test@gmail.com',
        phone: 4141423,
        arrayOfNumbers: [515151, 412414],
        arrayOfStrings: ['515151', '412414']
      })
    }
  }
};

YML

Filename: gj.yml

$mode: advanced
$types:
  User:
    name: String
    email: String
    phone: Number
    arrayOfNumbers: Number[]
    arrayOfStrings: String[]

$args:
  UserPayload:
    userId: String!
    userId2: String
    userId3: String
    userId4: String

$resolvers:
  findUser:
    type: User
    args:
      payload: UserPayload
    resolve:
      name: Kristiyan Tachev
      email: test@gmail.com
      phone: 414141
      arrayOfNumbers: 
        - 515151
        - 412414
      arrayOfStrings:
        - '515151'
        - '412414'

  findUser2:
    type: User
    args:
      payload: UserPayload
    resolve:
      name: Kristiyan Tachev
      email: test@gmail.com
      phone: 414141
      arrayOfNumbers: 
        - 515152
        - 412414
      arrayOfStrings:
        - '515151'
        - '412414'

Loading existing generated schema

Filename: gj.json

{
  "$schema": "./schema.graphql"
}

Or

gj --schema ./schema.graphql

Open http://localhost:9000/voyager

Aliases

graphqj, gg, gj

Exclude

Exclude .gj folder inside your .gitignore or .dockerignore files

Folder .gj is working directory when we store transpiled typescript configuration file

Experimental 📡

$mode: advanced
$directives: ./directives.ts
$externals:
  - map: 🛰
    file: ./interceptors.ts
  - map: 🛡️
    file: ./guards.ts
  - map: 🕵️
    file: ./modifiers.ts
  - map: 
    file: ./helpers/moment.js

$types:
  User:
    name: String => {🕵️OnlyAdmin}
    email: String => {🛰LoggerInterceptor}
    phone: Number => {🛡️IsLogged}
    arrayOfNumbers: Number[] => {🕵️OnlyAdmin}
    arrayOfStrings: String[]
    createdAt: String => {⌛fromNow}

$args:
  UserPayload:
    userId: String!
    userId2: String
    userId3: String
    userId4: String

$resolvers:
  findUser:
    type: User
    args:
      payload: UserPayload
    resolve:
      name: Kristiyan Tachev
      email: test@gmail.com
      phone: 414141
      arrayOfNumbers:
        - 515151
        - 412414
      arrayOfStrings:
        - '515151'
        - '412414'
$views:
  home:
    query: findUser
    props: User
    output: UserPayload
    html: |
      <bla-component></bla-component>
      {userId} {name} {email} {phone} {createdAt}
      A rich framework for building applications and services with GraphQL and Apollo inspired by Angular

Moment helper

import moment from 'moment';

export function fromNow() {
  return moment('20111031', 'YYYYMMDD').fromNow();
}

Chaining multiple $externals is quite easy

email: String => {🛰LoggerInterceptor} => {🛡️IsLogged} => {🕵️OnlyAdmin}

Magics

With Syringe 💉 operator you can inject yml, js, ts, json, graphql and html files,

$views:
  home:
    query: findUser2
    payload: UserPayload
    html: 💉./my.html

You can compose anything inside gj.yml

$mode: advanced
$directives: ./directives.ts
$externals:
  - map: 🛰
    file: ./interceptors.ts
  - map: 🛡️
    file: ./guards.ts
  - map: 🕵️
    file: ./modifiers.ts

$types: 💉./types.yml
$args: 💉./args.yml
$resolvers: 💉./resolvers.yml

$views:
  home:
    query: findUser2
    payload: UserPayload
    html: 💉./test.html

Even defining Graphql resolvers is simply easy

$mode: advanced
$resolvers:
  findUser:
    type: User
    args:
      payload: UserPayload
    resolve: 💉./findUser.ts

Resolver

import { Observable } from 'rxjs';
import { IUserType } from '@api';

export async function findUser(root, payload, context, info):
  | Promise<Observable<IUserType>>
  | Promise<IUserType>
  | Observable<IUserType>
  | IUserType {
  return {
    name: 'dada',
    email: 'dada',
    phone: 13131,
    arrayOfNumbers: [111, 222],
    arrayOfStrings: ['dada', 'dada']
  };
}

Guard

import { Observable } from 'rxjs';

export async function IsLogged(
  chainable$: Observable<any>,
  root,
  payload,
  context,
  descriptor
) {
  if (!context.user) {
    throw new Error('Unauthorized');
  }
}

Interceptor

import { tap } from 'rxjs/operators';
import { Observable } from 'rxjs';

export async function LoggerInterceptor(
  chainable$: Observable<any>,
  root,
  payload,
  context,
  descriptor
) {
  console.log('Before...');
  const now = Date.now();
  return chainable$.pipe(
    tap(() => console.log(`After... ${Date.now() - now}ms`))
  );
}

Modifier

import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

export async function OnlyAdmin(
  chainable$: Observable<any>,
  root,
  payload,
  context,
  descriptor
) {
  return chainable$.pipe(map(() => null));
}

Directives

import {
  DirectiveLocation,
  GraphQLCustomDirective,
  GraphQLNonNull,
  GraphQLString
} from '@gapi/core';

export async function toUppercase() {
  return new GraphQLCustomDirective({
    name: 'toUpperCase',
    description: 'change the case of a string to uppercase',
    locations: [DirectiveLocation.FIELD],
    resolve: async resolve => (await resolve()).toUpperCase()
  });
}

export async function AddTextDirective() {
  return new GraphQLCustomDirective({
    name: 'AddTextDirective',
    description: 'change the case of a string to uppercase',
    locations: [DirectiveLocation.FIELD],
    args: {
      inside: {
        type: new GraphQLNonNull(GraphQLString),
        description: 'the times to duplicate the string'
      },
      outside: {
        type: new GraphQLNonNull(GraphQLString),
        description: 'the times to duplicate the string'
      }
    },
    resolve: async (
      resolve,
      root,
      args
    ) => args.inside + (await resolve()) + args.outside
  });
}

Possible query

{
  findUser {
    name
    email @toUpperCase @AddTextDirective(inside: "dada", outside: "dadada")
    phone
    arrayOfNumbers
    arrayOfStrings
  }
}

Omg YML

Defining javascript function in yml

$omg: !!js/function >
  function foobar() {
    return 'Wow! JS-YAML Rocks!';
  }

Defining JS function in resolver

$resolvers:
  findUser:
    type: User
    args:
      payload: UserPayload
    resolve: !!js/function >
      function foobar(root, payload, context, info) {
        console.log('OMG')
        return {
          "name": "Kristiyan Tachev",
          "email": "test@gmail.com",
          "phone": 414141,
          "arrayOfNumbers": [515151, 412414],
          "arrayOfStrings": ['515151', '412414']
        }
      }

Possible flows

seq:
  # Ordered sequence of nodes
  Block style: !!seq
  - Mercury   # Rotates - no light/dark sides.
  - Venus     # Deadliest. Aptly named.
  - Earth     # Mostly dirt.
  - Mars      # Seems empty.
  - Jupiter   # The king.
  - Saturn    # Pretty.
  - Uranus    # Where the sun hardly shines.
  - Neptune   # Boring. No rings.
  - Pluto     # You call this a planet?
  Flow style: !!seq [ Mercury, Venus, Earth, Mars,      # Rocks
                      Jupiter, Saturn, Uranus, Neptune, # Gas
                      Pluto ]                           # Overrated

Will create the following json object

{
   "seq":{
      "Block style":[
         "Mercury",
         "Venus",
         "Earth",
         "Mars",
         "Jupiter",
         "Saturn",
         "Uranus",
         "Neptune",
         "Pluto"
      ],
      "Flow style":[
         "Mercury",
         "Venus",
         "Earth",
         "Mars",
         "Jupiter",
         "Saturn",
         "Uranus",
         "Neptune",
         "Pluto"
      ]
   }
}

Dependencies can be injected also

Define inside $externals following:

$externals:
  - map: 🕵️
    file: ./my-functions.js

Where ./my-functions.js looks like this

export async function test() {
  return {};
}
export async function test2() {
  return {};
}
export async function test3() {
  return {};
}

Then you can inject these functions and use them

findUser:
  deps: [{ provide: 🕵️, map: 'myFunctions'}]
  type: User
  args:
    payload: UserPayload
  resolve: !!js/function >
    function foobar(root, payload, context, info) {
      console.log(this.myFunctions.test()) // {}
      console.log(this.myFunctions.test2()) // {}
      console.log(this.myFunctions.test3()) // {}
      return {
        "name": "Kristiyan Tachev",
        "email": "test@gmail.com",
        "phone": 414141,
        "arrayOfNumbers": [515151, 412414],
        "arrayOfStrings": ['515151', '412414']
      }
    }

findUser2: 💉./resolvers/findUser.resolver.yml

Possible view configuration

$mode: advanced
# $imports:
#   - 💉./examples/mix/hamburger/server/hamburger.server.module.ts
# $components:
#   - 💉./examples/mix/hamburger/client/hamburger.client.module.ts

$types:
  User:
    name: String
    email: String
    phone: Number
    arrayOfNumbers: Number[]
    arrayOfStrings: String[]
    arrayOfStrings2: String[]
    users: User[]
$args:
  UserPayload:
    name: String!
    pesho: String

$resolvers:
  findUser:
    type: User
    args:
      userId: UserPayload
    resolve: !!js/function >
      function foobar(root, payload, context, info) {
        return {
          "name": "Кристиян Тачев",
          "arrayOfStrings": ["dada", "dada"],
          "email": "kristiqn.tachev@gmail.com",
          "phone": 876667537
        }
      }

$views:
  app:
    components:
    html: |
      <style>
        .spacer {
          flex: 1 3 auto;
        }
        .container {
          display: flex;
        }
        ul {
          list-style-type: none;
          margin: 0;
          padding: 0;
          overflow: hidden;
          background-color: #f3f3f3;
          cursor: pointer;
        }
        li {
          float: left;
        }
        li a {
          display: block;
          color: #666;
          text-align: center;
          padding: 14px 16px;
          text-decoration: none;
        }
        li a:hover:not(.active) {
          background-color: #ddd;
        }
        li a.active {
          color: white;
          background-color: #4caf50;
        }
        .footer {
          position: fixed;
          left: 0;
          bottom: 0;
          width: 100%;
          background-color: #03a9f4;
          color: white;
          text-align: center;
        }
      </style>
      <ul class="container" slot="header">
        <li><a href="/">Home</a></li>
        <li><a href="/gosho">Gosho</a></li>
        <li><a href="/gosho444">Gosho444</a></li>
        <li><a href="/dadada">1</a></li>
        <li><a href="/dadada">2</a></li>
        <li><a href="/dadada">3</a></li>
        <li><a href="/dadada">4</a></li>
        <li><a href="/dadada">5</a></li>
        <span class="spacer"></span>
      </ul>

      <div class="footer" slot="footer">
        <p>Footer</p>
      </div>

  home:
    query: |
      query findUser {
        findUser {
          name
          email
          phone
          arrayOfStrings
        }
      }
    output: UserPayload
    policy: network-only
    html: |
      Welcome to Home component
      <p>Name: {findUser.name}</p>
      <p>Email: {findUser.email}</p>
      <p>Phone: {findUser.phone}</p>
      {findUser.arrayOfStrings}
      <div>
        <div *let="x" of="findUser.arrayOfStrings">
          <div *template style="background-color: red">
            {{ x }}
            <hamburger-component type="3dx" active="true"></hamburger-component>
          </div>
        </div>
      </div>

      <div style="background-color: red">
        <hamburger-component type="3dx" active=true enableBackendStatistics=${true}></hamburger-component>
      </div>

  not-found:
    html: |
      Not found
  gosho:
    query: findUser
    html: |
      Welcome to Gosho
      <p>Name: {findUser.name}</p>
      <p>Email: {findUser.email}</p>
      <p>Phone: {findUser.phone}</p>

  dadada:
    html: |
      Welcome to Dadada

About

Create Graphql server from json, yml, ts, js, graphql and more advanced Schema definitions

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published