{"id":26748376,"url":"https://github.com/runabol/giraphe","last_synced_at":"2025-08-13T11:53:40.745Z","repository":{"id":94884143,"uuid":"114549901","full_name":"runabol/giraphe","owner":"runabol","description":"Headless Java CMS Framework","archived":false,"fork":false,"pushed_at":"2018-02-10T21:07:57.000Z","size":55,"stargazers_count":57,"open_issues_count":1,"forks_count":9,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-14T22:14:40.172Z","etag":null,"topics":["cms","framework","graphql","headless-cms","java","postgresql","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/runabol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2017-12-17T16:05:19.000Z","updated_at":"2024-05-23T02:36:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"dd13693d-ca9c-4177-8889-a3190a857557","html_url":"https://github.com/runabol/giraphe","commit_stats":null,"previous_names":["runabol/giraphe"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fgiraphe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fgiraphe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fgiraphe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/runabol%2Fgiraphe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/runabol","download_url":"https://codeload.github.com/runabol/giraphe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248968917,"owners_count":21191162,"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":["cms","framework","graphql","headless-cms","java","postgresql","spring-boot"],"created_at":"2025-03-28T10:17:36.560Z","updated_at":"2025-04-14T22:14:46.845Z","avatar_url":"https://github.com/runabol.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Giraphe\n\nGiraphe is a [Spring Boot](https://projects.spring.io/spring-boot/) based headless CMS Framework.\n\n# Rationale \n\nTraditionally, CMS products such as Wordpress, Drupal and the like were heavily focused on the web. They were eseentially an HTML rendering engine. But today, you can't really just exist on the web. With the advent of Smart Phones, Smart TVs, Smart Watches, [Smart Toilets](https://www.cnet.com/how-to/smart-toilets-make-your-bathroom-high-tech) and the rest of them you can't rely anymore on rendering HTML exclusively for your app exprience. \n\nWhat you really need is a platform-agnostic data exchange format which can be consumed by any of your apps. Be it web, smart devices, whatever. \n\nYes, REST/JSON could be a good fit but you are going to need a little help with wrangling all that semi-structured data of yours. \n\nLuckily, the bright folks in Facebook created a standard called [GraphQL](http://graphql.org/) which brings the much needed civilization to the world of web services. While REST APIs are great and I am personally a big fan of them, they suffer from a few major drawbacks which GraphQL seeks to correct: \n\n1. Fine-grained control over what you get from the server. In a traditional REST API you might make a call like so: \n\n```\nGET /person/1234\n```\n\nAnd you might get something like:\n\n```\n{\n   \"firstName\":\"Joe\",\n   \"lastName\":\"Jones\",\n   \"favorites\":[..],\n   \"friends\":[...],\n   \"prefs\":[...],\n   ... ---\n   ...   |  \u003c a bunch of other data\n   ... --- \n}\n```\n\nThen if you need to add additional piece of data to your response you just add that and hope to god that no client will decide to break because you added a new property. Moreover, some clients much want only a sliver of this response -- say just the first name of the person -- but you are giving them ALL the data whether they like it or not. Aside from the bandwidth cost, you might also have to make multiple roundrips to your database to fetch all that unnecessary data. Doesn't sound too efficient now does it? \n\nEnter GraphQL. \n\nYou want the person's name and nothing else. No problem :\n\n```\nPOST /graphql\n\n{\n   getPerson (id:\"1234\") {\n      firstName\n      lastName\n   }\n}\n```\n\nWhich will give you back something like:\n\n```\n  {\n     \"data\": {\n        \"getPerson\": {\n           \"firstName\":\"Joe\",\n           \"lastName\":\"Jones\"\n        }\n     }\n  }\n```\n\nNow, that's a whole lot better. No extra calls to the database to fetch his `favorites`, his `friends` etc.\n\n2. Versioning\n\nIn REST world, when you need to add new properties to your response you can either take the chance of adding them and hoping nothing breaks or you can add `/v2/person/:id` endpoint and add your properties there. The problem is that now you have two endpoints which are semantically very similar -- one being a superset of the other. In GraphQL, since clients are *explicitly* asking for what they want back from the server they are not going to break when you add any new properties.\n\n3. Schema\n\nBecause the shape of a GraphQL query closely matches the result, you can predict what the query will return without knowing that much about the server. But it's useful to have an exact description of the data we can ask for - what fields can we select? What kinds of objects might they return? What fields are available on those sub-objects? That's where the schema comes in.\n\nEvery GraphQL service defines a set of types which completely describe the set of possible data you can query on that service. Then, when queries come in, they are validated and executed against that schema.\n\n4. Documentation\n\nA nice outcome of having to define your schema is that you sort of get the documentation of your API for free. Client developers can now interrogate your API and find what you have available without you having to explicitly write seperate API docs.\n\n5. Tooling \n\nAnother nice outcome of having a schema is having tooling that lets you visualize your API and interact with it in real-time. One such tool is [GraphiQL](https://github.com/graphql/graphiql) which supports auto-completion and other nifty features.\n \n# Weapons of Choice\n\n1. Java 1.8\n2. Spring Boot\n3. GraphQL-Java\n4. PostgreSQL\n\n# Concepts\n\nGiraphe relies on a handful of core data structures: \n\n## Graph\n\nA Graph is actually an extermely simple yet powerful data structure:\n\n![alt text](graph.png \"Graph\")\n\nIt is composed of only two basic units:\n\n### Node\n\nA Node (the round elements in the Graph above) is the basic unit of data. Another word for it could be Object or an Entity but Node is the standard jargon in the Graph world.\n\nNodes are composed of key-value pairs.\n\nNodes are grouped by a type which is just a simple String.\n\nAlmost anything can be modelled as a Node: people, cars, invoices, transactions, movies, app pages etc.  \n\n### Edge\n\nAn edge represents a link between two nodes. \n\nAgain, this could be called a Link or a Connection but we'll stick with Edge.\n\nEdges also have a type they can also have key-value pairs associated with them.\n\n### That's it!\n\nAnd between these basic primitive data structures you can represent just about anything.\n\n# Getting Started\n\n## Installation\n\n1. Maven dependency\n\n```\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.creactiviti\u003c/groupId\u003e\n  \u003cartifactId\u003egiraphe\u003c/artifactId\u003e\n  \u003cversion\u003e0.0.1-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n2. Install PostgreSQL\n\nIf you have Docker and you want to spin one up easily run:\n\n```\ndocker run --name postgres -e POSTGRES_DB=giraphe -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=password -d -p 5432:5432 postgres:9.6.5-alpine\n```\n\n3. Initialize the database:\n\n```\nmvn clean spring-boot:run -Dspring.datasource.initialize=true -Dspring.datasource.url=jdbc:postgresql://localhost:5432/giraphe -Dspring.datasource.username=postgres -Dspring.datasource.password=password\n```\n\n## Usage \n\n```\n@Component\npublic class SomeComponent {\n  \n  @Autowired private Graph g;\n\n  public void doStuff () {\n  \n    // building a Movie node instance\n    Node movie = SimpleNode.builder(g)\n                           .type(\"Movie\")\n                           .property(\"title\", \"Terminator\")\n                           .build();\n                        \n    // adding the movie to the Graph\n    movie = g.add(movie);\n    \n    // building a director node instance\n    Node director = SimpleNode.builder(g)\n                              .type(\"Director\")\n                              .property(\"name\", \"James Cameron\")\n                              .build();\n                        \n    // adding the director to the graph.\n    director = g.add(director);\n    \n    // creating a \"directed\" link between the director\n    // and the movie\n    Edge directed = SimpleEdge.builder(g)\n                              .fromNodeId(director.id())\n                              .type(\"directed\")\n                              .toNodeId(movie.id())\n                              .build();\n    \n    directed = g.add(directed);\n    \n  }\n\n}\n\n```\n\n## GraphQL\n\nNow we can expose our data in a fine-grained manner using GraphQL:\n\n1. Define the GraphQL Schema:\n\n```\n@Component\npublic class Movie implements TypeBuilder {\n  \n  public static final String NAME = \"Movie\";\n  public static final GraphQLTypeReference REF = Types.ref(NAME);\n\n  @Override\n  public GraphQLType build() {\n    return Types.elementTypeBuilder()\n                .name(NAME)\n                .field(Fields.stringField(\"title\"))\n                .field(Fields.spelField(\"directors\", \"${source.from('directed')}\") // SPEL Expression\n                             .type(Types.list(Director.REF)))\n                .build();\n  }\n\n}\n```\n\n```\n@Component\npublic class Director implements TypeBuilder {\n\n  public static final String NAME = \"Director\";\n  public static final GraphQLTypeReference REF = Types.ref(NAME);\n  \n  @Override\n  public GraphQLType build() {\n    return Types.nodeTypeBuilder()\n                .name(NAME)\n                .field(Fields.stringField(\"name\"))\n                .build();\n  }\n\n}\n```\n\n```\n@Component\npublic class AddMovieMutation implements MutationBuilder {\n  \n  @Autowired private Graph g;\n\n  @Override\n  public void build (Builder aBuilder) {\n    aBuilder.field(Fields.field(\"addMovie\")\n                         .type(Movie.REF)\n                         .argument(Arguments.notNullStringArgument(\"title\"))\n                         .dataFetcher((env) -\u003e {\n                           Node node = SimpleNode.builder(g) \n                                                 .type(Movie.NAME)\n                                                 .properties(env.getArguments())\n                                                 .build();\n                           return g.add(node);\n                         }));\n  }\n\n}\n```\n\n```\n@Component\npublic class AddDirectorMutation implements MutationBuilder {\n  \n  @Autowired private Graph g;\n\n  @Override\n  public void build (Builder aBuilder) {\n    aBuilder.field(Fields.field(\"addDirector\")\n                         .type(Director.REF)\n                         .argument(Arguments.notNullStringArgument(\"name\"))\n                         .dataFetcher((env) -\u003e {\n                           Node node = SimpleNode.builder(g) \n                                                 .type(Director.NAME)\n                                                 .properties(env.getArguments())\n                                                 .build();\n                           return g.add(node);\n                         }));\n  }\n\n}\n```\n\n```\n@Component\npublic class AddMovieDirectorMutation implements MutationBuilder {\n  \n  @Autowired private Graph g;\n\n  @Override\n  public void build (Builder aBuilder) {\n    aBuilder.field(Fields.field(\"addMovieDirector\")\n                         .type(Scalars.GraphQLString)\n                         .argument(Arguments.notNullStringArgument(\"movieId\"))\n                         .argument(Arguments.notNullStringArgument(\"directorId\"))\n                         .dataFetcher((env) -\u003e {\n                           Edge edge = SimpleEdge.builder(g) \n                                                 .type(\"directed\")\n                                                 .fromNodeId(env.getArgument(\"directorId\"))\n                                                 .toNodeId(env.getArgument(\"movieId\"))\n                                                 .build();\n                           g.add(edge);\n                           \n                           return \"OK\";\n                         }));\n  }\n\n}\n```\n\n```\n@Component\npublic class GetAllMoviesQuery implements QueryBuilder {\n\n  @Autowired private Graph g;\n  \n  @Override\n  public void build(Builder aBuilder) {\n    aBuilder.field(Fields.field(\"getAllMovies\")\n                         .type(Types.list(Movie.REF))\n                         .dataFetcher((env) -\u003e {\n                           return g.nodes()\n                                   .hasType(Movie.NAME);\n                         }));\n  }\n\n}\n```\n\nNow let's add a movie through GraphQL: \n\n```\n$ curl -s -X POST -H \"Content-Type:application/json\" -d '{\"query\":\"mutation { addMovie (title:\\\"Titanic\\\") { id title } }\"}' http://localhost:8080/graphql | jq .\n```\n\nNext, let's add a director:\n\n```\ncurl -s -X POST -H \"Content-Type:application/json\" -d '{\"query\":\"mutation { addDirector(name:\\\"James Cameron\\\"){ id name } }\"}' http://localhost:8080/graphql | jq .\n```\n\nFinally, let's link the movie and the director:\n\n```\ncurl -s -X POST -H \"Content-Type:application/json\" -d '{\"query\":\"mutation { addMovieDirector(movieId:\\\"\u003cMOVIE_ID_GOES_HERE\u003e\\\" directorId:\\\"\u003cDIRECTOR_ID_GOES_HERE\u003e\\\") }\"}' http://localhost:8080/graphql | jq .\n```\n\nNow let's query for all our movies:\n\n```\n$  curl -s -X POST -H \"Content-Type:application/json\" -d '{\"query\":\"{ getAllMovies { id title directors { name } } }\"}' http://localhost:8080/graphql | jq .\n```\n\nWhich should give you back something like:\n\n``` \n{\n  \"data\": {\n    \"getAllMovies\": [\n      {\n        \"id\": \"035fec100e6c4e228237eebca84ea257\",\n        \"title\": \"Titanic\",\n        \"directors\": [\n           {\n              \"name\":\"James Cameron\"\n           }\n        }\n      }\n    ]\n  },\n  \"errors\": [],\n  \"extensions\": null\n}\n```\n\n## GraphiQL\n\nIf you want to interact with the GraphQL server using a visual query builder try:\n\n```\ndocker run --name=graphiql -p 9100:8080 -d -e GRAPHQL_SERVER=http://\u003cYOUR_IP_GOES_HERE\u003e:8080/graphql creactiviti/graphiql\n```\n\nAnd the go to [http://localhost:9100](http://localhost:9100)\n\n# License\n\nGiraphe is released under version 2.0 of the Apache License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frunabol%2Fgiraphe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frunabol%2Fgiraphe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frunabol%2Fgiraphe/lists"}