All Projects → spikenail → Spikenail

spikenail / Spikenail

Licence: other
A GraphQL Framework for Node.js

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to Spikenail

Grial
A Node.js framework for creating GraphQL API servers easily and without a lot of boilerplate.
Stars: ✭ 194 (-45.81%)
Mutual labels:  graphql, graphql-server, graphql-api, framework, nodejs-framework
Api Platform
Create REST and GraphQL APIs, scaffold Jamstack webapps, stream changes in real-time.
Stars: ✭ 7,144 (+1895.53%)
Mutual labels:  graphql, graphql-server, graphql-api, framework
Kepler
The open source full-stack geosocial network platform
Stars: ✭ 125 (-65.08%)
Mutual labels:  framework, nodejs-framework, real-time, realtime
Wp Graphql
🚀 GraphQL API for WordPress
Stars: ✭ 3,097 (+765.08%)
Mutual labels:  graphql, graphql-server, graphql-api
Server
Framework NodeJS for GraphQl
Stars: ✭ 118 (-67.04%)
Mutual labels:  graphql, graphql-server, framework
Aws Mobile Appsync Events Starter React Native
GraphQL starter application with Realtime and Offline functionality using AWS AppSync
Stars: ✭ 134 (-62.57%)
Mutual labels:  graphql, real-time, realtime
Orionjs
A new framework for serverside GraphQL apps
Stars: ✭ 35 (-90.22%)
Mutual labels:  graphql, graphql-server, framework
Storefront Api
Storefront GraphQL API Gateway. Modular architecture. ElasticSearch included. Works great with Magento1, Magento2, Spree, OpenCart, Pimcore and custom backends
Stars: ✭ 180 (-49.72%)
Mutual labels:  graphql, graphql-server, graphql-api
Hangzhou Graphql Party
杭州 GraphQLParty 往期记录(slide,照片,预告,视频等)
Stars: ✭ 142 (-60.34%)
Mutual labels:  graphql, graphql-server, graphql-api
Graphql2rest
GraphQL to REST converter: automatically generate a RESTful API from your existing GraphQL API
Stars: ✭ 181 (-49.44%)
Mutual labels:  graphql, graphql-server, graphql-api
Graphql Spqr Spring Boot Starter
Spring Boot 2 starter powered by GraphQL SPQR
Stars: ✭ 187 (-47.77%)
Mutual labels:  graphql, graphql-server, graphql-api
Rails Devise Graphql
A Rails 6 boilerplate to create your next Saas product. Preloaded with graphQL, devise, JWT, CanCanCan, RailsAdmin, Rubocop, Rspec, i18n and more.
Stars: ✭ 199 (-44.41%)
Mutual labels:  graphql, graphql-server, graphql-api
Graphql Stack
A visual explanation of how the various tools in the GraphQL ecosystem fit together.
Stars: ✭ 117 (-67.32%)
Mutual labels:  graphql, graphql-server, graphql-api
Daptin
Daptin - Backend As A Service - GraphQL/JSON-API Headless CMS
Stars: ✭ 1,195 (+233.8%)
Mutual labels:  graphql, graphql-server, graphql-api
Morpheus Graphql
Haskell GraphQL Api, Client and Tools
Stars: ✭ 285 (-20.39%)
Mutual labels:  graphql, graphql-server, graphql-api
Graphql
Haskell GraphQL implementation
Stars: ✭ 36 (-89.94%)
Mutual labels:  graphql, graphql-server, graphql-api
Pop
Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder
Stars: ✭ 160 (-55.31%)
Mutual labels:  graphql, graphql-server, graphql-api
Graphqlize
A Clojure & JVM library for developing GraphQL API instantly from Postgres and MySQL databases
Stars: ✭ 240 (-32.96%)
Mutual labels:  graphql, graphql-server, graphql-api
Graphql Spqr
Java 8+ API for rapid development of GraphQL services
Stars: ✭ 843 (+135.47%)
Mutual labels:  graphql, graphql-server, graphql-api
Turbulette
😴 Turbulette - A batteries-included framework to build high performance, fully async GraphQL APIs
Stars: ✭ 29 (-91.9%)
Mutual labels:  graphql, graphql-server, framework

