{"id":13769669,"url":"https://github.com/Distelli/graphql-apigen","last_synced_at":"2025-05-11T02:33:08.255Z","repository":{"id":15216722,"uuid":"74189838","full_name":"Distelli/graphql-apigen","owner":"Distelli","description":"Generate Java APIs with GraphQL Schemas","archived":false,"fork":false,"pushed_at":"2024-01-23T16:53:46.000Z","size":147,"stargazers_count":66,"open_issues_count":18,"forks_count":20,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-09-29T09:24:32.074Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Distelli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-11-19T05:47:23.000Z","updated_at":"2024-08-24T09:54:26.000Z","dependencies_parsed_at":"2024-08-03T17:02:28.934Z","dependency_job_id":null,"html_url":"https://github.com/Distelli/graphql-apigen","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Distelli%2Fgraphql-apigen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Distelli%2Fgraphql-apigen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Distelli%2Fgraphql-apigen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Distelli%2Fgraphql-apigen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Distelli","download_url":"https://codeload.github.com/Distelli/graphql-apigen/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225004650,"owners_count":17405653,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T17:00:30.309Z","updated_at":"2024-11-17T05:31:20.883Z","avatar_url":"https://github.com/Distelli.png","language":"Java","funding_links":[],"categories":["Schema Libraries"],"sub_categories":["Schema First"],"readme":"# graphql-apigen\n\nGenerate Java APIs with GraphQL Schemas in order to facilitate \"schema first\" development.\n\n### Posts Example\n\nCreate a file to define your schema. In this example we are creating the `schema/posts.graphql` file:\n\n```graphql\ntype Author @java(package:\"com.distelli.posts\") {\n    id: Int! # the ! means that every author object _must_ have an id\n    firstName: String\n    lastName: String\n    posts: [Post] # the list of Posts by this author\n}\n\ntype Post @java(package:\"com.distelli.posts\") {\n    id: Int!\n    title: String\n    author: Author\n    votes: Int\n}\n\n# the schema allows the following query:\ntype QueryPosts @java(package:\"com.distelli.posts\") {\n    posts: [Post]\n}\n\ninput InputPost @java(package:\"com.distelli.posts\") {\n    title: String\n    authorId: Int!\n}\n\n# this schema allows the following mutation:\ntype MutatePosts @java(package:\"com.distelli.posts\") {\n    createPost(post:InputPost): Post\n    upvotePost(\n      postId: Int!\n    ): Post\n}\n```\n\nNotice that we annotate the types with a java package name.  The above schema\nwill generate the following java **interfaces** in `target/generated-sources/apigen`\n(in the `com.distelli.posts` package):\n\n* `Author` and `Author.Resolver`\n* `Post` and `Post.Resolver`\n* `QueryPosts`\n* `InputPost`\n* `MutatePosts`\n\nThe `*.Resolver` interfaces are only generated if their is a field named \"id\". This\ninterface may be implemented to resolve a `*.Unresolved` (only the id field defined)\ninto a fully resolved implementation (all fields defined). All interface methods\nhave \"default\" implementations that return null.\n\nEach of these interfaces also have a default inner class named `*.Builder` and\n`*.Impl`. The `*.Builder` will have a no-argument constructor and a constructor\nthat takes the parent interface as an argument. The `*.Builder` will also have a\nmethod `with\u003cFieldName\u003e(\u003cFieldType\u003e)` for each no-arg field which returns the\nbuilder and a `build()` method that creates a `*.Impl`.\n\nAny field that takes arguments will cause a `*.\u003cFieldName\u003eArgs` interface to be\ngenerated with methods for each input field.\n\nAny field that does NOT take arguments will generate method names prefixed with\n\"get\".\n\nFinally, the above schema also generates a Guice module `PostsModule` which adds to\na `Map\u003cString, GraphQLType\u003e` multibinder (the name \"PostsModule\" comes from the\nfilename which defines the schema). See below for information about using Spring for\nDependency Injection.\n\nPutting this all together, we can implement the `QueryPosts` implementation as such:\n\n```java\n    public class QueryPostsImpl implements QueryPosts {\n        private Map\u003cInteger, Post\u003e posts;\n        public QueryPostsImpl(Map\u003cInteger, Post\u003e posts) {\n            this.posts = posts;\n        }\n        @Override\n        public List\u003cPost\u003e getPosts() {\n            return new ArrayList\u003c\u003e(posts.values());\n        }\n    }\n```\n\n...and the `MutatePosts` implementation as such:\n\n```java\n    public class MutatePostsImpl implements MutatePosts {\n        private AtomicInteger nextPostId = new AtomicInteger(1);\n        private Map\u003cInteger, Post\u003e posts;\n        public MutatePostsImpl(Map\u003cInteger, Post\u003e posts) {\n            this.posts = posts;\n        }\n        @Override\n        public Post createPost(MutatePosts.CreatePostArgs args) {\n            InputPost req = args.getPost();\n            Post.Builder postBuilder = new Post.Builder()\n                .withTitle(req.getTitle())\n                .withAuthor(new Author.Unresolved(req.getAuthorId()));\n            Post post;\n            synchronized ( posts ) {\n                Integer id = nextPostId.incrementAndGet();\n                post = postBuilder.withId(id).build();\n                posts.put(id, post);\n            }\n            return post;\n        }\n\n        @Override\n        public Post upvotePost(MutatePosts.UpvotePostArgs args) {\n            synchronized ( posts ) {\n                Post post = posts.get(args.getPostId());\n                if ( null == post ) {\n                    throw new NoSuchEntityException(\"PostId=\"+args.getPostId());\n                }\n                Post upvoted = new Post.Builder(post)\n                    .withVotes(post.getVotes()+1)\n                    .build();\n                posts.put(args.getPostId(), upvoted);\n                return upvoted;\n            }\n        }\n    }\n```\n\n...and the `Author.Resolver` interface as such:\n\n```java\n    public class AuthorResolver implements Author.Resolver {\n        private Map\u003cInteger, Author\u003e authors;\n        public AuthorResolver(Map\u003cInteger, Author\u003e authors) {\n            this.authors = authors;\n        }\n        @Override\n        public List\u003cAuthor\u003e resolve(List\u003cAuthor\u003e unresolvedList) {\n            List\u003cAuthor\u003e result = new ArrayList\u003c\u003e();\n            for ( Author unresolved : unresolvedList ) {\n                // In a real app we would check if it is instanceof Author.Unresolved\n                result.add(authors.get(unresolved.getId()));\n            }\n            return result;\n        }\n    }\n```\n\n...and the `Post.Resolver` interface as such:\n\n```java\n     public class PostResolver implements Post.Resolver {\n        private Map\u003cInteger, Post\u003e posts;\n        public PostResolver(Map\u003cInteger, Post\u003e posts) {\n            this.posts = posts;\n        }\n        @Override\n        public List\u003cPost\u003e resolve(List\u003cPost\u003e unresolvedList) {\n            List\u003cPost\u003e result = new ArrayList\u003c\u003e();\n            for ( Post unresolved : unresolvedList ) {\n                if ( null == unresolved ) {\n                    result.add(null);\n                } else {\n                    result.add(posts.get(unresolved.getId()));\n                }\n            }\n            return result;\n        }\n    }\n```\n\n...and you can use Guice to wire it all together as such (see below on\nusing this from Spring):\n\n```java\n    public class MainModule implements AbstractModule {\n        @Override\n        protected void configure() {\n            // Create the \"data\" used by the implementations:\n            Map\u003cInteger, Post\u003e posts = new LinkedHashMap\u003c\u003e();\n            Map\u003cInteger, Author\u003e authors = new LinkedHashMap\u003c\u003e();\n            // Install the generated module:\n            install(new PostsModule());\n            // Declare our implementations:\n            bind(Author.Resolver.class)\n                .toInstance(new AuthorResolver(authors));\n            bind(Post.Resolver.class)\n                .toInstance(new PostResolver(posts));\n            bind(MutatePosts.class)\n                .toInstance(new MutatePostsImpl(posts));\n            bind(QueryPosts.class)\n                .toInstance(new QueryPostsImpl(posts));\n        }\n    }\n```\n\n...and to use it:\n\n```java\n    public class GraphQLServlet extends HttpServlet {\n        private static ObjectMapper OM = new ObjectMapper();\n        private GraphQL graphQL;\n        @Inject\n        protected void GraphQLServlet(Map\u003cString, GraphQLType\u003e types) {\n            GraphQLSchema schema = GraphQLSchema.newSchema()\n                .query((GraphQLObjectType)types.get(\"QueryPosts\"))\n                .mutation((GraphQLObjectType)types.get(\"MutatePosts\"))\n                .build(new HashSet\u003c\u003e(types.values()));\n            graphQL = new GraphQL(schema, new BatchedExecutionStrategy());\n        }\n        protected void\tservice(HttpServletRequest req, HttpServletResponse resp) {\n            ExecutionResult result = graphQL.execute(req.getParameter(\"query\"));\n            OM.writeValue(resp.getOutputStream(), result);\n        }\n    }\n```\n\nThis example is also a unit test which can be found\n[here](apigen/src/test/projects/posts/src/test/java/com/disteli/posts/PostsTest.java)\n\n### Using Spring instead of Guice\n\nIf you want to use Spring to wire the components together instead of Guice, you need to \ninstruct Spring to include the generated code in a package-scan. Spring will find the `@Named`\nannotated components and will inject any dependencies (the type resolvers you implement, etc)\n\nFor example, if your code was generated into the package `com.distelli.posts`, the spring\nconfiguration would look like this: \n\n```java \n@ComponentScan(\"com.distelli.posts\")\n@Configuration\npublic class MyAppConfig {\n        ...\n}                                   \n```\n\nTo generate a mapping similar to the guice code above, you can add this to your spring\nconfiguration:\n\n```java\n    @Bean\n    public Map\u003cString, GraphQLType\u003e graphqlTypeMap(List\u003cProvider\u003c? extends GraphQLType\u003e\u003e typeList) {\n        return typeList.stream().map(Provider::get).collect(Collectors.toMap(GraphQLType::getName, Function.identity()));\n    }\n```\n\nThis will take any GraphQLTypes and generate a map of their string name to their implementation.\n\n### Getting started\n\n#### How to use the latest release with Maven\n\nGenerate the code with the following maven:\n\n```xml\n\u003cproject ...\u003e\n  ...\n  \u003cproperties\u003e\n    \u003capigen.version\u003e4.0.0\u003c/apigen.version\u003e\n  \u003c/properties\u003e\n\n  \u003cbuild\u003e\n    \u003cplugins\u003e\n      ...\n      \u003cplugin\u003e\n        \u003cgroupId\u003ecom.distelli.graphql\u003c/groupId\u003e\n        \u003cartifactId\u003egraphql-apigen\u003c/artifactId\u003e\n        \u003cversion\u003e${apigen.version}\u003c/version\u003e\n        \u003cconfiguration\u003e\n          \u003c!-- Optional. This is only needed when using Guice --\u003e\n          \u003cguiceModuleName\u003ecom.example.my.MyGuiceModule\u003c/guiceModuleName\u003e\n          \u003c!-- Optional. This is only needed if you omit the @java(package:\"...\")\n               annotations from your schema types. Using this feature\n               also means your GraphQL schema can NOT be depended upon\n               by GraphQL schemas defined in other maven projects. See:\n               https://github.com/Distelli/graphql-apigen/issues/5#issuecomment-275923555\n          --\u003e\n          \u003cdefaultPackageName\u003ecom.example.my\u003c/defaultPackageName\u003e\n          \u003c!-- Optional. Location of your schema file(s). Default is ${project.basedir}/schema. The \n               expected extension is *.graphql. --\u003e\n          \u003csourceDirectory\u003eschema/folder\u003c/sourceDirectory\u003e\n          \u003c!-- Optional. Output folder for Java source. Default is ${project.basedir}/target/generated-sources/apigen. \n                --\u003e\n          \u003coutputDirectory\u003eoutput/folder\u003c/outputDirectory\u003e          \n        \u003c/configuration\u003e\n        \u003cexecutions\u003e\n          \u003cexecution\u003e\n            \u003cid\u003ejava-apigen\u003c/id\u003e\n            \u003cgoals\u003e\n              \u003cgoal\u003eapigen\u003c/goal\u003e\n            \u003c/goals\u003e\n          \u003c/execution\u003e\n        \u003c/executions\u003e\n      \u003c/plugin\u003e\n    \u003c/plugins\u003e\n  \u003c/build\u003e\n\n  \u003cdependencies\u003e\n    ...\n    \u003c!-- Required by the generated code --\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003ecom.distelli.graphql\u003c/groupId\u003e\n      \u003cartifactId\u003egraphql-apigen-deps\u003c/artifactId\u003e\n      \u003cversion\u003e${apigen.version}\u003c/version\u003e\n    \u003c/dependency\u003e\n\n    \u003c!-- Optional, dependencies if using Guice for Dependency Injection --\u003e\n    \u003cdependency\u003e\n      \u003cgroupId\u003ecom.google.inject\u003c/groupId\u003e\n      \u003cartifactId\u003eguice\u003c/artifactId\u003e\n      \u003cversion\u003e4.0\u003c/version\u003e\n    \u003c/dependency\u003e\n\n    \u003cdependency\u003e\n      \u003cgroupId\u003ecom.google.inject.extensions\u003c/groupId\u003e\n      \u003cartifactId\u003eguice-multibindings\u003c/artifactId\u003e\n      \u003cversion\u003e4.0\u003c/version\u003e\n    \u003c/dependency\u003e\n\n  \u003c/dependencies\u003e\n\n\u003c/project\u003e\n```\n\nBe sure to replace the values above with the correct values (and remove unnecessary configuration properties if the \ndefaults are satisfactory).\n\n### Customizing the Output\n\nYou can customize the generated Java source by copying the [graphql-apigen.stg](apigen/src/main/resources/graphql-apigen.stg) \nfile to the base directory of your project and making any necessary changes. The plugin will automatically use it \ninstead of the one distributed with the library. The template uses the [StringTemplate](https://github.com/antlr/stringtemplate4/blob/master/doc/index.md) \ntemplate language. The model used for the template is defined in [STModel.java](apigen/src/main/java/com/distelli/graphql/apigen/STModel.java).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDistelli%2Fgraphql-apigen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDistelli%2Fgraphql-apigen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDistelli%2Fgraphql-apigen/lists"}