All Projects → andreas → Ocaml Graphql Server

andreas / Ocaml Graphql Server

Licence: mit
GraphQL servers in OCaml

Programming Languages

ocaml
1615 projects

Labels

Projects that are alternatives of or similar to Ocaml Graphql Server

Weaviate
Weaviate is a cloud-native, modular, real-time vector search engine
Stars: ✭ 509 (-11.48%)
Mutual labels:  graphql
Graphql Cost Analysis
A Graphql query cost analyzer.
Stars: ✭ 527 (-8.35%)
Mutual labels:  graphql
Typed Graphqlify
Build Typed GraphQL Queries in TypeScript. A better TypeScript + GraphQL experience.
Stars: ✭ 553 (-3.83%)
Mutual labels:  graphql
Graphql Ruby
Ruby implementation of GraphQL
Stars: ✭ 4,931 (+757.57%)
Mutual labels:  graphql
Brian Lovin Next
My personal site
Stars: ✭ 522 (-9.22%)
Mutual labels:  graphql
Graphql Java
GraphQL Java implementation
Stars: ✭ 5,330 (+826.96%)
Mutual labels:  graphql
Animavita
Trigger life-saving alerts, register animals for adoption and find the closest pet friend to adopt 🐶
Stars: ✭ 508 (-11.65%)
Mutual labels:  graphql
Damn Vulnerable Graphql Application
Damn Vulnerable GraphQL Application is an intentionally vulnerable implementation of Facebook's GraphQL technology, to learn and practice GraphQL Security.
Stars: ✭ 567 (-1.39%)
Mutual labels:  graphql
Tuql
Automatically create a GraphQL server from a SQLite database or a SQL file
Stars: ✭ 526 (-8.52%)
Mutual labels:  graphql
Ethql
A GraphQL interface to Ethereum 🔥
Stars: ✭ 547 (-4.87%)
Mutual labels:  graphql
Graphql Dotnet
GraphQL for .NET
Stars: ✭ 5,031 (+774.96%)
Mutual labels:  graphql
Aws Appsync Chat
Real-Time Offline Ready Chat App written with GraphQL, AWS AppSync, & AWS Amplify
Stars: ✭ 522 (-9.22%)
Mutual labels:  graphql
Rick And Morty Api
The Rick and Morty API
Stars: ✭ 542 (-5.74%)
Mutual labels:  graphql
Edgedb
The next generation relational database.
Stars: ✭ 5,368 (+833.57%)
Mutual labels:  graphql
Starwars
GraphQL 'Star Wars' example using GraphQL for .NET, ASP.NET Core, Entity Framework Core
Stars: ✭ 559 (-2.78%)
Mutual labels:  graphql
Conf Talks
GraphQL на русском — здесь много статей и видео об этой замечательной технологии.
Stars: ✭ 511 (-11.13%)
Mutual labels:  graphql
Fullstack Graphql
A book to learn GraphQL with a hands-on approach
Stars: ✭ 534 (-7.13%)
Mutual labels:  graphql
Kretes
A Programming Environment for TypeScript & Node.js built on top of VS Code
Stars: ✭ 570 (-0.87%)
Mutual labels:  graphql
Pup
The Ultimate Boilerplate for Products.
Stars: ✭ 563 (-2.09%)
Mutual labels:  graphql
Graphql Compose Mongoose
Mongoose model converter to GraphQL types with resolvers for graphql-compose https://github.com/nodkz/graphql-compose
Stars: ✭ 543 (-5.57%)
Mutual labels:  graphql

GraphQL Servers in OCaml

Build Status

This repo contains a library for creating GraphQL servers in OCaml. Note that the API is still under active development.

Current feature set:

  • [x] Type-safe schema design
  • [x] GraphQL parser in pure OCaml using Menhir
  • [x] Query execution
  • [x] Introspection of schemas
  • [x] Arguments for fields
  • [x] Allows variables in queries
  • [x] Lwt support
  • [x] Async support
  • [x] Example with HTTP server and GraphiQL
  • [x] GraphQL Subscriptions

Documentation

Four OPAM packages are provided:

  • graphql provides the core functionality and is IO-agnostic. It provides a functor Graphql.Schema.Make(IO) to instantiate with your own IO monad.
  • graphql-lwt provides the module Graphql_lwt.Schema with Lwt support in field resolvers.
  • graphql-async provides the module Graphql_async.Schema with Async support in field resolvers.
  • graphql_parser provides query parsing functionality.
  • graphql-cohttp allows exposing a schema over HTTP using Cohttp.

API documentation:

Examples

GraphiQL

To run a sample GraphQL server also serving GraphiQL, do the following:

opam install dune graphql-lwt graphql-cohttp cohttp-lwt-unix
git clone [email protected]:andreas/ocaml-graphql-server.git
dune exec examples/server.exe

Now open http://localhost:8080/graphql.

Defining a Schema

open Graphql

type role = User | Admin
type user = {
  id   : int;
  name : string;
  role : role;
}

let users = [
  { id = 1; name = "Alice"; role = Admin };
  { id = 2; name = "Bob"; role = User }
]