Spikenail is an open-source Node.js ES7 framework which allows you to build GraphQL API with little or no coding.

NPM Version Downloads Dependency Status License Donate

Features

Full support of ES7 features

Native GraphQL support

Real-Time: GraphQL Subscriptions

Relay compatible API

Easy to define access control of any complexity: nested relations, scopes, custom dynamic roles

Advanced schema definition: virtual fields, custom resolvers

Validations

Flexibility: easy to adjust or override every part of a framework

Examples

Creating Trello-like API: https://medium.com/@igor3489_46897/creating-advanced-graphql-api-quickly-using-spikenail-80ce6fd675ab

Install

npm install -g generator-spikenail
yo spikenail

Core concepts

An ability to build the API just by configuring is the main idea of Spikenail. This configuration might include relations, access control, validations and everything else we need.

At the same time, we should provide enough flexibility by allowing to adjust or override every action Spikenail does. From this point of view, Spikenail provides an architecture and a default implementation of it.

The configuration mentioned above stored in models.

Example of the model models/Item.js:

import { MongoDBModel } from 'spikenail';

class Item extends MongoDBModel {

  /**
   * Example of a custom method
   */
  customMethod() {
    // Access an underlying mongoose model
    return this.model.find({ 'category': 'test' }).limit(10);
  }
}

export default new Item({
  name: 'item',
  properties: {
    id: {
      type: 'id'
    },
    name: {
      type: String
    },
    description: {
      type: String
    },
    position: {
      type: Number
    },
    token: {
      type: String
    },
    virtualField: {
      virtual: true,
      // Ensure dependent fields to be queried from the database
      dependsOn: ['position'],
      type: String
    },
    userId: {
      type: 'id'
    },
    // Relations
    subItems: {
      relation: 'hasMany',
      ref: 'subItem',
      foreignKey: 'itemId'
    },
    user: {
      relation: 'belongsTo',
      ref: 'user',
      foreignKey: 'userId'
    }
  },
  // Custom resolvers
  resolvers: {
    description: async function(_, args) {
      // It is possible to do some async actions here
      let asyncActionResult = await someAsyncAction();
      return asyncActionResult ? _.description : null;
    },
    virtualField: (_, args) => {
      return 'justCustomModification' + _.position
    }
  },
  validations: [{
    field: 'name',
    assert: 'required'
  }, {
    field: 'name',
    assert: 'maxLength',
    max: 100
  }, {
    field: 'description',
    assert: 'required'
  }],
  acls: [{
    allow: false,
    properties: ['token'],
    actions: '*'
  }, {
    allow: true,
    properties: ['token'],
    actions: ['create']
  }]
});

CRUD

In Spikenail every CRUD action is a set of middlewares. These middlewares are not the request middlewares and they exists separately.

Some of default middlewares are:

  • Access control middleware
  • Validation middleware
  • Before action
  • Process action
  • After action

The whole chain can be changed in any way.

For example, you can override "Before action" middleware in a following way:

models/Item.js

  async beforeCreate(result, next, opts, input, ctx) {
    let checkResult = await someAsyncCall();

    if (checkResult) {
        return next();
    }

    result.errors = [{
        message: 'Custom error',
        code: '40321'
    }];
  }

Configuration

Configuration files are stored under config folder

Data sources

Currently, only MongoDB is supported.

It is recommended to store all configurations using environment variables

Example of config/sources.js

export default {
  'default': {
    adapter: 'mongo',
    connectionString: process.env.SPIKENAIL_MONGO_CONNECTION_STRING
  }
}

GraphQL API

Queries

node

node(id: ID!): Node

https://facebook.github.io/relay/docs/graphql-object-identification.html#content

Example:

{
    node(id: "some-id") {
        id,
        ... on Article {
            title,
            text
        }
    }
}

viewer

Root field

viewer: viewer

type viewer implements Node {
  id: ID!
  user: User,
  allXs(): viewer_XConnection
}

Query all items of a specific model (allXs)

For Article model:

query {
    viewer {
        allArticles() {
            edges {
            node {
                id,
                title,
                text
                }
            }
        }
    }
}

Query single item (getX)

