{"id":13499544,"url":"https://github.com/andreas/ocaml-graphql-server","last_synced_at":"2025-04-13T04:09:24.605Z","repository":{"id":12882088,"uuid":"73019263","full_name":"andreas/ocaml-graphql-server","owner":"andreas","description":"GraphQL servers in OCaml","archived":false,"fork":false,"pushed_at":"2024-03-03T07:58:21.000Z","size":683,"stargazers_count":622,"open_issues_count":28,"forks_count":59,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-13T02:14:18.106Z","etag":null,"topics":["graphql","ocaml"],"latest_commit_sha":null,"homepage":null,"language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreas.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.md","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-06T21:12:00.000Z","updated_at":"2025-01-06T23:19:53.000Z","dependencies_parsed_at":"2024-01-03T06:15:13.890Z","dependency_job_id":"f59e1eab-67c4-485c-88c6-ad4869b0d0fd","html_url":"https://github.com/andreas/ocaml-graphql-server","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas%2Focaml-graphql-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas%2Focaml-graphql-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas%2Focaml-graphql-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreas%2Focaml-graphql-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreas","download_url":"https://codeload.github.com/andreas/ocaml-graphql-server/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248654095,"owners_count":21140236,"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":["graphql","ocaml"],"created_at":"2024-07-31T22:00:34.622Z","updated_at":"2025-04-13T04:09:24.580Z","avatar_url":"https://github.com/andreas.png","language":"OCaml","readme":"GraphQL Servers in OCaml\n-----------------------------------------------\n\n![Build Status](https://travis-ci.org/andreas/ocaml-graphql-server.svg?branch=master)\n\nThis repo contains a library for creating GraphQL servers in OCaml. Note that the API is still under active development.\n\nCurrent feature set:\n\n- [x] Type-safe schema design\n- [x] GraphQL parser in pure OCaml using Menhir\n- [x] Query execution\n- [x] Introspection of schemas\n- [x] Arguments for fields\n- [x] Allows variables in queries\n- [x] Lwt support\n- [x] Async support\n- [x] Example with HTTP server and GraphiQL\n- [x] GraphQL Subscriptions\n\n## Documentation\n\nFour OPAM packages are provided:\n\n- `graphql` provides the core functionality and is IO-agnostic. It provides a functor `Graphql.Schema.Make(IO)` to instantiate with your own IO monad.\n- `graphql-lwt` provides the module `Graphql_lwt.Schema` with [Lwt](https://github.com/ocsigen/lwt) support in field resolvers.\n- `graphql-async` provides the module `Graphql_async.Schema` with [Async](https://github.com/janestreet/async) support in field resolvers.\n- `graphql_parser` provides query parsing functionality.\n- `graphql-cohttp` allows exposing a schema over HTTP using [Cohttp](https://github.com/mirage/ocaml-cohttp).\n\nAPI documentation:\n\n- [`graphql`](https://andreas.github.io/ocaml-graphql-server/graphql)\n- [`graphql-lwt`](https://andreas.github.io/ocaml-graphql-server/graphql-lwt)\n- [`graphql-async`](https://andreas.github.io/ocaml-graphql-server/graphql-async)\n- [`graphql_parser`](https://andreas.github.io/ocaml-graphql-server/graphql_parser)\n- [`graphql-cohttp`](https://andreas.github.io/ocaml-graphql-server/graphql-cohttp)\n\n## Examples\n\n### GraphiQL\n\nTo run a sample GraphQL server also serving GraphiQL, do the following:\n\n```bash\nopam install dune graphql-lwt graphql-cohttp cohttp-lwt-unix\ngit clone git@github.com:andreas/ocaml-graphql-server.git\ndune exec examples/server.exe\n```\n\nNow open [http://localhost:8080/graphql](http://localhost:8080/graphql).\n\n### Defining a Schema\n\n```ocaml\nopen Graphql\n\ntype role = User | Admin\ntype user = {\n  id   : int;\n  name : string;\n  role : role;\n}\n\nlet users = [\n  { id = 1; name = \"Alice\"; role = Admin };\n  { id = 2; name = \"Bob\"; role = User }\n]\n\nlet role = Schema.(enum \"role\"\n  ~doc:\"The role of a user\"\n  ~values:[\n    enum_value \"USER\" ~value:User;\n    enum_value \"ADMIN\" ~value:Admin;\n  ]\n)\n\nlet user = Schema.(obj \"user\"\n  ~doc:\"A user in the system\"\n  ~fields:[\n    field \"id\"\n      ~doc:\"Unique user identifier\"\n      ~typ:(non_null int)\n      ~args:Arg.[]\n      ~resolve:(fun info p -\u003e p.id)\n    ;\n    field \"name\"\n      ~typ:(non_null string)\n      ~args:Arg.[]\n      ~resolve:(fun info p -\u003e p.name)\n    ;\n    field \"role\"\n      ~typ:(non_null role)\n      ~args:Arg.[]\n      ~resolve:(fun info p -\u003e p.role)\n  ]\n)\n\nlet schema = Schema.(schema [\n  field \"users\"\n    ~typ:(non_null (list (non_null user)))\n    ~args:Arg.[]\n    ~resolve:(fun info () -\u003e users)\n])\n```\n\n### Running a Query\n\nWithout variables:\n\n```ocaml\nmatch Graphql_parser.parse \"{ users { name } }\" with\n| Ok query -\u003e Graphql.Schema.execute schema ctx query\n| Error err -\u003e failwith err\n```\n\nWith variables parsed from JSON:\n\n```ocaml\nmatch Graphql_parser.parse \"{ users(limit: $x) { name } }\" with\n| Ok query -\u003e\n    let json_variables = Yojson.Basic.(from_string \"{\\\"x\\\": 42}\" |\u003e Util.to_assoc) in\n    let variables = (json_variables :\u003e (string * Graphql_parser.const_value) list)\n    Graphql.Schema.execute schema ctx ~variables query\n| Error err -\u003e\n    failwith err\n```\n\n### Recursive Objects\n\nThe function `Schema.fix` can be used to define both self-recursive and mutually recursive objects:\n\n```ocaml\n(* self-recursive *)\ntype tweet = {\n  id : int;\n  replies : tweet list;\n}\n\nlet tweet = Schema.(fix (fun recursive -\u003e\n  recursive.obj \"tweet\"\n    ~fields:(fun tweet -\u003e [\n      field \"id\"\n        ~typ:(non_null int)\n        ~args:Arg.[]\n        ~resolve:(fun info t -\u003e t.id)\n        ;\n      field \"replies\"\n        ~typ:(non_null (list (non_null tweet)))\n        ~args:Arg.[]\n        ~resolve:(fun info t -\u003e t.replies)\n    ])))\n```\n\n```ocaml\n(* mutually recursive *)\nlet foo, bar = Schema.(fix (fun recursive -\u003e\n  let foo = recursive.obj \"foo\" ~fields:(fun (_, bar) -\u003e [\n      field \"bar\"\n        ~typ:bar\n        ~args:Arg.[]\n        ~resolve:(fun info foo -\u003e foo.bar)\n    ])\n  in\n  let bar = recursive.obj \"bar\" ~fields:(fun (foo, _) -\u003e [\n    field \"foo\"\n      ~typ:foo\n      ~args:Arg.[]\n      ~resolve:(fun info bar -\u003e bar.foo)\n      ])\n  in\n  foo, bar))\n```\n\n### Lwt Support\n\n```ocaml\nopen Lwt.Infix\nopen Graphql_lwt\n\nlet schema = Schema.(schema [\n  io_field \"wait\"\n    ~typ:(non_null float)\n    ~args:Arg.[\n      arg \"duration\" ~typ:float;\n    ]\n    ~resolve:(fun info () -\u003e\n      Lwt_result.ok (Lwt_unix.sleep duration \u003e|= fun () -\u003e duration)\n    )\n])\n```\n\n### Async Support\n\n```ocaml\nopen Core.Std\nopen Async.Std\nopen Graphql_async\n\nlet schema = Schema.(schema [\n  io_field \"wait\"\n    ~typ:(non_null float)\n    ~args:Arg.[\n      arg \"duration\" ~typ:float;\n    ]\n    ~resolve:(fun info () -\u003e\n      after (Time.Span.of_float duration) \u003e\u003e| fun () -\u003e duration\n    )\n])\n```\n\n### Arguments\n\nArguments for a field can either be required, optional or optional with a default value:\n\n```ocaml\nSchema.(obj \"math\"\n  ~fields:(fun _ -\u003e [\n    field \"sum\"\n      ~typ:int\n      ~args:Arg.[\n        arg  \"x\" ~typ:(non_null int); (* \u003c-- required *)\n        arg  \"y\" ~typ:int;            (* \u003c-- optional *)\n        arg' \"z\" ~typ:int ~default:7  (* \u003c-- optional w/ default *)\n      ]\n      ~resolve:(fun info () x y z -\u003e\n        let y' = match y with Some n -\u003e n | None -\u003e 42 in\n        x + y' + z\n      )\n  ])\n)\n```\n\nNote that you must use `arg'` to provide a default value.\n\n### Subscriptions\n\n```ocaml\nSchema.(schema [\n     ...\n  ]\n  ~subscriptions:[\n    subscription_field \"user_created\"\n      ~typ:(non_null user)\n      ~resolve:(fun info -\u003e\n        let user_stream, push_to_user_stream = Lwt_stream.create () in\n        let destroy_stream = (fun () -\u003e push_to_user_stream None) in\n        Lwt_result.return (user_stream, destroy_stream))\n    ])\n```\n\n### HTTP Server\n\nUsing Lwt:\n\n```ocaml\nopen Graphql_lwt\n\nlet schema = Schema.(schema [\n  ...\n])\n\nmodule Graphql_cohttp_lwt = Graphql_cohttp.Make (Schema) (Cohttp_lwt.Body)\n\nlet () =\n  let callback = Graphql_cohttp_lwt.make_callback (fun _req -\u003e ()) schema in\n  let server = Cohttp_lwt_unix.Server.make ~callback () in\n  let mode = `TCP (`Port 8080) in\n  Cohttp_lwt_unix.Server.create ~mode server\n  |\u003e Lwt_main.run\n```\n\n## Design\n\nOnly valid schemas should pass the type checker. If a schema compiles, the following holds:\n\n1. The type of a field agrees with the return type of the resolve function.\n2. The arguments of a field agrees with the accepted arguments of the resolve function.\n3. The source of a field agrees with the type of the object to which it belongs.\n4. The context argument for all resolver functions in a schema agree.\n\nThe following blog posts introduces the core design concepts:\n\n- https://andreas.github.io/2017/11/29/type-safe-graphql-with-ocaml-part-1/\n- https://andreas.github.io/2018/01/05/modeling-graphql-type-modifiers-with-gadts-part-2/\n- https://andreas.github.io/2019/05/20/graphql-resolver-arguments-as-diff-lists-part-3/\n","funding_links":[],"categories":["Libraries","OCaml","Implementations"],"sub_categories":["OCaml Libraries","OCaml"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreas%2Focaml-graphql-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreas%2Focaml-graphql-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreas%2Focaml-graphql-server/lists"}