命令行实用工具来构建GraphQL生产就绪的服务器
命令行实用工具来构建GraphQL生产就绪的服务器
Node.js 命令行实用程序
共439Star
详细介绍
Create GraphQL
Create production-ready GraphQL servers
About
Check out the post on Medium!
Install
npm install --global create-graphql
Update
npm update --global create-graphql
Usage
It can generate:
- Mutations (create and edit);
- Types;
- Connections;
- Loaders;
- Tests (with Jest);
- Generate from a Mongoose schema (with
--schema
option).
Examples
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.
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.
Options
init
- Create a brand-new GraphQL server
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.
generate
-t
, --type
- Create a GraphQL type
The following command will generate the file AwesomeType
under the folder ./src/type
:
create-graphql --type Awesome
./src/type/AwesomeType.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,
},
}),
});
Using the --schema
option with a Mongoose schema, the type would be generated like this:
./src/model/Comment.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
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,
},
}),
});
-m
, --mutation
- Create GraphQL mutations
The following command will generate the files AwesomeAddMutation
& AwesomeEditMutation
under the folder ./src/mutation
:
create-graphql --mutation Awesome
./src/mutation/AwesomeAddMutation.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,
},
},
});
./src/mutation/AwesomeEditMutation.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
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/mutation/CommentAddMutation.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
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,
},
},
});
-c
, --connection
- Create a Relay connection
The following command will generate the file AwesomeConnection
importing AwesomeType
under the folder ./src/connection
:
create-graphql --connection Awesome
./src/connection/AwesomeConnection.js
import { connectionDefinitions } from 'graphql-relay';
import AwesomeType from '../type/AwesomeType';
export default connectionDefinitions({
name: 'Awesome',
nodeType: AwesomeType,
});
-l
, --loader
- Create a GraphQL loader
The following command will generate the file AwesomeLoader
importing AwesomeConnection
under the folder ./src/loader
:
create-graphql --loader Awesome
./src/loader/AwesomeLoader.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
);
}
}
-
, --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:
{
"directories": {
"source": "src",
"connection": "graphql/connection",
"loader": "graphql/loader",
"model": "models/models",
"mutation": "graphql/mutation",
"type": "graphql/type"
}
}
How to contribute
- 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 properlyLink 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!
?
Feedback?
Open an issue, I will be glad to discuss your suggestions!
License
MIT © Lucas Bento
-
122 Star
-
7729 Star
-
0 Star
-
253 Star
-
705 Star
-
0 Star