Query a specific item by unique field:

query {
    getArticle(id: "article-id-1") {
        id, title, text
    }
}

Pagination

Example:

{
    getArticle(id: "some-id") {
        id
        userId
        user {
            id
            name
        }
        tags(first: 10, after: "opaqueCursor") {
            edges {
                node {
                    id
                    name
                    itemsCount
                }
            }
            pageInfo {
                hasNextPage
                hasPreviousPage
                endCursor
                startCursor
            }
        }
    }
}

See relay documentation for more details: https://facebook.github.io/relay/graphql/connections.htm

Filtering and sorting

Example:

query {
  viewer {
    allBoards(filter: { where: { name: { regexp: "^Public" } }, order: "id DESC" }) {
      edges {
        node {
          id
          userId
          name
        }
      }
    }
  }
}

Mutations

createX
mutation createX(input: CreatexInput): CreatexPayload

Example:

mutation {
  createItem(input: { name: "New item", clientMutationId: "123" }) {
    item {
      id
      name
    }
    clientMutationId
    errors {
      message
      code
    }
  }
}
updateX
mutation updateX(input: UpdatexInput): UpdatexPayload

Example:

mutation {
  updateItem(input: { name: "New item name", clientMutationId: "123" }) {
    item {
      id
      name
    }
    clientMutationId
    errors {
      message
      code
    }
  }
}
removeX
mutation removeX(input: RemovexInput): RemovexPayload

Example:

mutation {
  removeItem(input: { id: "Ym9hcmQ6NTkyYmZjOTA2ZjM5Zjc5MGNmNGI5Yjhh" }) {
    removedId
    errors {
      code
      message
    }
  }
}

Subscriptions

First of all, you need to install a needed PubSub adapter:

npm install --save spikenail-pubsub-redis

Then, create a config/pubsub.js file to enable subscriptions:

export default {
  pubsub: {
    adapter: 'redis'
  }
}

When the server is started, you can go to the http://localhost:5000/graphiql to open in-browser IDE which supports GraphQL subscriptions.

Default WebSocket endpoint is ws://localhost:8000/graphql

WebSockets authentication

It’s not possible to provide custom headers when creating WebSocket connection in browser. You to pass auth_token as query parameter, e.g. ws://localhost:8000/graphql?auth_token=igor-secret-token

subscribeToX

Examples:

Subscribe to all Items:

subscription {
  subscribeToItem {
    mutation
    node {
      id
      name
      user {
        id
        name
      }
      nesteditems {
        edges {
          node {
            id
            name
          }
        }
      }
    }
    previousValues {
      id
    }
  }
}

Subscribe to only particular item changes:

subscription {
  subscribeToItem(filter: { where: { id: "Ym9hcmQ6NTkyYmZjOTA2ZjM5Zjc5MGNmNGI5Yjg4" } }) {
    mutation
    node {
      id
      name
      user {
        id
        name
      }
      nesteditems {
        edges {
          node {
            id
            name
          }
        }
      }
    }
  }
}

Subscribe to all Books in specified Category:

subscription {
  subscribeToBook(filter: { where: { categoryId: "Ym9hcmQ6NTkyYmZjOTA2ZjM5Zjc5MGNmNGI5Yjg4" } }) {
    mutation
    node {
      id
      title
      author {
        id
        name
      }
    }
  }
}

Defining a Model

Using model generator

You can use model generator in order to simplify model creation:

yo spikenail:model board

This will create models/Board.js file with only id field:

import { MongoDBModel } from 'spikenail';

class Board extends MongoDBModel {}

export default new Board({
  name: 'Board',
  properties: {
    id: {
      type: 'id'
    }
  }
});

Relations

hasMany relation

models/Book.js

properties: {
    authors: {
      relation: 'hasMany',
      ref: 'author',
      foreignKey: 'bookId'
    }
}

authors definition could be simplified:

authors: {
  relation: 'hasMany'
}

In this case framework will try to guess other parameters.

Custom hasMany condition
 getCondition: function(_) {
    let names = _.map(i => i.name);
    return { otherModelField: { '$in': names } }
 }

belongsTo relation