let role = Schema.(enum "role"
  ~doc:"The role of a user"
  ~values:[
    enum_value "USER" ~value:User;
    enum_value "ADMIN" ~value:Admin;
  ]
)

let user = Schema.(obj "user"
  ~doc:"A user in the system"
  ~fields:(fun _ -> [
    field "id"
      ~doc:"Unique user identifier"
      ~typ:(non_null int)
      ~args:Arg.[]
      ~resolve:(fun info p -> p.id)
    ;
    field "name"
      ~typ:(non_null string)
      ~args:Arg.[]
      ~resolve:(fun info p -> p.name)
    ;
    field "role"
      ~typ:(non_null role)
      ~args:Arg.[]
      ~resolve:(fun info p -> p.role)
  ])
)

let schema = Schema.(schema [
  field "users"
    ~typ:(non_null (list (non_null user)))
    ~args:Arg.[]
    ~resolve:(fun info () -> users)
])

Running a Query

Without variables:

match Graphql_parser.parse "{ users { name } }" with
| Ok query -> Graphql.Schema.execute schema ctx query
| Error err -> failwith err

With variables parsed from JSON:

match Graphql_parser.parse "{ users(limit: $x) { name } }" with
| Ok query ->
    let json_variables = Yojson.Basic.(from_string "{\"x\": 42}" |> Util.to_assoc) in
    let variables = (json_variables :> (string * Graphql_parser.const_value) list)
    Graphql.Schema.execute schema ctx ~variables query
| Error err ->
    failwith err

Self-Recursive Objects

To allow defining an object that refers to itself, the type itself is provided as argument to the ~fields function. Example:

type tweet = {
  id : int;
  replies : tweet list;
}

let tweet = Schema.(obj "tweet"
  ~fields:(fun tweet -> [
    field "id"
      ~typ:(non_null int)
      ~args:Arg.[]
      ~resolve:(fun info t -> t.id)
    ;
    field "replies"
      ~typ:(non_null (list tweet))
      ~args:Arg.[]
      ~resolve:(fun info t -> t.replies)
  ])
)

Mutually Recursive Objects

Mutually recursive objects can be defined using let rec and lazy:

let rec foo = lazy Schema.(obj "foo"
  ~fields:(fun _ -> [
    field "bar"
      ~typ:Lazy.(force bar)
      ~args.Arg.[]
      ~resolve:(fun info foo -> foo.bar)
  ])
and bar = lazy Schema.(obj "bar"
  ~fields:(fun _ -> [
    field "foo"
      ~typ:Lazy.(force foo)
      ~args.Arg.[]
      ~resolve:(fun info bar -> bar.foo)
  ])

Lwt Support

open Lwt.Infix
open Graphql_lwt

let schema = Schema.(schema [
  io_field "wait"
    ~typ:(non_null float)
    ~args:Arg.[
      arg "duration" ~typ:float;
    ]
    ~resolve:(fun info () ->
      Lwt_result.ok (Lwt_unix.sleep duration >|= fun () -> duration)
    )
])

Async Support

open Core.Std
open Async.Std
open Graphql_async

let schema = Schema.(schema [
  io_field "wait"
    ~typ:(non_null float)
    ~args:Arg.[
      arg "duration" ~typ:float;
    ]
    ~resolve:(fun info () ->
      after (Time.Span.of_float duration) >>| fun () -> duration
    )
])

Arguments

Arguments for a field can either be required, optional or optional with a default value:

Schema.(obj "math"
  ~fields:(fun _ -> [
    field "sum"
      ~typ:int
      ~args:Arg.[
        arg  "x" ~typ:(non_null int); (* <-- required *)
        arg  "y" ~typ:int;            (* <-- optional *)
        arg' "z" ~typ:int ~default:7  (* <-- optional w/ default *)
      ]
      ~resolve:(fun info () x y z ->
        let y' = match y with Some n -> n | None -> 42 in
        x + y' + z
      )
  ])
)

Note that you must use arg' to provide a default value.

Subscriptions

Schema.(schema [
     ...
  ]
  ~subscriptions:[
    subscription_field "user_created"
      ~typ:(non_null user)
      ~resolve:(fun info ->
        let user_stream, push_to_user_stream = Lwt_stream.create () in
        let destroy_stream = (fun () -> push_to_user_stream None) in
        Lwt_result.return (user_stream, destroy_stream))
    ])

HTTP Server

Using Lwt:

open Graphql_lwt

let schema = Schema.(schema [
  ...
])

module Graphql_cohttp_lwt = Graphql_cohttp.Make (Schema) (Cohttp_lwt.Body)

let () =
  let callback = Graphql_cohttp_lwt.make_callback (fun _req -> ()) schema in
  let server = Cohttp_lwt_unix.Server.make ~callback () in
  let mode = `TCP (`Port 8080) in
  Cohttp_lwt_unix.Server.create ~mode server
  |> Lwt_main.run

Design

Only valid schemas should pass the type checker. If a schema compiles, the following holds:

  1. The type of a field agrees with the return type of the resolve function.
  2. The arguments of a field agrees with the accepted arguments of the resolve function.
  3. The source of a field agrees with the type of the object to which it belongs.
  4. The context argument for all resolver functions in a schema agree.

The following blog posts introduces the core design concepts:

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].