Skip to content

Commit

Permalink
fix(beta): First beta
Browse files Browse the repository at this point in the history
GraphQL Middleware
  • Loading branch information
maticzav committed Apr 20, 2018
2 parents ba33823 + cf8d6f7 commit 912d2a5
Show file tree
Hide file tree
Showing 19 changed files with 6,992 additions and 271 deletions.
14 changes: 14 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
jobs:
build:
docker:
- image: 'circleci/node:latest'
steps:
- checkout
- run:
name: install
command: |
sudo npm install -g yarn
yarn install
- run: yarn test
- run: yarn semantic-release
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ node_modules
dist
.idea
.DS_Store
*.log*
package-lock.json
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": false,
"trailingComma": "all",
"singleQuote": true
}
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "AVA tests debug",
"program": "${workspaceFolder}/node_modules/ava/profile.js",
"args": ["--serial", "${file}"],
"skipFiles": ["<node_internals>/**/*.js"]
}
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"eslint.enable": false
}
158 changes: 68 additions & 90 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,86 +1,37 @@
# graphql-middleware

> NOTE: This repo is still WIP
<p align="center"><img src="media/logo.svg" width="150" /></p>

## What's `graphql-middleware`?
# graphql-middleware

* Middlewares can be wrapped (based on provided order)
[![CircleCI](https://circleci.com/gh/graphcool/graphql-middleware.svg?style=shield)](https://circleci.com/gh/graphcool/graphql-middleware)
[![npm version](https://badge.fury.io/js/graphql-middleware.svg)](https://badge.fury.io/js/graphql-middleware)

### What it does
All in one solution to manage middleware in your GraphQL projects.

* Value transformation
* Override arguments
* Error handling (throw & catch errors)
* Globbing syntax
## Overview

### What is doesn't
GraphQL Middleware is a schema wrapper which allows you to manage additional functionality across multiple resolvers efficiently.

* Does **not** change the exposed GraphQL schema
* **Easiest way to handle GraphQL middleware:** Intuitive, yet familiar API will get under your skin in a second.
* **Powerful:** Allows complete control over your resolvers (Before, After).
* **Compatible:** Works with any GraphQL Schema.

## Install

```sh
yarn add graphql-middleware
```

## API

### Field middleware

A field middleware is a resolver function that wraps another resolver function

```ts
type IFieldMiddlewareFunction = (
resolve: Function,
parent: any,
args: any,
context: any,
info: GraphQLResolveInfo,
) => Promise<any>

interface IFieldMiddlewareTypeMap {
[key: string]: IFieldMiddlewareFunction | IFieldMiddlewareFieldMap
}

interface IFieldMiddlewareFieldMap {
[key: string]: IFieldMiddlewareFunction
}

type IFieldMiddleware = IFieldMiddlewareFunction | IFieldMiddlewareTypeMap

function applyFieldMiddleware(schema: GraphQLSchema, ...middlewares: IFieldMiddleware[]): GraphQLSchema
```

### Document middleware

```ts
interface GraphQLResponse {
data: any
errors?: any[]
extensions?: any
}
type IDocumentMiddlewareFunction = (
execute: Function,
rootValue: any,
context: any,
info: GraphQLResolveInfo,
): Promise<GraphQLResponse>
function applyDocumentMiddleware(schema: GraphQLSchema, ...middlewares: IDocumentMiddlewareFunction[]): GraphQLSchema
```

## Examples
## Usage

```ts
import { applyFieldMiddleware } from 'graphql-middleware'
import { applyMiddleware } from 'graphql-middleware'
import { makeExecutableSchema } from 'graphql-tools'
import { authMiddleware, metricsMiddleware } from './middlewares'

// Minimal example middleware (before & after)
const beepMiddleware = {
Query: {
hello: (resolve, parent, args, context, info) => {
hello: async (resolve, parent, args, context, info) => {
// You can you middlewares to override arguments
const argsWithDefault = { name: 'Bob', ...args }
const result = await resolve(parent, argsWithDefault, context, info)
Expand All @@ -90,15 +41,6 @@ const beepMiddleware = {
},
}

const responseSizeMiddleware = async (execute, rootValue, context, info) => {
const response = await execute(rootValue, context, info)
if (count(response) > 1000) {
throw new Error('Response too big')
}
return response
}
const typeDefs = `
type Query {
hello(name: String): String
Expand All @@ -112,17 +54,21 @@ const resolvers = {

const schema = makeExecutableSchema({ typeDefs, resolvers })

const schemaWithFieldMiddlewares = applyFieldMiddleware(schema, metricsMiddleware, authMiddleware, beepMiddleware)
const schemaWithDocumentMiddlewares = applyDocumentMiddleware(schemaWithFieldMiddlewares, responseSizeMiddleware)
const schemaWithMiddleware = applyMiddleware(
schema,
metricsMiddleware,
authMiddleware,
beepMiddleware,
)
```

### Usage with `graphql-yoga`

`graphql-yoga` has built-in support for `graphql-middleware`
> `graphql-yoga` has built-in support for `graphql-middleware`!
```ts
import { GraphQLServer } from 'graphql-yoga'
import { authMiddleware, metricsMiddleware, responseSizeMiddleware } from './middlewares'
import { authMiddleware, metricsMiddleware } from './middlewares'

const typeDefs = `
type Query {
Expand All @@ -138,37 +84,69 @@ const resolvers = {
const server = new GraphQLServer({
typeDefs,
resolvers,
fieldMiddlewares: [authMiddleware, metricsMiddleware],
documentMiddlewares: [responseSizeMiddleware],
fieldMiddleware: [authMiddleware, metricsMiddleware],
documentMiddleware: [],
})
server.start(() => console.log('Server is running on localhost:4000'))
```

## Terminology
### Examples

## API

A middleware is a resolver function that wraps another resolver function.

```ts
type IMiddlewareFunction = (
resolve: Function,
parent: any,
args: any,
context: any,
info: GraphQLResolveInfo,
) => Promise<any>

interface IMiddlewareTypeMap {
[key: string]: IMiddlewareFunction | IMiddlewareFieldMap
}

interface IMiddlewareFieldMap {
[key: string]: IMiddlewareFunction
}

* Core resolver
type IMiddleware = IMiddlewareFunction | IMiddlewareTypeMap

## Middleware Use Cases
function applyMiddleware(
schema: GraphQLSchema,
...middlewares: IMiddleware[]
): GraphQLSchema
```

### Field level
## GraphQL Middleware Use Cases

* Logging
* Metrics
* Input sanitzation
* Input sanitisation
* Performance measurement
* Authorization (`graphql-shield`)
* Authorization
* Caching
* Tracing

### Document level
## FAQ

### Can I use GraphQL Middleware without GraphQL Yoga?

Yes. Nevertheless, we encourage you to use it in combination with Yoga. Combining the power of `fieldMiddleware` that GraphQL Middleware offers, with `documentMiddleware` which Yoga exposes, gives you unparalleled control over the execution of your queries.

### How does GraphQL Middleware compare to `directives`?

GraphQL Middleware and `directives` tackle the same problem in a completely different way. GraphQL Middleware allows you to implement all your middleware logic in your code, whereas directives encourage you to mix schema with your functionality.

* Complexity analysis
### Should I modify the context using GraphQL Middleware?

## Open questions
GraphQL Middleware allows you to modify the context of your resolvers, but we encourage you to use GraphQL Yoga's `documentMiddleware` for this functionality instead.

* [ ] Allow to transform schema?
* [ ] Anything to consider for subscriptions?
## Help & Community [![Slack Status](https://slack.graph.cool/badge.svg)](https://slack.graph.cool)

## Alternatives
Join our [Slack community](http://slack.graph.cool/) if you run into issues or have questions. We love talking to you!

- Directive resolvers
[![GraphCool](http://i.imgur.com/5RHR6Ku.png)](https://www.graph.cool/)
57 changes: 57 additions & 0 deletions examples/authorization/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const { GraphQLServer } = require('graphql-yoga')
const { makeExecutableSchema } = require('graphql-tools')
const { applyFieldMiddleware } = require('graphql-middleware')

// Agents

const agents = [
{ id: '1', name: 'Matt', code: 'mysecret' },
{ id: '2', name: 'Joh', code: 'cool' },
{ id: '3', name: 'Em', code: 'donttell' },
]

// Schema

const typeDefs = `
type Query {
hello(name: String): String!
agent(mission: String!): String!
}
`

const resolvers = {
Query: {
hello: (_, { name }) => `Hello ${name || 'World'}`,
agent: (_, { mission }) =>
`Top secret information about your mission (${mission})`,
},
}

// Middleware

const authMiddleware = {
Query: {
agent: (resolve, parent, args, ctx, info) => {
// Include your agent code as Authorization: <token> header.
const code = ctx.request.get('Authorization')
const permit = agents.some(agent => agent.code === code)

if (!permit) {
throw new Error(`Not authorised!`)
}
return resolve()
},
},
}

// Server

const schema = makeExecutableSchema({ typeDefs, resolvers })
const securedSchema = applyFieldMiddleware(schema, authMiddleware)

const server = new GraphQLServer({
schema: securedSchema,
context: req => ({ ...req }),
})

server.start(() => console.log('Server is running on localhost:4000'))
11 changes: 11 additions & 0 deletions examples/authorization/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "authorization",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"graphql-middleware": "latest",
"graphql-tools": "^2.23.1",
"graphql-yoga": "latest"
}
}

0 comments on commit 912d2a5

Please sign in to comment.