GithubHelp home page GithubHelp logo

tmeasday / create-graphql-server Goto Github PK

View Code? Open in Web Editor NEW
322.0 13.0 33.0 384 KB

Generate your GraphQL server one type at a time

License: MIT License

JavaScript 87.77% Shell 12.23%
graphql graphql-server codegeneration mongodb

create-graphql-server's Introduction

Create GraphQL Server

Create-graphql-server is a scaffolding tool that lets you generate a new Mongo/Express/Node.js GraphQL server project from the command line. After generating the project you can also generate code to support your GraphQL schema directly from the schema files. Basic authentication support is included via Passport Local with JWTs.

NOTE: this project is not actively maintained. As a scaffolding tool, this is not necessarily an issue (you can generate a project with it and then forget about the tool), but it is increasingly falling out of date. A spritiual successor is the graphql-cli project. Please take a look!

Getting Started

Installation

Install it once globally:

npm install -g create-graphql-server

Creating a Server

To create a new server in the my-new-server-dir folder use the init command:

create-graphql-server init my-new-server-dir
cd my-new-server-dir
yarn install

Starting the Server

In most development environments you can now fire up your new server using the packaged prebuilt Mongo server:

yarn start

If mongo-prebuilt fails to start, or you'd rather use another MongoDB installation for development, simply set the MONGO_URL environment variable when you start the server, as follows:

# On Windows:
SET MONGO_URL=mongodb://localhost:27017&&yarn start

# On Unix/OSX:
MONGO_URL=mongodb://localhost:27017 yarn start

If you set up a username, password or a different port for Mongo, or are accessing Mongo through a service such as mLab, correct the MONGO_URL above to reflect that.

Running Queries

Your server is now up and running. To query it, point your browser at http://localhost:3010/graphiql. There isn't anything to query yet however.

Adding Types: Overview

To add types, you can define them in GraphQL schema files, then generate code for them using the add-type command, as follows:

create-graphql-server add-type path/to/my-new-type.graphql

If you have a folder full of schema files, you can add them all at once by pointing add-type to a folder instead of an individual schema file:

create-graphql-server add-type path

Sample schema files are included in test/input. To see what a complete generated server looks like using them, check out test/output-app.

Schemas

You create a GraphQL type for your schema by specifying the type as input, with some special code-generation controlling directives.

For example, in User.graphql:

type User {
  email: String!
  bio: String

  tweets: [Tweet!] @hasMany(as: "author")
  liked: [Tweet!] @belongsToMany

  following: [User!] @belongsToMany
  followers: [User!] @hasAndBelongsToMany(as: "following")
}

The above will generate a User type which is linked to other users and a tweet type via foriegn keys and which will have mutations to add, update and remove users, as well as some root queries to find a single user or all users.

The directives used control the code generation (see below).

Directives

  • @unmodifiable - the field will not appear in the update mutation
  • @enum - the field's type is an enum, and can be set directly (not just by Id).

Relations

If types reference each other, you should use an association directive to explain to the generator how the reference should be stored in mongo:

Singleton fields

If the field references a single (nullable or otherwise) instance of another type, it will be either:

  • @belongsTo - the foreign key is stored on this type as ${fieldName}Id [this is the default]
  • @hasOne - the foreign key is stored on the referenced type as ${typeName}Id. Provide the "as": X argument if the name is different. [NOTE: this is not yet fully implemented].

Paginated fields

If the field references an array (again w/ or w/o nullability) of another type, it will be either:

  • @belongsToMany - there is a list of foreign keys stored on this type as ${fieldName}Ids [this is the default]
  • @hasMany - the foreign key is on the referenced type as ${typeName}Id. Provide the "as": X argument if the name is different. (this is the reverse of @belongsTo in a 1-many situation).
  • @hasAndBelongsToMany - the foreign key on the referenced type as ${typeName}Ids. Provide the "as": X argument if the name is different. (this is the reverse of @belongsToMany in a many-many situation).

Updating types

To update types, just re-run add-type again:

create-graphql-server add-type path/to/input.graphql [--force-update]

This overwrites your old type specific files from the directories: schema, model, resolvers.

It recognizes, if you've changed any code file, which will be overwritten by the generator and stops and warns. If you are sure, you want to overwrite your changes, then just use the --force-update option.

