Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/rxdi/graphqj

Create Graphql server from json, yml, ts, js, graphql and more advanced Schema definitions
https://github.com/rxdi/graphqj

Last synced: about 1 month ago
JSON representation

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

Awesome Lists containing this project

README

        

# @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](https://github.com/Stradivario/gapi)

## Installation

```bash
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

```json
{
"$mode": "basic",
"$resolvers": {
"findUser": {
"name": "Kristiyan Tachev",
"email": "[email protected]",
"phone": 414141,
"arrayOfNumbers": [515151, 412414],
"arrayOfStrings": ["515151", "412414"]
}
}
}
```

Advanced configuration

```json
{
"$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": "[email protected]",
"phone": 414141,
"arrayOfNumbers": [515151, 412414],
"arrayOfStrings": ["515151", "412414"]
}
},
"findUserWithPayloadRequired": {
"type": "User",
"args": {
"payload":"UserPayload!",
},
"resolve": {
"name": "Kristiyan Tachev",
"email": "[email protected]",
"phone": 414141,
"arrayOfNumbers": [515151, 412414],
"arrayOfStrings": ["515151", "412414"]
}
},
}
}
```

Schema:

```graphql
type Query {
findUser: User
status: StatusQueryType
}

type StatusQueryType {
status: String
}

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

Query:
```graphql
query {
findUser {
name
email
phone
arrayOfNumbers
arrayOfStrings
}
}
```

Result:
```json
{
"data": {
"findUser": {
"name": "Kristiyan Tachev",
"email": "[email protected]",
"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

```bash
npm i -g @gapi/cli
```

Filename: `gj.ts`
```typescript
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: '[email protected]',
phone: 4141423,
arrayOfNumbers: [515151, 412414],
arrayOfStrings: ['515151', '412414']
})
}
}
};

```

### ES6

Filename: `gj.js`
```typescript
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: '[email protected]',
phone: 4141423,
arrayOfNumbers: [515151, 412414],
arrayOfStrings: ['515151', '412414']
})
}
}
};

```

### YML

Filename: `gj.yml`
```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: [email protected]
phone: 414141
arrayOfNumbers:
- 515151
- 412414
arrayOfStrings:
- '515151'
- '412414'

findUser2:
type: User
args:
payload: UserPayload
resolve:
name: Kristiyan Tachev
email: [email protected]
phone: 414141
arrayOfNumbers:
- 515152
- 412414
arrayOfStrings:
- '515151'
- '412414'
```

### Loading existing generated schema

Filename: `gj.json`
```json
{
"$schema": "./schema.graphql"
}
```

Or

```bash
gj --schema ./schema.graphql
```

### [Graphql Voyager](https://github.com/Stradivario/gapi-voyager)

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 📡

```yml
$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: [email protected]
phone: 414141
arrayOfNumbers:
- 515151
- 412414
arrayOfStrings:
- '515151'
- '412414'
$views:
home:
query: findUser
props: User
output: UserPayload
html: |

{userId} {name} {email} {phone} {createdAt}
A rich framework for building applications and services with GraphQL and Apollo inspired by Angular
```

Moment helper

```typescript
import moment from 'moment';

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

Chaining multiple `$externals` is quite easy

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

#### Magics

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

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

You can compose anything inside `gj.yml`

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

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

#### Resolver

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

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

#### Guard

```typescript
import { Observable } from 'rxjs';

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

#### Interceptor

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

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

#### Modifier

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

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

#### Directives

```typescript
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

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

#### Omg YML
Defining `javascript` function in `yml`

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

Defining JS function in resolver

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

Possible flows

```yml
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

```json
{
"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:
```yml
$externals:
- map: 🕵️
file: ./my-functions.js
```

Where `./my-functions.js` looks like this

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

Then you can inject these functions and use them

```yml
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": "[email protected]",
"phone": 414141,
"arrayOfNumbers": [515151, 412414],
"arrayOfStrings": ['515151', '412414']
}
}

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

# Possible view configuration

```yml
$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": "[email protected]",
"phone": 876667537
}
}

$views:
app:
components:
html: |

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

home:
query: |
query findUser {
findUser {
name
email
phone
arrayOfStrings
}
}
output: UserPayload
policy: network-only
html: |
Welcome to Home component

Name: {findUser.name}


Email: {findUser.email}


Phone: {findUser.phone}


{findUser.arrayOfStrings}



{{ x }}





not-found:
html: |
Not found
gosho:
query: findUser
html: |
Welcome to Gosho

Name: {findUser.name}


Email: {findUser.email}


Phone: {findUser.phone}

dadada:
html: |
Welcome to Dadada

```