├── examples ├── exampleWithReactClient │ ├── .gitignore │ ├── .babelrc │ ├── dist │ │ └── index.html │ ├── src │ │ ├── index.html │ │ └── app.js │ ├── package.json │ └── index.js ├── logo.png ├── exampleWithRouter │ ├── index.js │ └── router.js ├── exampleWithSubscription.js ├── example.js ├── exampleWithAuth.js └── exampleWithRelations.js ├── .gitignore ├── package.json ├── ops ├── CONTRIBUTING.md ├── __tests__ └── index.js ├── index.js └── README.md /examples/exampleWithReactClient/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .cache -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | package-lock.json 4 | _production-test -------------------------------------------------------------------------------- /examples/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tylerthebuildor/graphqless/HEAD/examples/logo.png -------------------------------------------------------------------------------- /examples/exampleWithReactClient/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["@babel/plugin-proposal-class-properties"] 4 | } 5 | -------------------------------------------------------------------------------- /examples/exampleWithRouter/index.js: -------------------------------------------------------------------------------- 1 | const { GraphQLess } = require('../../index.js'); 2 | const app = new GraphQLess(); 3 | const router = require('./router'); 4 | 5 | app.use(router); 6 | 7 | app.listen(3000, () => { 8 | console.log('Visit: http://localhost:3000/graphql'); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/exampleWithReactClient/dist/index.html: -------------------------------------------------------------------------------- 1 | React Example
-------------------------------------------------------------------------------- /examples/exampleWithReactClient/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | React Example 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphqless", 3 | "version": "1.3.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "files": [ 7 | "index.js", 8 | "CONTRIBUTING.md" 9 | ], 10 | "scripts": { 11 | "test": "node ./node_modules/jest/bin/jest.js" 12 | }, 13 | "dependencies": { 14 | "apollo-server-express": "^2.4.8", 15 | "express": "^4.16.4", 16 | "graphql": "^14.1.1", 17 | "merge-graphql-schemas": "^1.5.8" 18 | }, 19 | "devDependencies": { 20 | "jest": "^24.3.1", 21 | "jsonwebtoken": "^8.5.0", 22 | "nodemon": "^1.18.10" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/exampleWithSubscription.js: -------------------------------------------------------------------------------- 1 | const { GraphQLess, ApolloServerExpress } = require('../index.js'); 2 | const { PubSub } = ApolloServerExpress; 3 | 4 | const app = new GraphQLess(); 5 | const pubsub = new PubSub(); 6 | const COUNT_UP = 'COUNT_UP'; 7 | 8 | let count = 0; 9 | setInterval(() => pubsub.publish(COUNT_UP, { count: count++ }), 1000); 10 | 11 | app.subscription('/count', { 12 | subscribe: () => pubsub.asyncIterator(COUNT_UP), 13 | }); 14 | 15 | app.query('/dummy', (req, res) => { 16 | res.send('Hello'); 17 | }); 18 | 19 | app.useSchema(` 20 | type Subscription { 21 | count: Int 22 | } 23 | type Query { 24 | dummy: String 25 | } 26 | `); 27 | 28 | app.listen(3000, () => { 29 | console.log('Visit: http://localhost:3000/graphql'); 30 | }); 31 | -------------------------------------------------------------------------------- /ops: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # stop script on frist error 4 | 5 | # These commands take this form 6 | # ./ops [script] [option] 7 | 8 | script=${1} 9 | option=${2} 10 | if [ ! -z $script ] ; then shift ; fi 11 | if [ ! -z $option ] ; then shift ; fi 12 | rest="$@" 13 | 14 | if [ -z $script ] ; then 15 | echo "please enter a command after ./ops" 16 | 17 | elif [ $script == "setup" ] ; then 18 | yarn install 19 | brew install nektos/tap/act 20 | 21 | elif [ $script == "setup-bash" ] ; then 22 | yarn install 23 | curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash 24 | 25 | elif [ $script == "test" ] ; then 26 | if [ $option ] && [ $option != "--watchAll" ] ; then 27 | yarn test "__tests__/$option-test.js" $rest 28 | else 29 | yarn test $option $rest 30 | fi 31 | 32 | fi -------------------------------------------------------------------------------- /examples/exampleWithRouter/router.js: -------------------------------------------------------------------------------- 1 | const { Router } = require('../../index.js'); 2 | const router = new Router(); 3 | 4 | const db = { users: [{ name: 'Tyler' }] }; 5 | 6 | router.get('/users', (req, res) => { 7 | const { users } = db; 8 | res.send(users); 9 | }); 10 | 11 | router.get('/user', (req, res) => { 12 | const user = db.users.find(user => user.name === req.body.name); 13 | res.send(user); 14 | }); 15 | 16 | router.post('/createUser', (req, res) => { 17 | const userCount = db.users.push({ name: req.body.name }); 18 | res.send(userCount); 19 | }); 20 | 21 | router.useSchema(` 22 | type Query { 23 | users: [User] 24 | user(name: String): User 25 | } 26 | type Mutation { 27 | createUser(name: String): Int 28 | } 29 | type User { 30 | name: String 31 | } 32 | `); 33 | 34 | module.exports = router; 35 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | const { GraphQLess } = require('../index.js'); 2 | const app = new GraphQLess(); 3 | 4 | const db = { users: [{ name: 'Tyler' }] }; 5 | 6 | app.get('/users', (req, res) => { 7 | const { users } = db; 8 | res.send(users); 9 | }); 10 | 11 | app.get('/user', (req, res) => { 12 | const user = db.users.find(user => user.name === req.body.name); 13 | res.send(user); 14 | }); 15 | 16 | app.post('/createUser', (req, res) => { 17 | const userCount = db.users.push({ name: req.body.name }); 18 | res.send(userCount); 19 | }); 20 | 21 | app.useSchema(` 22 | type Query { 23 | users: [User] 24 | user(name: String): User 25 | } 26 | type Mutation { 27 | createUser(name: String): Int 28 | } 29 | type User { 30 | name: String 31 | } 32 | `); 33 | 34 | app.listen(3000, () => { 35 | console.log('Visit: http://localhost:3000/graphql'); 36 | }); 37 | -------------------------------------------------------------------------------- /examples/exampleWithReactClient/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-with-react-client", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "rm -rf dist && NODE_ENV=development parcel watch src/index.html --no-source-maps", 8 | "build": "rm -rf dist && NODE_ENV=production parcel build src/index.html --no-source-maps --public-url /" 9 | }, 10 | "dependencies": { 11 | "apollo-cache-inmemory": "^1.5.1", 12 | "apollo-client": "^2.5.1", 13 | "apollo-link-http": "^1.5.11", 14 | "graphql-tag": "^2.10.1", 15 | "react": "^16.7.0", 16 | "react-apollo": "^2.4.0", 17 | "react-dom": "^16.7.0" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.0.0-0", 21 | "@babel/plugin-proposal-class-properties": "^7.3.0", 22 | "@babel/preset-env": "^7.3.1", 23 | "@babel/preset-react": "^7.0.0", 24 | "parcel-bundler": "^1.11.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/exampleWithReactClient/index.js: -------------------------------------------------------------------------------- 1 | const { GraphQLess, express } = require('../../index.js'); 2 | const app = new GraphQLess(); 3 | 4 | const db = { 5 | users: [{ name: 'Tyler' }, { name: 'Brett' }, { name: 'Josh' }], 6 | }; 7 | 8 | app.get('/users', (req, res) => { 9 | const { users } = db; 10 | res.send(users); 11 | }); 12 | 13 | app.post('/createUser', (req, res) => { 14 | const user = { name: req.body.name }; 15 | db.users.push(user); 16 | return res.send(user); 17 | }); 18 | 19 | app.useSchema(` 20 | type Query { 21 | users: [User] 22 | } 23 | type Mutation { 24 | createUser(name: String): User 25 | } 26 | type User { 27 | name: String 28 | } 29 | `); 30 | 31 | app.express.use('/', express.static(__dirname + '/dist')); 32 | app.listen(3000, () => { 33 | console.log('Visit: http://localhost:3000/ for React.js front-end'); 34 | console.log('Visit: http://localhost:3000/graphql for GraphQLPlayground'); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/exampleWithAuth.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | const { GraphQLess } = require('../index.js'); 4 | const app = new GraphQLess({ context: ({ req }) => ({ userId: req.jwt }) }); 5 | 6 | const JWT_SECRET = 'SHHH'; 7 | const db = { users: [{ id: 'abc', name: 'Tyler' }] }; 8 | 9 | // This is our authentication middleware 10 | app.use((req, res, next) => { 11 | req.jwt = req.headers.authorization 12 | ? jwt.verify(req.headers.authorization.replace('Bearer ', ''), JWT_SECRET) 13 | : null; 14 | next(); 15 | }); 16 | 17 | // Hit this route to get a token 18 | app.get('getToken', (req, res) => { 19 | res.send(jwt.sign(db.users[0].id, JWT_SECRET)); 20 | }); 21 | 22 | // Now we can access in our route 23 | // context is passed inside req 24 | app.get('/me', (req, res) => { 25 | res.send(db.users.find(user => user.id === req.context.userId)); 26 | }); 27 | 28 | app.useSchema(` 29 | type Query { 30 | me: User 31 | getToken: String 32 | } 33 | type User { 34 | id: ID 35 | name: String 36 | } 37 | `); 38 | 39 | app.listen(3000, () => { 40 | console.log('Visit: http://localhost:3000/graphql'); 41 | }); 42 | -------------------------------------------------------------------------------- /examples/exampleWithRelations.js: -------------------------------------------------------------------------------- 1 | const { GraphQLess } = require('../index.js'); 2 | const app = new GraphQLess(); 3 | 4 | const db = { 5 | users: [{ id: 1, name: 'Tyler', favoriteIds: [2] }], 6 | favorites: [{ id: 2, name: 'Pizza', userId: 1 }], 7 | }; 8 | 9 | class User { 10 | constructor({ name, favoriteIds }) { 11 | this.name = name; 12 | this.favoriteIds = favoriteIds; 13 | } 14 | 15 | favorites() { 16 | return db.favorites 17 | .filter(favorite => this.favoriteIds.includes(favorite.id)) 18 | .map(favorite => new Favorite(favorite)); 19 | } 20 | } 21 | 22 | class Favorite { 23 | constructor({ name, userId }) { 24 | this.name = name; 25 | this.userId = userId; 26 | } 27 | 28 | user() { 29 | return new User(db.users.find(user => user.id === this.userId)); 30 | } 31 | } 32 | 33 | app.get('/users', (req, res) => { 34 | const { users } = db; 35 | res.send(users.map(user => new User(user))); 36 | }); 37 | 38 | app.get('/favorites', (req, res) => { 39 | const { favorites } = db; 40 | res.send(favorites.map(favorite => new Favorite(favorite))); 41 | }); 42 | 43 | app.useSchema(` 44 | type Query { 45 | users: [User] 46 | favorites: [Favorite] 47 | } 48 | type User { 49 | id: ID! 50 | name: String 51 | favorites: [Favorite] 52 | } 53 | type Favorite { 54 | id: ID! 55 | name: String 56 | user: User 57 | } 58 | `); 59 | 60 | app.listen(3000, () => { 61 | console.log('Visit: http://localhost:3000/graphql'); 62 | }); 63 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Welcome to the contributing doc! Thanks for showing interest, we're stoked to have you help us make this project even better! Anything you can think of no matter how small just open an issue or PR and we can explore your idea together. 4 | 5 | To get started clone this repo then run `./ops setup`. If you're not running MacOS you'll need a bash shell installed on your OS then you can alternatively run `./ops setup-bash`. 6 | 7 | Checkout the `examples` folder to get started: 8 | 9 | ```bash 10 | npx nodemon examples/example.js 11 | npx nodemon examples/exampleWithAuth.js 12 | npx nodemon examples/exampleWithRouter/index.js 13 | npx nodemon examples/exampleWithReactClient/index.js 14 | npx nodemon examples/exampleWithSubscription.js 15 | npx nodemon examples/exampleWithRelations.js 16 | ``` 17 | 18 | The `__tests__` folder provides good documentation of the inner workings of GraphQLess: 19 | 20 | ```bash 21 | ./ops test 22 | ``` 23 | 24 | ## Setup 25 | 26 | ```bash 27 | ./ops setup # MacOS 28 | ./ops setup-bash # other systems with BASH installed 29 | ``` 30 | 31 | ## Test 32 | 33 | ```bash 34 | ./ops test 35 | ``` 36 | 37 | ## Todo 38 | 39 | - Write tests with nock and or graphql-request (Use examples apps as tests) 40 | - Create Github action for building/releasing npm package and use Github package for local development of actions 41 | - Maybe add variable route resolution ie: `/users/:id` req.query.id or something??? Use pick to pull the route defined variables from the GraphQL input vars and place them in req.query then leave the rest in req.body 42 | - Throw error for any route with more than one name `/users` okay. `/users/:id` okay. `/users/:id/:name` okay. `/users/favorites` -> throw Error("Because routes map directly to resolvers they can only have a depth of 1") 43 | -------------------------------------------------------------------------------- /examples/exampleWithReactClient/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { ApolloClient } from 'apollo-client'; 4 | import { InMemoryCache } from 'apollo-cache-inmemory'; 5 | import { HttpLink } from 'apollo-link-http'; 6 | import { ApolloProvider, Query, Mutation } from 'react-apollo'; 7 | import gql from 'graphql-tag'; 8 | 9 | const CREATE_USER = gql(` 10 | mutation createUser($name: String) { 11 | createUser(name: $name) { 12 | name 13 | } 14 | } 15 | `); 16 | 17 | const GET_USERS = gql(` 18 | query getUsers { 19 | users { 20 | name 21 | } 22 | } 23 | `); 24 | 25 | class UserList extends React.Component { 26 | state = { name: '' }; 27 | 28 | createUserHandler = (cache, { data: { createUser } }) => { 29 | const { users } = cache.readQuery({ query: GET_USERS }); 30 | cache.writeQuery({ 31 | query: GET_USERS, 32 | data: { users: users.concat([createUser]) }, 33 | }); 34 | }; 35 | 36 | renderSubmitButton = createUser => ( 37 | 42 | ); 43 | 44 | renderContent = ({ loading, error, data }) => { 45 | if (loading) return

Loading...

; 46 | if (error) return

Error

; 47 | return ( 48 |
49 |

User List

50 | this.setState({ name: ev.target.value })} 52 | placeholder="New username..." 53 | value={this.state.name} 54 | /> 55 | 56 | {this.renderSubmitButton} 57 | 58 | 63 |
64 | ); 65 | }; 66 | 67 | render() { 68 | return {this.renderContent}; 69 | } 70 | } 71 | 72 | const client = new ApolloClient({ 73 | cache: new InMemoryCache(), 74 | link: new HttpLink({ uri: 'http://localhost:3000/graphql' }), 75 | }); 76 | 77 | ReactDOM.render( 78 | 79 | 80 | , 81 | document.getElementById('root') 82 | ); 83 | -------------------------------------------------------------------------------- /__tests__/index.js: -------------------------------------------------------------------------------- 1 | const { GraphQLess, Router } = require('../index.js'); 2 | 3 | describe('Router()', () => { 4 | it('Router variables are not static and should not be shared between routers', () => { 5 | const router1 = new Router(); 6 | const router2 = new Router(); 7 | 8 | router1.useSchema('a dog'); 9 | router2.useSchema('a cat'); 10 | 11 | expect(router1.schemas).not.toEqual(['a dog', 'a cat']); 12 | expect(router1.schemas).toEqual(['a dog']); 13 | expect(router2.schemas).toEqual(['a cat']); 14 | }); 15 | }); 16 | 17 | describe('use()', () => { 18 | it('should only accept an instance of Function or Router', () => { 19 | const app = new GraphQLess(); 20 | const router = new Router(); 21 | 22 | expect(app.use(() => {})).toEqual(app); 23 | expect(app.use(router)).toEqual(app); 24 | expect(() => app.use('red')).toThrow( 25 | Error('use() expects an instance of Function or Router') 26 | ); 27 | }); 28 | 29 | it('should chain multiple middleware functions', () => { 30 | const app = new GraphQLess(); 31 | 32 | app.use(() => 'first in chain'); 33 | app.use(() => 'second in chain'); 34 | 35 | expect(app.middlewares.length).toEqual(2); 36 | expect(app.middlewares[0]()).toEqual('first in chain'); 37 | expect(app.middlewares[1]()).toEqual('second in chain'); 38 | }); 39 | 40 | it('should merge class variables if input is a GraphQLess instance', () => { 41 | const app = new GraphQLess(); 42 | const router = new Router(); 43 | 44 | router.get('/get', () => {}); 45 | router.post('/post', () => {}); 46 | router.put('/put', () => {}); 47 | router.delete('/delete', () => {}); 48 | router.query('/query', () => {}); 49 | router.mutation('/mutation', () => {}); 50 | router.subscription('/mutation', () => {}); 51 | 52 | router.useSchema('test'); 53 | router.useSchema('test2'); 54 | 55 | router.use(() => {}); 56 | router.use(() => {}); 57 | 58 | app.use(router); 59 | expect(app.middlewares.length).toEqual(2); 60 | expect( 61 | Object.keys({ 62 | ...app.resolvers.Query, 63 | ...app.resolvers.Mutation, 64 | ...app.resolvers.Subscription, 65 | }).length 66 | ).toBe(6); 67 | expect(app.schemas.length).toBe(2); 68 | }); 69 | 70 | it('should correctly chain Router middlewares to existing app middleswares', () => { 71 | const app = new GraphQLess(); 72 | const router = new Router(); 73 | 74 | router.use(() => 'router middleware'); 75 | app.use(router); 76 | app.use(() => 'app middleware'); 77 | 78 | expect(app.middlewares.length).toEqual(2); 79 | expect(app.middlewares[0]()).toEqual('router middleware'); 80 | expect(app.middlewares[1]()).toEqual('app middleware'); 81 | }); 82 | }); 83 | 84 | describe('express', () => { 85 | it('should be accessible from GraphQLess instance', () => { 86 | const app = new GraphQLess(); 87 | app.express.use('/example', (req, res) => res.send('test')); 88 | 89 | // express internals 90 | const layer = app.express._router.stack.pop(); 91 | const output = layer.handle(null, { send: val => val }); 92 | 93 | expect(output).toEqual('test'); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { mergeResolvers, mergeTypes } = require('merge-graphql-schemas'); 2 | const ApolloServerExpress = require('apollo-server-express'); 3 | const express = require('express'); 4 | const http = require('http'); 5 | 6 | const { ApolloServer, gql } = ApolloServerExpress; 7 | 8 | class Router { 9 | constructor() { 10 | this.RESOLVER_TYPES = { 11 | Query: 'Query', 12 | Mutation: 'Mutation', 13 | Subscription: 'Subscription', 14 | }; 15 | this.delete = this.patch = this.post = this.put = this.mutation; 16 | this.get = this.query; 17 | this.middlewares = []; 18 | this.resolvers = {}; 19 | this.schemas = []; 20 | } 21 | 22 | use(func) { 23 | if (func instanceof Router) { 24 | this.middlewares = this.middlewares.concat(func.middlewares); 25 | this.resolvers = mergeResolvers([this.resolvers, func.resolvers]); 26 | this.schemas = [...this.schemas, ...func.schemas]; 27 | } else if (func instanceof Function) { 28 | this.middlewares.push(func); 29 | } else { 30 | throw Error('use() expects an instance of Function or Router'); 31 | } 32 | return this; 33 | } 34 | 35 | query(route, resolver) { 36 | const { Query } = this.RESOLVER_TYPES; 37 | if (!this.resolvers[Query]) { 38 | this.resolvers[Query] = {}; 39 | } 40 | return this.useResolver(route, resolver, Query); 41 | } 42 | 43 | mutation(route, resolver) { 44 | const { Mutation } = this.RESOLVER_TYPES; 45 | if (!this.resolvers[Mutation]) { 46 | this.resolvers[Mutation] = {}; 47 | } 48 | return this.useResolver(route, resolver, Mutation); 49 | } 50 | 51 | subscription(route, resolver) { 52 | const { Subscription } = this.RESOLVER_TYPES; 53 | if (!this.resolvers[Subscription]) { 54 | this.resolvers[Subscription] = {}; 55 | } 56 | return this.useResolver(route, resolver, Subscription); 57 | } 58 | 59 | useResolver(route, resolver, type) { 60 | const name = route.replace('/', ''); 61 | if (this.resolvers[type][name]) { 62 | throw Error(`Cannot declare duplicate resolver name: ${name}`); 63 | } 64 | const { Subscription } = this.RESOLVER_TYPES; 65 | this.resolvers[type][name] = 66 | type === Subscription 67 | ? resolver 68 | : (parent, args, context, info) => 69 | new Promise(resolve => 70 | resolver({ body: args, context }, { send: resolve }) 71 | ); 72 | return this; 73 | } 74 | 75 | useSchema(schema) { 76 | this.schemas.push(schema); 77 | return this; 78 | } 79 | } 80 | 81 | class GraphQLess extends Router { 82 | constructor(config = { options: {} }) { 83 | super(); 84 | this.express = express(); 85 | const { options, ...rest } = config; 86 | this.configApolloServer = rest; 87 | this.options = { 88 | endpoint: '/graphql', 89 | playground: true, 90 | mergeTypes: { all: true }, 91 | ...options, 92 | }; 93 | } 94 | 95 | listen(...argsListen) { 96 | const server = new ApolloServer({ 97 | typeDefs: gql(mergeTypes(this.schemas, this.options.mergeTypes)), 98 | resolvers: this.resolvers, 99 | playground: this.options.playground, 100 | ...this.configApolloServer, 101 | }); 102 | 103 | this.middlewares.forEach(middleware => { 104 | this.express.use(this.options.endpoint, middleware); 105 | }); 106 | 107 | server.applyMiddleware({ app: this.express, path: this.options.endpoint }); 108 | 109 | const { Subscription } = this.RESOLVER_TYPES; 110 | const hasSubscriptions = this.resolvers[Subscription]; 111 | 112 | if (hasSubscriptions) { 113 | const httpServer = http.createServer(this.express); 114 | server.installSubscriptionHandlers(httpServer); 115 | return httpServer.listen(...argsListen); 116 | } 117 | 118 | return this.express.listen(...argsListen); 119 | } 120 | } 121 | 122 | module.exports = { 123 | GraphQLess, 124 | Router, 125 | ApolloServerExpress, 126 | express, 127 | }; 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | GraphQLess 3 |

4 |

5 | GraphQLess 6 |

7 |

8 | ⚛️ 🚀🤘 9 |

10 |

11 | REST and GraphQL really aren't that different. I'll prove it!
12 | GraphQLess is a thin wrapper around the official apollo-server-express project. 13 |
14 | GraphQLess lets you write a GraphQL server in an Express.js style. 15 |

16 | 17 | ## Setup 18 | 19 | ```bash 20 | yarn add graphqless 21 | ``` 22 | 23 | And here is how you write a server... Look familiar? 24 | 25 | ```jsx 26 | const { GraphQLess } = require('graphqless'); 27 | const app = new GraphQLess(); 28 | 29 | const db = { users: [{ name: 'Tyler' }] }; 30 | 31 | app.get('/users', (req, res) => { 32 | const { users } = db; 33 | res.send(users); 34 | }); 35 | 36 | app.get('/user', (req, res) => { 37 | const user = db.users.find(user => user.name === req.body.name); 38 | res.send(user); 39 | }); 40 | 41 | app.post('/createUser', (req, res) => { 42 | const userCount = db.users.push({ name: req.body.name }); 43 | res.send(userCount); 44 | }); 45 | 46 | app.listen(3000, () => { 47 | console.log('Visit: http://localhost:3000/playground'); 48 | }); 49 | ``` 50 | 51 | I know it looks like Express.js but the code above is a GraphQL server! There is one caveat though... 52 | 53 | GraphQL requires us to write a schema that describes the `.get` and `.post` functions' inputs and outputs. 54 | 55 | Just know that `.get === Query && .post === Mutation`. Now let's modify the last few lines of the snippet above to include the required schema: 56 | 57 | ```jsx 58 | app 59 | .useSchema( 60 | ` 61 | type Query { 62 | users: [User] 63 | user(name: String): User 64 | } 65 | type Mutation { 66 | createUser(name: String): Int 67 | } 68 | type User { 69 | name: String 70 | } 71 | ` 72 | ) 73 | .listen(3000, () => { 74 | console.log('Visit: http://localhost:3000/playground'); 75 | }); 76 | ``` 77 | 78 | That's the only catch! You now have a fully functioning and extendable GraphQL server. 79 | 80 | You can find more examples in the [examples](/examples) folder. 81 | 82 | ## Examples 83 | 84 | ### Queries for examples/example 85 | 86 | ```bash 87 | npx nodemon examples/example.js 88 | ``` 89 | 90 | ```graphql 91 | mutation createUser { 92 | createUser(name: "Buchea") 93 | } 94 | 95 | query getUsers { 96 | users { 97 | name 98 | } 99 | user(name: "Tyler") { 100 | name 101 | } 102 | } 103 | ``` 104 | 105 | ### Queries for examples/exampleWithAuth 106 | 107 | ```bash 108 | npx nodemon examples/exampleWithAuth.js 109 | ``` 110 | 111 | ```graphql 112 | # Add this to "HTTP HEADERS" in GraphQL Playground: 113 | # { "Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.YWJj.4noRC-c0ay0hOeZ5Cgc80MVS0P4p4FrR2lJFzMNSnE4" } 114 | 115 | query getMe { 116 | getToken 117 | me { 118 | id 119 | name 120 | } 121 | } 122 | ``` 123 | 124 | ### Queries for examples/exampleWithRouter 125 | 126 | ```bash 127 | npx nodemon examples/exampleWithRouter/index.js 128 | ``` 129 | 130 | ```graphql 131 | mutation createUser { 132 | createUser(name: "Buchea") 133 | } 134 | 135 | query getUsers { 136 | users { 137 | name 138 | } 139 | user(name: "Tyler") { 140 | name 141 | } 142 | } 143 | ``` 144 | 145 | ### Queries for examples/exampleWithReactClient 146 | 147 | ```bash 148 | npx nodemon examples/exampleWithReactClient/index.js 149 | ``` 150 | 151 | ```graphql 152 | mutation createUser { 153 | createUser(name: "Buchea") { 154 | name 155 | } 156 | } 157 | 158 | query getUsers { 159 | users { 160 | name 161 | } 162 | } 163 | ``` 164 | 165 | ### Queries for examples/exampleWithSubscription 166 | 167 | ```bash 168 | npx nodemon examples/exampleWithSubscription.js 169 | ``` 170 | 171 | ```graphql 172 | subscription subscribeToCount { 173 | count 174 | } 175 | 176 | query getDummyData { 177 | dummy 178 | } 179 | ``` 180 | 181 | ### Queries for examples/exampleWithRelations 182 | 183 | ```bash 184 | npx nodemon examples/exampleWithRelations.js 185 | ``` 186 | 187 | ```graphql 188 | query getDeepRelations { 189 | users { 190 | name 191 | favorites { 192 | name 193 | user { 194 | name 195 | } 196 | } 197 | } 198 | } 199 | ``` 200 | --------------------------------------------------------------------------------