Create production-ready GraphQL servers
Check out the post on Medium! 😉
npm install --global create-graphql
npm update --global create-graphql
It can generate:
- Mutations (create and edit);
- Types;
- Connections;
- Loaders;
- Tests (with Jest);
- Generate from a Mongoose schema (with
--schema
option).
-
Generate a type, the file name will be automatically suffixed with
Type
:create-graphql generate --type Awesome # create `AwesomeType`.
-
Generate mutations, the files names will be automatically suffixed with
Mutation
:create-graphql generate --mutation Awesome # create `AwesomeAddMutation` & `AwesomeEditMutation`.
-
Generate mutations based on a Mongoose schema:
create-graphql generate --mutation Awesome --schema Awesome
Which will look for
Awesome
Mongoose schema file in./src/model
and generate the mutations fields based on it.
🔥 Tip: You may use aliases commands to create multiple files in one single command:
create-graphql -tm Awesome --schema Awesome
# Will create a GraphQL type and mutation based on `Awesome` schema.
😎 Mutations and types will be automatically generated with tests.
create-graphql init AwesomeProject
Provides an easy way to create a GraphQL server based on @sibelius/graphql-dataloader-boilerplate which is a production-ready server.
We are currently using the same boilerplate on three applications running on production at @entria.
The following command will generate the file AwesomeType
under the folder ./src/type
:
create-graphql --type Awesome
`./src/type/AwesomeType.js`
```js import { GraphQLObjectType, GraphQLString, } from 'graphql';export default new GraphQLObjectType({ name: 'AwesomeType', description: 'Represents AwesomeType', fields: () => ({ example: { type: GraphQLString, description: 'My example field', resolve: obj => obj.example, }, }), });
</details>
Using the `--schema` option with a Mongoose schema, the type would be generated like this:
<details>
<summary>`./src/model/Comment.js`</summary>
```js
import mongoose from 'mongoose';
const { ObjectId } = mongoose.Schema.Types;
const Schema = new mongoose.Schema({
content: {
type: String,
description: 'Comment content in the original language',
required: true,
},
user: {
type: ObjectId,
ref: 'User',
indexed: true,
description: 'User that created this comment',
required: true,
},
owner: {
type: ObjectId,
required: true,
description: 'Object that owns of this product. References to Product, Posts or other comment.',
},
}, {
collection: 'comment',
timestamps: {
createdAt: 'createdAt',
updatedAt: 'updatedAt',
},
});
export default mongoose.model('Comment', Schema);
`./src/type/CommentType.js`
```js import { GraphQLObjectType, GraphQLString, GraphQLID, } from 'graphql';export default new GraphQLObjectType({ name: 'CommentType', description: 'Represents CommentType', fields: () => ({ content: { type: GraphQLString, description: 'Comment content in the original language', resolve: obj => obj.content, }, user: { type: GraphQLID, description: 'User that created this comment', resolve: obj => obj.user, }, owner: { type: GraphQLID, description: 'Object that owns of this product. References to Product, Posts or other comment.', resolve: obj => obj.owner, }, }), });
</details>
<p> </p>
##### `-m`, `--mutation` - Create GraphQL mutations
The following command will generate the files `AwesomeAddMutation` & `AwesomeEditMutation` under the folder `./src/mutation`:
```sh
create-graphql --mutation Awesome
`./src/mutation/AwesomeAddMutation.js`
```js import { GraphQLID, GraphQLString, GraphQLNonNull, } from 'graphql'; import { mutationWithClientMutationId, toGlobalId, } from 'graphql-relay';import AwesomeLoader from '../loader/AwesomeLoader'; import AwesomeConnection from '../connection/AwesomeConnection';
export default mutationWithClientMutationId({ name: 'AwesomeAdd', inputFields: { example: { type: GraphQLString, description: 'My example field', }, }, mutateAndGetPayload: async ({ example }) => { // TODO: mutation logic
return {
// id: id, // ID of the newly created row
error: null,
};
}, outputFields: { awesomeEdge: { type: AwesomeConnection.edgeType, resolve: async({ id }, args, { user }) => { // TODO: load new edge from loader
const awesome = await AwesomeLoader.load(
user, id
);
// Returns null if no node was loaded
if (!awesome) {
return null;
}
return {
cursor: toGlobalId('awesome', awesome),
node: awesome,
};
},
},
error: {
type: GraphQLString,
resolve: ({ error }) => error,
},
}, });
</details>
<details>
<summary>`./src/mutation/AwesomeEditMutation.js`</summary>
```js
import {
GraphQLID,
GraphQLString,
GraphQLNonNull,
} from 'graphql';
import {
mutationWithClientMutationId,
toGlobalId,
} from 'graphql-relay';
import AwesomeType from '../type/AwesomeType';
import AwesomeLoader from '../loader/AwesomeLoader';
import AwesomeConnection from '../connection/AwesomeConnection';
export default mutationWithClientMutationId({
name: 'AwesomeEdit',
inputFields: {
id: {
type: new GraphQLNonNull(GraphQLID),
},
example: {
type: GraphQLString,
},
},
mutateAndGetPayload: async ({ id, example }) => {
// TODO: mutation logic
return {
id: id,
error: null,
};
},
outputFields: {
awesomeEdge: {
type: AwesomeConnection.edgeType,
resolve: async({ id }, args, { user }) => {
// TODO: load new edge from loader
const awesome = await AwesomeLoader.load(
user, id
);
// Returns null if no node was loaded
if (!awesome) {
return null;
}
return {
cursor: toGlobalId('awesome', awesome),
node: awesome,
};
},
},
awesome: {
type: AwesomeType,
resolve: async ({ user, id }) => {
if (!user || !id) {
return null;
}
return await AwesomeLoader.load(user, id);
},
},
error: {
type: GraphQLString,
resolve: ({ error }) => error,
},
},
});
Using the --schema
option with a Mongoose schema, the mutations would be generated like this:
`./src/model/Comment.js`
```js import mongoose from 'mongoose'; const { ObjectId } = mongoose.Schema.Types;const Schema = new mongoose.Schema({ content: { type: String, description: 'Comment content in the original language', required: true, }, user: { type: ObjectId, ref: 'User', indexed: true, description: 'User that created this comment', required: true, }, owner: { type: ObjectId, required: true, description: 'Object that owns of this product. References to Product, Posts or other comment.', }, }, { collection: 'comment', timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt', }, });
export default mongoose.model('Comment', Schema);
</details>
<details>
<summary>`./src/mutation/CommentAddMutation.js`</summary>
```js
import {
GraphQLString,
GraphQLID,
GraphQLNonNull,
GraphQLNonNull,
GraphQLNonNull,
} from 'graphql';
import {
mutationWithClientMutationId,
toGlobalId,
} from 'graphql-relay';
import CommentLoader from '../loader/CommentLoader';
import CommentConnection from '../connection/CommentConnection';
export default mutationWithClientMutationId({
name: 'CommentAdd',
inputFields: {
content: {
type: new GraphQLNonNull(GraphQLString),
},
user: {
type: new GraphQLNonNull(GraphQLID),
},
owner: {
type: new GraphQLNonNull(GraphQLID),
},
},
mutateAndGetPayload: async ({ example }) => {
// TODO: mutation logic
return {
// id: id, // ID of the newly created row
error: null,
};
},
outputFields: {
commentEdge: {
type: CommentConnection.edgeType,
resolve: async({ id }, args, { user }) => {
// TODO: load new edge from loader
const comment = await CommentLoader.load(
user, id
);
// Returns null if no node was loaded
if (!comment) {
return null;
}
return {
cursor: toGlobalId('comment', comment),
node: comment,
};
},
},
error: {
type: GraphQLString,
resolve: ({ error }) => error,
},
},
});
`./src/mutation/CommentEditMutation.js`
```js import { GraphQLString, GraphQLID, GraphQLNonNull, } from 'graphql'; import { mutationWithClientMutationId, toGlobalId, } from 'graphql-relay';import CommentType from '../type/CommentType'; import CommentLoader from '../loader/CommentLoader'; import CommentConnection from '../connection/CommentConnection';
export default mutationWithClientMutationId({ name: 'CommentEdit', inputFields: { id: { type: new GraphQLNonNull(GraphQLID), }, content: { type: new GraphQLNonNull(GraphQLString), }, user: { type: new GraphQLNonNull(GraphQLID), }, owner: { type: new GraphQLNonNull(GraphQLID), }, }, mutateAndGetPayload: async ({ id, example }) => { // TODO: mutation logic
return {
id: id,
error: null,
};
}, outputFields: { commentEdge: { type: CommentConnection.edgeType, resolve: async({ id }, args, { user }) => { // TODO: load new edge from loader
const comment = await CommentLoader.load(
user, id
);
// Returns null if no node was loaded
if (!comment) {
return null;
}
return {
cursor: toGlobalId('comment', comment),
node: comment,
};
},
},
comment: {
type: CommentType,
resolve: async ({ user, id }) => {
if (!user || !id) {
return null;
}
return await CommentLoader.load(user, id);
},
},
error: {
type: GraphQLString,
resolve: ({ error }) => error,
},
}, });
</details>
<p> </p>
##### `-c`, `--connection` - Create a Relay connection
The following command will generate the file `AwesomeConnection` importing `AwesomeType` under the folder `./src/connection`:
```sh
create-graphql --connection Awesome
`./src/connection/AwesomeConnection.js`
```js import { connectionDefinitions } from 'graphql-relay';import AwesomeType from '../type/AwesomeType';
export default connectionDefinitions({ name: 'Awesome', nodeType: AwesomeType, });
</details>
<p> </p>
##### `-l`, `--loader` - Create a GraphQL loader
The following command will generate the file `AwesomeLoader` importing `AwesomeConnection` under the folder `./src/loader`:
```sh
create-graphql --loader Awesome
`./src/loader/AwesomeLoader.js`
```js import DataLoader from 'dataloader'; import ConnectionFromMongoCursor from '../connection/ConnectionFromMongoCursor'; import AwesomeModel from '../model/Awesome';type AwesomeType = { id: string, exampleField: string, }
export default class Awesome { id: string; exampleField: string;
static AwesomeLoader = new DataLoader( ids => Promise.all( ids.map(id => AwesomeModel.findOne({ _id: id }) ), ), );
constructor(data: AwesomeType) { this.id = data.id; this.exampleField = data.exampleField; }
static viewerCanSee(viewer, data) { // TODO: handle security
return true;
}
static async load(viewer, id) { const data = await Awesome.AwesomeLoader.load(id);
return Awesome.viewerCanSee(viewer, data) ? new Awesome(data) : null;
}
static clearCache(id) { return Awesome.AwesomeLoader.clear(id); }
static async loadAwesome(viewer, args) { // TODO: load multiple rows
const Awesome = [];
return ConnectionFromMongoCursor.connectionFromMongoCursor(
viewer, Awesome, args, Awesome.load
);
}
}
</details>
<p> </p>
#### `-`, `--help` - Output all the available commands
## Configuration file
You may customize the folders that the generated files will be created on by using a `.graphqlrc` file on the root folder with the following content:
```json
{
"directories": {
"source": "src",
"connection": "graphql/connection",
"loader": "graphql/loader",
"model": "models/models",
"mutation": "graphql/mutation",
"type": "graphql/type"
}
}
-
Fork this repository;
-
Clone the forked version of create-graphql:
git clone git@github.com:<your_username>/create-graphql.git
-
Install lerna/lerna
npm install --global lerna@prerelease
-
Install the main package depencies
yarn
or
npm i
-
Bootstrap all packages
lerna bootstrap
This will install all dependencies of all
subpackages
and link them properly -
Link the
generator
packagecd packages/generator npm link
-
Watch all packages (create-graphql and generator)
npm run watch
-
Create a new branch
git checkout -b feature/more_awesomeness
-
Make your changes
-
Run the CLI with your changes
node packages/create-graphql/dist --help
-
Commit your changes and push your branch
git add . git commit -m 'more awesome for create-graphql' git push origin feature/more_awesomeness
-
Open your Pull Request
-
Have your Pull Request merged! 😎
Open an issue, I will be glad to discuss your suggestions!
MIT © Lucas Bento