Removing types

To remove types, use the following command with the path to the GraphQL file, or as alternative, just enter the type name without path.

create-graphql-server remove-type path/to/input.graphql

create-graphql-server remove-type typename

create-graphql-server remove-type path

This command deletes your old type specific files from the directories: schema, model, resolvers. It also removes the code references out of the corresponding index files.

It recognizes, if you've changed any code file, which will be overwritten by the generator and stops and warns. If you are sure, you want to overwrite your changes, then just use the force-update option.

Authentication

CGS sets up a basic passport-based JWT authentication system for your app.

NOTE: you should ensure users connect to your server through SSL.

To use it, ensure you have a GraphQL type called User in your schema, with a field email, by which users will be looked up. When creating users, ensure that a bcrypted hash database field is set. For instance, if you created your users in this way:

type User {
  email: String!
  bio: String
}

You could update the generated CreateUserInput input object to take a password field:

input CreateUserInput {
  email: String!
  password: String! # <- you need to add this line to the generated output
  bio: String
}

And then update the generated User model to hash that password and store it:

import bcrypt from 'bcrypt';
// Set this as appropriate
const SALT_ROUNDS = 10;

class User {
  async insert(doc) {
    // We don't want to store passwords plaintext!
    const { password, ...rest } = doc;
    const hash = await bcrypt.hash(password, SALT_ROUNDS);
    const docToInsert = Object.assign({}, rest, {
      hash,
      createdAt: Date.now(),
      updatedAt: Date.now(),
    });

    // This code is unchanged.
    const id = (await this.collection.insertOne(docToInsert)).insertedId;
    this.pubsub.publish('userInserted', await this.findOneById(id));
    return id;
  }
}

Client side code

To create users, simply call your generated createUser mutation (you may want to add authorization to the resolver, feel free to modify it).

To login on the client, you make a RESTful request to /login on the server, passing email and password in JSON. You'll get a JWT token back, which you should attach to the Authorization header of all GraphQL requests.

Here's some code to do just that:

const KEY = 'authToken';
let token = localStorage.getItem(KEY);

const networkInterface = createNetworkInterface(/* as usual */);
networkInterface.use([
  {
    applyMiddleware(req, next) {
      req.options.headers = {
        authorization: token ? `JWT ${token}` : null,
        ...req.options.headers,
      };
      next();
    },
  },
]);

// Create your client as usual and pass to a provider
const client = /*...*/

// Call this function from your login form, or wherever.
const login = async function(serverUrl, email, password) {
  const response = await fetch(`${serverUrl}/login`, {
    method: 'POST',
    body: JSON.stringify({ email, password }),
    headers: { 'Content-Type': 'application/json' },
  });
  const data = await response.json();
  token = data.token;
  localStorage.setItem(KEY, token);
}

Development

Running code generation tests

You can run some basic code generation tests with npm test.

Testing full app code generation

A simple test to check that using the test/input input files with the CGS scripts generates test/output-app can be run with npm run output-app-generation-test.

Running end-to-end tests

You can run a set of end-to-end tests of the user/tweet app (which lives in test/output-app) with npm run end-to-end-test. This will seed the database, and run against a running server.

The test files are in test/output-app-end-to-end.

You need to start the standard server with cd test/output-app; npm start, then run npm run end-to-end-test.

Creating seed database

If you need to change the fixtures for the test db

Start the server, then run

mongoexport --host 127.0.0.1:3002 --db database --collection user > seeds/User.json
mongoexport --host 127.0.0.1:3002 --db database --collection tweet > seeds/Tweet.json

Maintenance

As this is a code generator, and not a library, once you run the code, you are on your own :)

By which I mean, you should feel free to read the generated code, understand it, and modify it as you see fit. Any updates to CGS will just affect future apps that you generate.

If you'd like to see improvements, or find any bugs, by all means report them via the issues, and send PRs. But workarounds should be always be possible simply by patching the generated code.

create-graphql-server's People

Contributors

aadmaa avatar edmarbarros avatar idkjs avatar shilman avatar tmeasday avatar tobkle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

create-graphql-server's Issues

Build

Hi, how can I make production build?

Clarify Instructions for passport-jwt in README