list: {
    relation: 'belongsTo'
    ref: 'list',
    foreignKey: 'listId'
}

Simplified definition:

list: {
    relation: 'belongsTo'
}

MongoDBModel

Underlying model is a mongoose model. You can access it through this.model

Changing collection name
providerOptions: {
    collection: 'customName'
}

Authentication

Simple token authentication middleware

Spikenail has built-in middleware for the authentication.

It looks for tokens array stored in User model in a following format:

[{
    token: "user-random-token"
}, {
    token: "user-random-token-2"
}]

The current user will be placed in context and accessible through ctx.currentUser

ACL

Introduction

ACL rules are specified under the acls property of the model schema. Rules are processed by framework one by one in a natural order. There is no any access restrictions by default.

Take a look at a below example:

acls: [{
    allow: false,
    roles: ['*'],
    actions: ['*']
}, {
    allow: true,
    roles: ['*'],
    actions: ['*'],
    scope: function() {
        return { isPublic: true }
    }
}

The first rule here is disable everything for everyone:

{
    allow: false,
    roles: ['*'],
    actions: ['*']
}

The second rule allows everything if isPublic property of a item equals true.

Rules notation could be simplified and above rules might be written as:

acls: [{
    allow: false
}, {
    allow: true
    scope: function() {
        return { isPublic: true }
    }
}

Rule structure

allow

Each rule must have the allow property defined. allow is a boolean value that indicates if a rule allows something or disallows.

Example:

allow: true

properties (optional)

properties is an array of properties of a model that rule should apply to. Omit or use * sign to apply to all rules.

actions (optional)

Specify what actions rule should be applied to. There are 4 types of actions:

  • create
  • update
  • remove
  • read

Omit this property or use * sign to apply to all actions.

Example:

actions: ['create', 'update']

scope

Scope is a MongoDB condition. Rule will be applied only to those documents that match the scope.

Example

{ isPublic: true }

The rule will be applied only to documents that have isPublic property equals true.

Scope can be defined as a function. In this case you have an access to the context variable:

scope: function(ctx) {
    return { isPublic: true }
}

roles

roles is an array of roles that rule should apply to.

Example

roles: ['anonymous', 'member']

Roles might be static or dynamic.

Static roles

Static roles are roles that not depend on a particular document or a data set. They are calculated once per a request for a current user.

Built-in static roles are:

  • anonymous
  • user
Adding your own static roles

Override the getStaticRoles function of the model.

Dynamic roles

Dynamic roles are calculated for each particular document. For example, role owner means that currentUser.id === fetchedDocument.id

Built-in dynamic roles are:

  • owner
Defining dynamic roles

Dynamic roles are defined using roles object of the model schema.

For example, we have members array where sharing information stored in a following format:

[{
    userId: 123
    role: 'member'
}, {
    userId: 456,
    role: 'observer'
}]

Then we can define a role member in the model schema:

roles: {
 member: {
   cond: function(ctx) {
     return { 'members': { '$elemMatch': { 'userId': ctx.currentUser.id, role: 'member' } } }
   }
 }
}

And use it in the roles property of ACL rule:

roles: ['member']

Access based on another model

In some cases we want to apply rule only if another model satisfies some condition. We can use the checkRelation property for that.

checkRelation

Example:

Article.js model has defined belongsTo relation

blog: {
    relation: 'belongsTo'
}

We want allow for user to read an article only if he can read the blog it belongs to:

acls: [{
    allow: false
}, {
    allow: true,
    roles: ['user'],
    actions: ['read'],
    checkRelation: {
        name: 'blog',
        action: 'read'
    }
}]

If checkRelation condition is not satisfied, the rule will not be applied at all. It means that allow: true will not become allow: false and vice versa. Rule will be filtered out.

Validations

Usually the data that we receive from users needs to be validated. It is easy to do with Spikenail. For example, we want name to be required property and its length to not exceed 50 characters.  This could be done in following way:

models/Item.js

validations: [{
  field: 'name',
  assert: 'required'
}, {
  field: 'name',
  assert: 'maxLength',
  max: 50
}]

Future plans

SQL databases support

Simple endpoint (non-relay)

Support

Donate

License

MIT © Igor Lesnenko

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].