{"id":13562017,"url":"https://github.com/jlouis/graphql-erlang","last_synced_at":"2025-04-05T20:08:09.438Z","repository":{"id":40324573,"uuid":"68302586","full_name":"jlouis/graphql-erlang","owner":"jlouis","description":"GraphQL implementation in Erlang.","archived":false,"fork":false,"pushed_at":"2023-11-24T23:51:49.000Z","size":935,"stargazers_count":313,"open_issues_count":45,"forks_count":52,"subscribers_count":30,"default_branch":"master","last_synced_at":"2024-10-29T12:59:28.085Z","etag":null,"topics":["erlang","facebook","graph","graphql"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jlouis.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"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}},"created_at":"2016-09-15T14:43:40.000Z","updated_at":"2024-08-20T09:42:43.000Z","dependencies_parsed_at":"2024-01-14T03:45:01.334Z","dependency_job_id":"13138c02-dedc-4457-9dcc-24b2e1e6209d","html_url":"https://github.com/jlouis/graphql-erlang","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlouis%2Fgraphql-erlang","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlouis%2Fgraphql-erlang/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlouis%2Fgraphql-erlang/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jlouis%2Fgraphql-erlang/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jlouis","download_url":"https://codeload.github.com/jlouis/graphql-erlang/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247393570,"owners_count":20931813,"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":["erlang","facebook","graph","graphql"],"created_at":"2024-08-01T13:01:03.639Z","updated_at":"2025-04-05T20:08:09.412Z","avatar_url":"https://github.com/jlouis.png","language":"Erlang","funding_links":[],"categories":["Erlang"],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.org/shopgun/graphql-erlang.svg?branch=develop)](https://travis-ci.org/shopgun/graphql-erlang)\n\n# A GraphQL Server library - in Erlang\n\nThis project contains the necessary support code to implement GraphQL\nservers in Erlang. Its major use is on top of some other existing\ntransport library, for instance the cowboy web server. When a request\narrives, it can be processed by the GraphQL support library and a\nGraphQL answer can be given. In a way, this replaces all of your REST\nendpoints with a single endpoint: one for Graph Queries.\n\nThis README provides the system overview and its mode of operation.\n\n# Changelog\n\nSee the file `CHANGELOG.md` in the root of the project.\n\n# Status\n\nCurrently, the code implements all of the October 2016 GraphQL\nspecification, except for a few areas:\n\n* Some validators are missing and pending implementation. The\n  important validators are present, however. Missing stuff are all\n  tracked as issues in this repository.\n* Parametrization inside fragments are not yet implemented fully.\n\nIn addition, we are working towards June 2018 compliance. We already\nimplemented many of the changes in the system. But we are still\nmissing some parts. The implementation plan is on a demand driven\nbasis for Shopgun currently, in that we tend to implement things when\nthere is a need for them.\n\n# Documentation\n\nThis is a big library. In order to ease development, we have provided\na complete tutorial for GraphQL Erlang:\n\nhttps://github.com/shopgun/graphql-erlang-tutorial\n\nAlso, the tutorial has a book which describes how the tutorial example\nis implemented in detail:\n\nhttps://shopgun.github.io/graphql-erlang-tutorial/\n\n*NOTE:* Read the tutorial before reading on in this repository if you\nhaven't already. This README gives a very quick overview, but the\ncanonical documentation is the book at the moment.\n\n\n## What is GraphQL\n\nGraphQL is a query language for the web. It allows a client to tell\nthe server what it wants in a declarative way. The server then\nmaterializes a response based on the clients query. This makes your\ndevelopment client-centric and client-driven, which tend to be a lot\nfaster from a development perspective. A project is usually driven\nfrom the top-of-the-iceberg and down, so shuffling more onus on the\nclient side is a wise move in modern system design.\n\nGraphQL is also a *contract*. Queries and responses are *typed* and\ncontract-verified on both the input and output side. That is, GraphQL\nalso acts as a contract-checker. This ensures:\n\n* No client can provide illegal queries to the server backend. These\n  are filtered out by the GraphQL layer.\n* No server can provide illegal responses to the client. These are\n  altered such that the client gets a valid response according to the\n  schema by replacing failing nodes with null-values.\n* The contract documents the API\n* The contract describes how the client can query data. This is the\n  closest to HATEOAS we will probably get without going there.\n* Queries tend to be large and all-encompassing. This means we don't\n  pay the round-trip-time for a request/response like you do in e.g.,\n  HTTP and HTTP/2 based systems where multiple queries are executed\n  back to back and depends on each other. Almost every query can be\n  handled in a single round trip.\n\nFinally, GraphQL supports *introspection* of its endpoint. This allows\nsystems to query the server in order to learn what the schema is. In\nturn, tooling can be built on top of GraphQL servers to provide\ndevelopment-debug user interfaces. Also, languages with static types\ncan use the introspection to derive a type model in code which matches\nthe contract. Either by static code generation, or by type providers.\n\n## Whirlwind tour\n\nThe GraphQL world specifies a typed *schema* definition. For instance\nthe following taken from the Relay Modern specification:\n\n```graphql\ninterface Node {\n  id: ID!\n}\n\ntype Faction : Node {\n  id: ID!\n  name: String\n  ships: ShipConnection\n}\n\ntype Ship : Node {\n  id: ID!\n  name: String\n}\n\ntype ShipConnection {\n  edges: [ShipEdge]\n  pageInfo: PageInfo!\n}\n\ntype ShipEdge {\n  cursor: String!\n  node: Ship\n}\n\ntype PageInfo {\n  hasNextPage: Boolean!\n  hasPreviousPage: Boolean!\n  startCursor: String\n  endCursor: String\n}\n\ntype Query {\n  rebels: Faction\n  empire: Faction\n  node(id: ID!): Node\n}\n\ninput IntroduceShipInput {\n  factionId: String!\n  shipNamed: String!\n  clientMutationId: String!\n}\n\ntype IntroduceShipPayload {\n  faction: Faction\n  ship: Ship\n  clientMutationId: String!\n}\n\ntype Mutation {\n  introduceShip(input: IntroduceShipInput!): IntroduceShipPayload\n}\n```\n\nThe schema is a subset of the Star Wars schema given as the typical\nGraphQL example all over the web. The GraphQL world roughly splits the\nworld into *input objects* and *output objects*. Input objects are\ngiven as part of a query request by the client. Output objects are\nsent back from the server to the client.\n\nThis Erlang implementation contains a schema parser for schemas like\nthe above. Once parsed, a mapping is provided by the programmer which\nmaps an output type in the schema to an Erlang module. This module\nmust implement a function\n\n```erlang\n-spec execute(Context, Object, Field, Args) -\u003e\n    {ok, Response}\n  | {error, Reason}.\n```\n\nwhich is used to materialize said object. That is, when you request a\n*field* `F` in the object `O`, a call is made to\n`execute(Context, O, F, Args)`. The value `Context` provides a global\ncontext for the query. It is used for authentication data, for origin\nIP addresses and so on. The context is extensible by the developer\nwith any field they need. The `Args` provides arguments for the field.\nLook, for instance at the type `Mutation` and the `introduceShip`\nfield, which takes an argument `input` of type `IntroduceShipInput!`.\n\nMaterialization is thus simply a function call in the Erlang world.\nThese calls tend to be used in two ways: Either they *acquire* a piece\nof data from a database (e.g., mnesia) and return that data as an\n`Object`. Or they materialize fields on an already loaded object. When\nexecution of a query is processed, you can imagine having a \"cursor\"\nwhich is being moved around in the result set and is used to\nmaterialize each part of the query.\n\nFor example, look at the following query:\n\n```graphql\nquery Q {\n  node(id: \"12098141\") {\n      ... on Ship {\n        id\n        name\n      }\n  }\n}\n```\n\nWhen this query executes, it will start by a developer provided\ninitial object. Typically the empty map `#{}`. Since the `node` field\nis requested, a call is performed to match:\n\n```erlang\n-module(query).\n\n...\nexecute(Ctx, #{}, \u003c\u003c\"node\"\u003e\u003e, #{ \u003c\u003c\"id\"\u003e\u003e := ID }) -\u003e\n    {ok, Obj} = load_object(ID).\n```\n\nNow, since you are requesting the `id` and `name` fields on a `Ship`\ninside the node, the system will make a callback to a type-resolver\nfor the `Obj` in order to determine what type it is. We omit that part\nhere, but if it was something else, a faction say, then the rest of\nthe query would not trigger. Once we know that id \"12098141\" is a\nShip, we \"move the cursor\" to a ship and calls the execute function\nthere:\n\n```erlang\n-module(ship).\n\n-record(ship, { id, name }).\n\nexecute(Ctx, #ship{ id = Id }, \u003c\u003c\"id\"\u003e\u003e, _Args) -\u003e\n    {ok, ID};\nexecute(Ctx, #ship{ name = Name }, \u003c\u003c\"name\"\u003e\u003e, _Args) -\u003e\n    {ok, Name}.\n```\n\nTwo materialization calls will be made. One for the field `\u003c\u003c\"id\"\u003e\u003e`\nand one for the field `\u003c\u003c\"name\"\u003e\u003e`. The end result is then\nmaterialized as a response to the caller.\n\n### Materilization through derivation\n\nA common use of the functions is to *derive* data from existing data.\nSuppose we extend the ship in the following way:\n\n```graphql\ntype Ship {\n  ...\n  capacity : float!\n  load : float!\n  loadRatio : float!\n}\n```\n\nso a ship has a certain capacity and a current load in its cargo bay.\nWe could store the `loadRatio` in the database and keep it up to date.\nBut a more efficient way to handle this is to compute it from other\ndata:\n\n```erlang\n-module(ship).\n\n-record(ship,\n    { id,\n      name,\n      capacity,\n      load }).\n\nexecute(...) -\u003e\n  ...;\nexecute(Ctx, #ship {\n                capacity = Cap,\n                load = Load }, \u003c\u003c\"loadRatio\"\u003e\u003e, _) -\u003e\n    {ok, Load / Cap };\n...\n```\n\nThis will compute that field if it is requested, but not compute it\nwhen it is not requested by a client. Many fields in a data set are\nderivable in this fashion. Especially when a schema changes and grows\nover time. Old fields can be derived for backwards compatibility and\nnew fields can be added next to it.\n\nIn addition, it tends to be more efficient. A sizable portion of\nmodern web work is about moving data around. If you have to move less\ndata, you decrease the memory and network pressure, which can\ntranslate to faster service.\n\n### Materializing JOINs\n\nIf we take a look at the `Faction` type, we see the following:\n\n```graphql\ntype Faction : Node {\n  id: ID!\n  name: String\n  ships: ShipConnection\n}\n```\n\nin this, `ships` is a field referring to a `ShipConnection`. A\nConnection type is Relay Modern standard of how to handle a\npaginated set of objects in GraphQL. Like \"Materialization by\nderivation\" we would derive this field by looking up the data in the\ndatabase for the join and then producing an object which the\n`ship_connection_resource` can handle. For instance:\n\n```erlang\nexecute(Ctx, #faction { id = ID }, \u003c\u003c\"ships\"\u003e\u003e, _Args) -\u003e\n    {ok, Ships} = ship:lookup_by_faction(ID),\n    pagination:build_pagination(Ships).\n```\n\nwhere the `build_pagination` function returns some object which is a\ngeneric connection object. It will probably look something along the\nlines of\n\n```erlang\n#{\n  '$type' =\u003e \u003c\u003c\"ShipConnection\"\u003e\u003e,\n  \u003c\u003c\"pageInfo\"\u003e\u003e =\u003e #{\n      \u003c\u003c\"hasNextPage\"\u003e\u003e =\u003e false,\n      ...\n  },\n  \u003c\u003c\"edges\"\u003e\u003e =\u003e [\n      #{ \u003c\u003c\"cursor\"\u003e\u003e =\u003e base64:encode(\u003c\u003c\"edge:1\"\u003e\u003e),\n          \u003c\u003c\"node\"\u003e\u003e =\u003e #ship{ ... } },\n      ...]\n}\n```\n\nwhich can then be processed further by other resources. Note how we\nare eagerly constructing several objects at once and then exploiting the\ncursor moves of the GraphQL system to materialize the fields which the\nclient requests. The alternative is to lazily construct\nmaterializations on demand, but when data is readily available anyway,\nit is often more efficient to just pass pointers along.\n\n## API\n\nThe GraphQL API is defined in the module `graphql`. Every\nfunctionality is exported in that module. Do not call inside other\nmodules as their functionality can change at any point in time even\nbetween major releases.\n\nThe system deliberately splits each phase and hands it over to the\nprogrammer. This allows you to debug a bit easier and gives the\nprogrammer more control over the parts. A typical implementation will\nstart by using the schema loader:\n\n```erlang\ninject() -\u003e\n  {ok, File} = application:get_env(myapp, schema_file),\n  Priv = code:priv_dir(myapp),\n  FName = filename:join([Priv, File]),\n  {ok, SchemaData} = file:read_file(FName),\n  Map = #{\n    scalars =\u003e #{ default =\u003e scalar_resource },\n    interfaces =\u003e #{ default =\u003e resolve_resource },\n    unions =\u003e #{ default =\u003e resolve_resource },\n    objects =\u003e #{\n      'Ship' =\u003e ship_resource,\n      'Faction' =\u003e faction_resource,\n      ...\n      'Query' =\u003e query_resource,\n      'Mutation' =\u003e mutation_resource\n    }\n  },\n  ok = graphql:load_schema(Map, SchemaData),\n      Root = {root,\n      #{\n        query =\u003e 'Query',\n        mutation =\u003e 'Mutation',\n        interfaces =\u003e []\n      }},\n  ok = graphql:insert_schema_definition(Root),\n  ok = graphql:validate_schema(),\n  ok.\n```\n\nThis will set up the schema in the code by reading it from a file on\ndisk. Each of the `_resource` names refers to modules which implements\nthe backend code.\n\nIn order to execute queries on the schema, code such as the following\ncan be used. We have a query document in `Doc` and we have a requested\noperation name in `OpName` and parameter variables for the given op in\n`Vars`. The variables `Req` and `State` are standard cowboy request\nand state tracking variables from `cowboy_rest`.\n\n```erlang\nrun(Doc, OpName, Vars, Req, State) -\u003e\n  case graphql:parse(Doc) of\n    {ok, AST} -\u003e\n      try\n          {ok, #{fun_env := FunEnv,\n                ast := AST2 }} = graphql:type_check(AST),\n          ok = graphql:validate(AST2),\n          Coerced = graphql:type_check_params(FunEnv, OpName, Vars),\n          Ctx = #{ params =\u003e Coerced, operation_name =\u003e OpName },\n          Response = graphql:execute(Ctx, AST2),\n          Req2 = cowboy_req:set_resp_body(encode_json(Response), Req),\n          {ok, Reply} = cowboy_req:reply(200, Req2),\n          {halt, Reply, State}\n      catch\n            throw:Err -\u003e\n                err(400, Err, Req, State)\n      end;\n    {error, Error} -\u003e\n        err(400, {parser_error, Error}, Req, State)\n  end.\n```\n\n## Conventions\n\nIn this GraphQL implementation, the default value for keys are type\n`binary()`. This choice is deliberate, since it makes the code more\nresistent to `atom()` overflow and also avoids some conversions\nbetween `binary()` and `atom()` values in the system. A later version\nof the library might redesign this aspect, but we are somewhat stuck\nwith it for now.\n\nHowever, there are many places where you can input atom values and\nthen have them converted internally by the library into binary values.\nThis greatly simplifies a large number of data entry tasks for the\nprogrammer. The general rules are:\n\n* If you supply a value to the system and it is an atom(), the\n  internal representation is a binary value.\n* If the system hands you a value, it is a binary() value and not an\n  atom().\n\n## Middlewares\n\nThis GraphQL system does not support middlewares, because it turns out\nthe systems design is flexible enough middlewares can be implemented\nby developers themselves. The observation is that any query runs\nthrough the `Query` type and thus a `query_resource`. Likewise, any\n`Mutation` factors through the `mutation_resource`.\n\nAs a result, you can implement middlewares by using the `execute/4`\nfunction as a wrapper. For instance you could define a mutation\nfunction as:\n\n```erlang\nexecute(Ctx, Obj, Field, Args) -\u003e\n    AnnotCtx = perform_authentication(Ctx),\n    execute_field(AnnotCtx, Obj, Field, Args).\n```\n\nThe reason this works so well is because we are able to use pattern\nmatching on `execute/4` functions and then specialize them. If we had\nan individual function for each field, then we would have been forced\nto implement middlewares in the system, which incurs more code lines\nto support.\n\nMore complex systems will define a stack of middlewares in the list\nand run them one by one. As an example, a `clientMutationId` is part\nof the Relay Modern specification and must be present in every\nmutation. You can build your `mutation_resource` such that it runs a\n`maps:take/2` on the argument input, runs the underlying mutation, and\nthen adds back the `clientMutationId` afterwards.\n\n## Schema Definitions\n\nThis GraphQL implementation follows the Jun2018 specification for\ndefining a schema. In this format, one writes the schema according to\nspecification, including doc-strings. What was represented as `tags`\nin an earlier implementation of GraphQL for Erlang is now represented\nas a `@directive` annotation, as per the specification.\n\nAs an example, you can write something along the lines of:\n\n```graphql\n\"\"\"\nA Ship from the Star Wars universe\n\"\"\"\ntype Ship : Node {\n  \"Unique identity of the ship\"\n  id: ID!\n\n  \"The name of the ship\"\n  name: String\n}\n```\n\nAnd the schema parser knows how to transform this into documentation\nfor introspection.\n\n## Resource modules\n\nThe following section documents the layout of resource modules as they\nare used in GraphQL, and what they are needed for in the\nimplementation.\n\n### Scalar Resources\n\nGraphQL contains two major kinds of data: objects and scalars. Objects\nare product types where each element in the product is a field. Raw\ndata are represented as *scalar* values. GraphQL defines a number of\nstandard scalar values: boolean, integers, floating point numbers,\nenumerations, strings, identifiers and so on. But you can extend the\nset of scalars yourself. The spec will contain something along the\nlines of\n\n```graphql\nscalar Color\nscalar DateTime\n```\n\nand so on. These are mapped onto resource modules handling scalars. It\nis often enough to provide a default scalar module in the mapping and\nthen implement two functions to handle the scalars:\n\n```erlang\n-module(scalar_resource).\n\n-export(\n  [input/2,\n    output/2]).\n\n-spec input(Type, Value) -\u003e {ok, Coerced} | {error, Reason}\n  when\n    Type :: binary(),\n    Value :: binary(),\n    Coerced :: any(),\n    Reason :: term().\ninput(\u003c\u003c\"Color\"\u003e\u003e, C) -\u003e color:coerce(C);\ninput(\u003c\u003c\"DateTime\"\u003e\u003e, DT) -\u003e datetime:coerce(DT);\ninput(Ty, V) -\u003e\n    error_logger:info_report({coercing_generic_scalar, Ty, V}),\n    {ok, V}.\n\n-spec output(Type, Value) -\u003e {ok, Coerced} | {error, Reason}\n  when\n    Type :: binary(),\n    Value :: binary(),\n    Coerced :: any(),\n    Reason :: term().\noutput(\u003c\u003c\"Color\"\u003e\u003e, C) -\u003e color:as_binary(C);\noutput(\u003c\u003c\"DateTime\"\u003e\u003e, DT) -\u003e datetime:as_binary(DT);\noutput(Ty, V) -\u003e\n    error_logger:info_report({output_generic_scalar, Ty, V}),\n    {ok, V}.\n```\n\nScalar Mappings allow you to have an internal and external\nrepresentation of values. You could for instance read a color such as\n`#aabbcc`, convert it into `#{ r =\u003e 0.66, g =\u003e 0.73, b =\u003e 0.8 }`\ninternally and back again when outputting it. Likewise a datetime\nobject can be converted to a UNIX timestamp and a timezone internally\nif you want. You can also handle multiple different ways of\ncoercing input data, or have multiple internal data representations.\n\n### Type resolution Resources\n\nFor GraphQL to function correctly, we must be able to resolve types of\nconcrete objects. This is because the GraphQL system allows you to\nspecify abstract interfaces and unions. An example from the above\nschema is the `Node` interface which is implemented by `Ship` and\n`Faction` among other things. If we are trying to materialize a node,\nthe GraphQL must have a way to figure out the type of the object it is\nmaterializing. This is handled by the type resolution mapping:\n\n```erlang\n-module(resolve_resource).\n\n-export([execute/1]).\n\n%% The following is probably included from a header file in a real\n%% implementation\n-record(ship, {id, name}).\n-record(faction, {id, name}).\n\nexecute(#ship{}) -\u003e {ok, \u003c\u003c\"Ship\"\u003e\u003e};\nexecute(#faction{}) -\u003e {ok, \u003c\u003c\"Faction\"\u003e\u003e};\nexecute(Obj) -\u003e\n    {error, unknown_type}.\n```\n\n\n### Output object Resources\n\nEach (output) object is mapped onto an Erlang module responsible for\nhandling field requests in that object. The module looks like:\n\n```erlang\n-module(object_resource).\n\n-export([execute/4]).\n\nexecute(Ctx, SrcObj, \u003c\u003c\"f\"\u003e\u003e, Args) -\u003e\n    {ok, 42};\nexecute(Ctx, SrcObj, Field, Args) -\u003e\n    default\n```\n\nThe only function which is needed is the `execute/4` function which is\ncalled by the system whenever a field is requested in that object. The\n4 parameters are as follows:\n\n* `Ctx` - The *context* of the query. It contains information\n  pertaining to the current position in the Graph, as well as\n  user-supplied information from the start of the request. It is\n  commonly used as a read-only store for authentication/authorization\n  data, so you can limit what certain users can see.\n* `SrcObj` - The *current* object on which we are operating. Imagine\n  we have two ships, a B-wing and an X-wing. Even if we request the\n  same fields on the two ships, the `SrcObj` is going to be different.\n  GraphQL often proceeds by having certain fields *fetch* objects out\n  of a backing store and then moving the *cursor* onto that object and\n  calling the correct object resource for that type. The `SrcObj` is\n  set to point to the object that is currently being operated upon.\n* `Field` - The field in the object which is requested.\n* `Args` - A map of field arguments. See the next section.\n\n#### Field Argument rules\n\nIn GraphQL, field arguments follow a specific pattern:\n\n* Clients has no way to input a `null` value. The only thing they can\n  do is to omit a given field in the input. In particular, clients\n  *must* supply a field which is non-null.\n* Servers *always* see every field in the input, even if the client\n  doesn't supply it. If the client does not supply a field, and it has\n  no default value, the server sees a `null` value for that field.\n\nThis pattern means there is a clear way for the client to specify \"no\nvalue\" and a clear way for the server to work with the case where the\nclient specified \"no value. It eliminates corner cases where you have\nto figure out what the client meant.\n\nResolution follows a rather simple pattern in GraphQL. If a client\nomits a field and it has a default value, the default value is input.\nOtherwise `null` is input. Clients *must* supply every non-null field.\n\n_Note:_ This limitation is lifted in the Jun2018 GraphQL\nspecification, but this server doesn't implement that detail yet.\n\nOn the server side, we handle arguments by supplying a map of KV pairs\nto the execute function. Suppose we have an input such as\n\n```graphql\ninput Point {\n    x = 4.0 float\n    y float\n}\n```\n\nThe server can handle this input by matching directly:\n\n```erlang\nexecute(Ctx, SrcObj, Field,\n    #{ \u003c\u003c\"x\"\u003e\u003e := XVal, \u003c\u003c\"y\"\u003e\u003e := YVal }) -\u003e\n  ...\n```\n\nThis will always match. If the client provides the input `{}` which is\nthe empty input, `XVal` will be `4.0` due to the default value. And\n`YVal` will be `null`. If the client supplies, e.g., `{ x: 2.0, y: 7.0\n}` the map `#{ \u003c\u003c\"x\"\u003e\u003e =\u003e 2.0, \u003c\u003c\"y\"\u003e\u003e =\u003e 7.0 }` will be provided.\n\n#### Tips \u0026 Tricks\n\nThe execute function allows you to make object-level generic handling\nof fields. If, for example, your `SrcObj` is a map, you can do generic\nlookups by using the following handler:\n\n```erlang\nexecute(_Ctx, Obj, Field, _Args) -\u003e\n    case maps:get(Field, Obj, not_found) of\n      not_found -\u003e {ok, null};\n      Val -\u003e {ok, Val}\n    end.\n```\n\nAnother trick is to use generic execution to handle \"middlewares\" -\nSee the appropriate section on Middlewares.\n\n# System Architecture\n\nMost other GraphQL servers provide no type-\u003emodule mapping. Rather,\nthey rely on binding of individual functions to fields. The\nimplementation began with the same setup, but it turns out pattern\nmatching is a good fit for the notion of requesting different fields\ninside an object. Thus, we use pattern matching as a destructuring\nmechanism for incoming queries.\n\n### Schema\n\nInternally, the system parses the schema into an ETS table, on which\nit can perform queries in parallel to satisfy multiple requests at the\nsame time.\n\nA schema *injector* allows the developer to parse a schema from a file\nor from memory, then bind exeuction modules to the schemas types. Once\nfinishes, the schema is *finalized* which runs a lint check over the\nschema and rejects schemas which are nonsensical.\n\n### Query\n\nA query is treated as a compiler chain, which is a design that fits\nErlang well. Compilers rely a lot on pattern matching, so we can\nprocess a query symbolically by matching on it and gradually\ntransforming it into a *query plan* which can then be executed.\n\n* A *lexer* tokenizes the query\n* A *parser* constructs an AST of the query from the token stream\n* An *type checker* walks the AST and attaches type information to the\n  AST by looking up data in the schema ETS table. The pass also\n  detects type and validation errors. The type checker is written in a\n  bi-directional style so it flips between *inference*, in which we\n  deduce the type of a term, and *checking* in which we verify a term\n  has a given type.\n* A *validator* performs additional linting. Many queries are\n  type-correct and thus *executable*, but are still malformed because\n  they have nonsensical parts in them. The validator phase rejects\n  such queries.\n* A *query plan* is formed from the AST.\n* An *executor* runs the *query plan*.\n\nOf these tasks, only the execution phase in the end is\nperformance-critical. Clients can pre-load query documents to the\nserver, which means the document acts as a stored procedure on the\nserver side. The server can then do parsing, elaboration, type\nchecking and validation once and for all at load time. In addition it\nprovides a security measure: clients in production can only call a\npre-validated set of queries if such desired.\n\n# User Interface\n\nGraphQL has some very neat Javascript tooling which plugs into the\nintrospection of a GraphQL server and provides additional\nfunctionality:\n\n* GraphiQL - Provides a query interface with autocompletion,\n  documentation, debugging, ways to execute queries and so on. It is\n  highly recommended you add such a system in staging and production\n  as it is indispensable if you are trying to figure out a new query\n  or why a given query returned a specific kind of error.\n\nAdditionally, Relay Modern provides specifications for cache\nrefreshing, pagination, mutation specifications and so on. It is\nrecommended you implement those parts in your system as it is part of\na de-facto standard for how GraphQL servers tend to operate.\n\n# Tests\n\nThe GraphQL project has an extensive test suite. We prefer adding\nregressions to the suite as we experience them. Some of the tests are\ntaken from the the official GraphQL repository and translated. More\nwork is definitely needed, but in general new functionality should be\nprovided together with a test case that demonstrates the new\nfunctionality.\n\nThe general tests are:\n\n* `dungeon_SUITE` which implements a \"MUD\" style dungeon backend. It\n  is used as a way to handle most of the test cases we cook up\n  ourselves. It is driven by a schema and uses a query document for\n  its queries. If you don't know where to add a test, this is a good\n  place.\n* `enum_SUITE` Taken from the official Node.js de-facto\n  implementation, this suite uses the \"colors\" schema in order to\n  verify certain hard-to-get-right properties about enumerated data\n  types.\n* `graphql_SUITE` Main suite for things which doesn't fit elsewhere.\n  Checks lexing/parsing, \"Hello World\" style queries and also\n  introspection.\n* `star_wars_SUITE` An implementation from the specification of the\n  Star Wars universe. Allows us to verify queries from the\n  specification in our own code. Also part of the Node.js de-facto\n  implementation, so it is easy for us to transplant a test from there\n  to here.\n* `validation_SUITE` GraphQL contains a lot of different validation\n  checks. We handle some of these in the type checker and some in a\n  validation pass. The tests here are mostly verifying parts of the\n  specification. It uses the \"Pet\" schema as a base.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlouis%2Fgraphql-erlang","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjlouis%2Fgraphql-erlang","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjlouis%2Fgraphql-erlang/lists"}