I just began using cgs and I found it a bit difficult to figure out the passport-jwt part. I think it would be very helpful to have instructions in the README regarding how to check for a failed authentication attempt, as passport-jwt won't automatically send back a 401 when you use it the way we have.

Add a testing strategy based on mongo mocking

Here is some code that I've used:

Example test:

it('creates builds in the right state', async () => {
  const { createBuild: build } = await runQuery(
    db,
    gql`
      mutation {
        createBuild(input: { appId: "${appId}" }) {
          status
        }
      }
    `
  );

  expect(build).toEqual({
    status: 'BUILD_IN_PROGRESS',
  });
});

Reuse model instances?

I noticed this because one of my models starts polling on creation.

We're not giving any request info to addModelsToContext(), so is there a point to creating new model instances for every request? If not, we could just do it once:

+  const context = addModelsToContext({ db, pubsub })
+
   const app = express().use('*', cors())
   app.use(bodyParser.urlencoded({ extended: true }))
   app.use(bodyParser.json())
 
   app.use((req, res, next) => {
-    req.context = addModelsToContext({ db, pubsub })
+    req.context = context
     next()

Dataloader does not key on mongo ids properly

Two different identical mongo ids (i.e. the same id reached in two different ways) will not have equality in JS and thus will be separate keys in the data loader.

The effect of this is:

  1. unnecessary queries
  2. if you clear a key (e.g. on update) you may not see the changes (if you are reading from the other key).

I think the simplest solution is to use: { cacheKeyFn id => id.toString() } when creating data loaders.

Add mutations for associations?

It seems like it would be useful to generate something like

Mutation {
  createUserFollower(userId: ID!, follower: UserInput!): User
  addToUserFollowersById(userId: ID!, followerId: ID!): User
  setUserFollowers(userId: ID!, followerIds: [ID!]!): User
  removeFromUserFollowersById(userId: ID!, followerId: ID!): Boolean
}
  1. Does it make sense to generate all of these methods?
  2. Are there any others we should add?
  3. How to deal with pluralization? (We were leaning towards not doing Rails-style auto-pluralization but it becomes real hard to give non-crappy names without singularizing followers)

add-type cannot read a file

I have node 7.10, freshly installed create-graphql-server, initialized new project and ran it. Here everything works.

But the moment I try to create new type, it is not working. Running command: $ create-graphql-server add-type ./User.graphql gave me an output:

CREATE-GRAPHQL-SERVER

Running add-type
Error: Cannot read file ./User.graphql

I tried to run it with sudo (in the case of some strange permission related things) but still the same problem. I would appreciate any hints how to make it work

Windows 10 MongoDB Error 2

Hello! After installing CGS and all its dependencies on Windows 10, I immediately get "Error Code 2: MongoDB was started with erroneous or incompatible command line options" upon startup. Wondering if other Windows 10 users are having the same issue. For reference, here is my debug log:

0 info it worked if it ends with ok
1 verbose cli [ 'C:\\Program Files (x86)\\nodejs\\node.exe',
1 verbose cli   'C:\\Program Files (x86)\\nodejs\\node_modules\\npm\\bin\\npm-cli.js',
1 verbose cli   'run',
1 verbose cli   'start' ]
2 info using [email protected]
3 info using [email protected]
4 verbose run-script [ 'prestart', 'start', 'poststart' ]
5 info lifecycle [email protected]~prestart: [email protected]
6 silly lifecycle [email protected]~prestart: no script for prestart, continuing
7 info lifecycle [email protected]~start: [email protected]
8 verbose lifecycle [email protected]~start: unsafe-perm in lifecycle true
9 verbose lifecycle [email protected]~start: PATH: C:\Program Files (x86)\nodejs\node_modules\npm\bin\node-gyp-bin;C:\Sublime\Playground3\GraphQLServer1\node_modules\.bin;C:\Program Files\ConEmu\ConEmu\Scripts;C:\Program Files\ConEmu;C:\Program Files\ConEmu\ConEmu;C:\ProgramData\Oracle\Java\javapath;C:\Program Files\Common Files\Microsoft Shared\Windows Live;C:\Program Files (x86)\Common Files\Microsoft Shared\Windows Live;c:\Program Files (x86)\Intel\iCLS Client\;c:\Program Files\Intel\iCLS Client\;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\DAL;C:\Program Files (x86)\Intel\Intel(R) Management Engine Components\IPT;C:\Program Files (x86)\Windows Live\Shared;C:\wamp64\bin\php\php5.6.25;C:\Program Files (x86)\Skype\Phone\;C:\Program Files\Git\cmd;C:\Program Files (x86)\nodejs\;C:\Program Files (x86)\Yarn\bin;C:\Users\Brian\AppData\Local\Microsoft\WindowsApps;C:\Program Files (x86)\Java\jre7\bin;C:\Users\Brian\AppData\Local\.meteor\;C:\Users\Brian\AppData\Roaming\npm;C:\Users\Brian\AppData\Local\atom\bin;C:\Users\Brian\AppData\Local\Yarn\.bin
10 verbose lifecycle [email protected]~start: CWD: C:\Sublime\Playground3\GraphQLServer1
11 silly lifecycle [email protected]~start: Args: [ '/d /s /c', 'babel-node index.js' ]
12 silly lifecycle [email protected]~start: Returned: code: 1  signal: null
13 info lifecycle [email protected]~start: Failed to exec start script
14 verbose stack Error: [email protected] start: `babel-node index.js`
14 verbose stack Exit status 1
14 verbose stack     at EventEmitter.<anonymous> (C:\Program Files (x86)\nodejs\node_modules\npm\lib\utils\lifecycle.js:279:16)
14 verbose stack     at emitTwo (events.js:106:13)
14 verbose stack     at EventEmitter.emit (events.js:194:7)
14 verbose stack     at ChildProcess.<anonymous> (C:\Program Files (x86)\nodejs\node_modules\npm\lib\utils\spawn.js:40:14)
14 verbose stack     at emitTwo (events.js:106:13)
14 verbose stack     at ChildProcess.emit (events.js:194:7)
14 verbose stack     at maybeClose (internal/child_process.js:899:16)
14 verbose stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5)
15 verbose pkgid [email protected]
16 verbose cwd C:\Sublime\Playground3\GraphQLServer1
17 error Windows_NT 10.0.14393
18 error argv "C:\\Program Files (x86)\\nodejs\\node.exe" "C:\\Program Files (x86)\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "run" "start"
19 error node v7.8.0
20 error npm  v4.2.0
21 error code ELIFECYCLE
22 error errno 1
23 error [email protected] start: `babel-node index.js`
23 error Exit status 1
24 error Failed at the [email protected] start script 'babel-node index.js'.
24 error Make sure you have the latest version of node.js and npm installed.
24 error If you do, this is most likely a problem with the ~name~ package,
24 error not with npm itself.
24 error Tell the author that this fails on your system:
24 error     babel-node index.js
24 error You can get information on how to open an issue for this project with:
24 error     npm bugs ~name~
24 error Or if that isn't available, you can get their info via:
24 error     npm owner ls ~name~
24 error There is likely additional logging output above.
25 verbose exit [ 1, true ]

Thanks!

create-graphql-server add-type naively appends to index files

Version 0.5.3 will append model, resolver, and schema hooks to their respective index files for every type added through a call to create-graphql-server add-type .... This ends up appending the same set of lines over and over to the bottom of each of these files -- triggering a slew of linter errors.

Understandable, since it's generated code and difficult to detect whether these imports already exist. However, I'd recommend either looking for identical lines already present in a file (which is difficult anyways since users might have completely reformatted everything about the initial import) or else generate a notice message whenever the tool runs to indicate these changes are being made.

Just a small nit, though. This tool is awesome!

createdAt pagination

Thanks so much for all the work you have put into this. It is making many things so much faster for my workflow.

I am hitting a snag though with the way pagination works. It uses the createdAt field, which seems to be an ISO date format. This means that the default, 0, is not pulling in any documents. If I switch it to this:

all({ lastCreatedAt = 0, limit = 10 }) {
    return this.collection.find({
      createdAt: { $gt: new Date(lastCreatedAt) },
    }).sort({ createdAt: 1 }).limit(limit).toArray();
  }

It works fine.

I feel like there is something on my end I am not doing to make this work properly. Any help or clarification would be greatly appreciated.

Thanks!

Support current apollo subscriptions

Sorry for the lack of PR—working in a generated server with lots of other changes! Latest version of apollo-link-ws uses graphql-ws as the protocol, which isn't supported by

"subscriptions-transport-ws": "0.2.6"

image

HTTP/1.1 400 Bad Request
Connection: close
X-WebSocket-Reject-Reason: Unsupported protocol.

Fix

package.json
-    "subscriptions-transport-ws": "0.2.6"
+    "subscriptions-transport-ws": "^0.9.1"
-    "graphql": "0.7.2",
-    "graphql-server": "^0.3.2",
-    "graphql-server-express": "^0.4.3",
-    "graphql-subscriptions": "0.2.0",
-    "graphql-tools": "^0.8.2",
+    "graphql": "^0.11.7",
+    "graphql-subscriptions": "^0.5.4",
+    "graphql-tag": "^2.5.0",
+    "graphql-tools": "^2.6.1",
+    "apollo-cache-inmemory": "^1.0.0",
+    "apollo-client": "^2.0.1",
+    "apollo-link-http": "^1.1.0",
+    "apollo-server-express": "^1.2.0",

example resolver:

  Subscription: {
    githubStars: {
      subscribe: () => pubsub.asyncIterator('githubStars')
    }
  }

server/index.js:

import { execute, subscribe } from 'graphql'
...
  SubscriptionServer.create(
    {
      schema,
      execute,
      subscribe,
      onConnect: connectionParams =>
        addModelsToContext({ db, githubAPI, pubsub })
    },
    {
      server: websocketServer
    }
  )

Setup CI

We need to ensure that all the various testing modalities pass.

Use prettier

Just need to be a bit careful in converting the output app and the input skel.

Relation directives

It might be more flexible to instead of:

  tweets: [Tweet!] @hasMany(as: "author")

write

  tweets: [Tweet!] @as(key: "author") @paginated

Also we should potentially paginated all array fields by default?

Mutations for related data

Hi, that is a great project, thank you for you time!

It seems like generated code respects only querying with related data (likes, followers etc), but for mutations I do not see lets say addLiked, removeFollower. Is it not implemented yet or hard to program?

Tests fail

When submitting a PR, an unrelated test fails

Add production build command

npm run build since babel-node isn't meant for production use. My attempt is failing:

$ GLOBIGNORE="node_modules/*"; babel **/*.js -o build.js 
ReferenceError: [BABEL] data/chapters.js: Unknown option: /Users/me/gh/api-model/node_modules/babel-preset-env/lib/index.js.__esModule. Check out http://babeljs.io/docs/usage/options/ for more info
    at Logger.error (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/logger.js:39:11)
    at OptionManager.mergeOptions (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:267:20)
    at /usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:349:14
    at /usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:369:24
    at Array.map (native)
    at OptionManager.resolvePresets (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:364:20)
    at OptionManager.mergePresets (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:348:10)
    at OptionManager.mergeOptions (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:307:14)
    at OptionManager.addConfig (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:207:10)
    at OptionManager.findConfigs (/usr/local/lib/node_modules/babel-cli/node_modules/babel-core/lib/transformation/file/options/option-manager.js:413:16)
"dev": "babel-node index.js",
"build": "rm build.js; babel -d/o something",
"start": "node build.js"

Init project failure

Started like this:

➜  rootDir  npm install -g create-graphql-server
/Users/me/.nvm/versions/node/v6.9.1/bin/create-graphql-server -> /Users/me/.nvm/versions/node/v6.9.1/lib/node_modules/create-graphql-server/dist/bin/create-graphql-server.js
/Users/me/.nvm/versions/node/v6.9.1/lib
└── [email protected]

Error:

➜  rootDir create-graphql-server init generator
env: babel-node: No such file or directory

Added update-type and remove-type

@tmeasday Thanks for your great work again. This helped me a lot to get deeper into graphql.

Forked and enhanced your version for having beside add-type also ...

  • update-type
  • remove-type

You will find it in my fork: https://github.com/tobkle/create-graphql-server

Be aware, that it doesn't update adjusted type specific resolvers, schemas or model files. Instead it will remove the existing files and will regenerate them. But during removal: Instead of deleting the files, it will archive them in specific archive folders. It removes the references of the types in the corresponding index.js files. See Changelog and adjusted documentation for more information.

Please be careful, this change is still an alpha version, and hasn't been tested well in my enhancements. Please feel free to copy or adjust to meet your own coding quality standards.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.