Create easy Graphql server from `json`, `yml`, `graphql`, `js` or `ts` files
npm install @rxdi/graphqjCreate easy Graphql server from json, yml, graphql, js or ts files
- 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.
- Tool for creating Graphql backend from Different source for testing purposes and Client MVP's
- A Production ready server (it is created only for MVP's)
> For production ready server check @gapi/core
``bash`
npm i -g @rxdi/graphqj
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": "test@gmail.com",
"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": "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:
`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": "test@gmail.com",
"phone": 414141,
"arrayOfNumbers": [
515151,
412414
],
"arrayOfStrings": [
"515151",
"412414"
]
}
}
}
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
To be able to run config with typescript you need to install @gapi/cli globallytypescript
This will transpile our 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: 'test@gmail.com',
phone: 4141423,
arrayOfNumbers: [515151, 412414],
arrayOfStrings: ['515151', '412414']
})
}
}
};
`
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: 'test@gmail.com',
phone: 4141423,
arrayOfNumbers: [515151, 412414],
arrayOfStrings: ['515151', '412414']
})
}
}
};
`
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: 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'
`
Filename: gj.json`json`
{
"$schema": "./schema.graphql"
}
Or
`bash`
gj --schema ./schema.graphql
Open http://localhost:9000/voyager
graphqj, gg, gj
Exclude .gj folder inside your .gitignore or .dockerignore files
Folder .gj is working directory when we store transpiled typescript configuration file
`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: test@gmail.com
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": "test@gmail.com",
"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": "test@gmail.com",
"phone": 414141,
"arrayOfNumbers": [515151, 412414],
"arrayOfStrings": ['515151', '412414']
}
}
findUser2: 💉./resolvers/findUser.resolver.yml
`
`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": "kristiqn.tachev@gmail.com",
"phone": 876667537
}
}
$views:
app:
components:
html: |
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}
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
``