{"id":23902687,"url":"https://github.com/mtumilowicz/scala-graphql-caliban-workshop","last_synced_at":"2026-02-25T21:02:51.286Z","repository":{"id":110879095,"uuid":"437129809","full_name":"mtumilowicz/scala-graphql-caliban-workshop","owner":"mtumilowicz","description":"Introduction to GraphQL using pure functional approach: Scala, Caliban and ZIO.","archived":false,"fork":false,"pushed_at":"2024-03-19T18:33:58.000Z","size":121,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-10T15:07:46.789Z","etag":null,"topics":["caliban","caliban-graphql","effects","graphql","graphql-api","graphql-server","pure-functional","purely-functional-data-structures","scala","workshop","workshop-materials","workshops","zio","zio-effect","zio-http","zio-test"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mtumilowicz.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-12-10T22:25:50.000Z","updated_at":"2024-07-02T11:53:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"fdc2289f-64bd-4e4c-a5c2-372fb6c93da6","html_url":"https://github.com/mtumilowicz/scala-graphql-caliban-workshop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mtumilowicz/scala-graphql-caliban-workshop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-graphql-caliban-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-graphql-caliban-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-graphql-caliban-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-graphql-caliban-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mtumilowicz","download_url":"https://codeload.github.com/mtumilowicz/scala-graphql-caliban-workshop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mtumilowicz%2Fscala-graphql-caliban-workshop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29839938,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-25T20:42:33.054Z","status":"ssl_error","status_checked_at":"2026-02-25T20:42:21.322Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["caliban","caliban-graphql","effects","graphql","graphql-api","graphql-server","pure-functional","purely-functional-data-structures","scala","workshop","workshop-materials","workshops","zio","zio-effect","zio-http","zio-test"],"created_at":"2025-01-04T22:49:52.818Z","updated_at":"2026-02-25T21:02:51.262Z","avatar_url":"https://github.com/mtumilowicz.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Build Status](https://app.travis-ci.com/mtumilowicz/scala-graphql-caliban-workshop.svg?branch=master)](https://app.travis-ci.com/mtumilowicz/scala-graphql-caliban-workshop)\n[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n# scala-graphql-caliban-workshop\n\n* references\n    * [GraphQL - when REST API is not enough - lessons learned - Marcin Stachniuk](https://www.youtube.com/watch?v=vMGet_9y40g)\n    * [GraphQL as an alternative approach to REST with Luis Weir](https://www.youtube.com/watch?v=hJOOdCPlXbU)\n    * [Moving beyond REST: GraphQL and Java - Pratik Patel](https://www.youtube.com/watch?v=LVhzB2kFEAE)\n    * [2019 - Marcin Stachniuk - GraphQL - gdy api RESTowe to za mało](https://www.youtube.com/watch?v=lJiOay1fk_g)\n    * [GraphQL in Java World, let's go for a dive - Vladimir Dejanović](https://www.youtube.com/watch?v=5_uSpiXCeMI)\n    * [Introducing and Scaling a GraphQL BFF](https://www.youtube.com/watch?v=YnZr-qeiGO0)\n    * [Migrate your APIs to GraphQL: how? and why! by Guillaume Scheibel](https://www.youtube.com/watch?v=IkPMpzQ-TRI)\n    * [Exploring GraphQL-Braid: Leaving RESTish world and building a distributed GraphQL system](https://www.youtube.com/watch?v=iNtxkbzopDg)\n    * [Your GraphQL field guide by Bojan Tomić](https://www.youtube.com/watch?v=ROwICdehlb0)\n    * [4Developers Katowice: GraphQL - when REST API is not enough (...) cz. II, Marcin Stachniuk](https://www.youtube.com/watch?v=95I3e2Zy09Y)\n    * [Zymposium - Caliban](https://www.youtube.com/watch?v=mzqsXklbmfM)\n    * [Functional Scala - Caliban: Designing a Functional GraphQL Library by Pierre Ricadat](https://www.youtube.com/watch?v=OC8PbviYUlQ)\n    * [Caliban: Functional GraphQL Library for Scala by Pierre Ricadat](https://www.youtube.com/watch?v=oLvdhVNIC3k)\n    * [SF Scala–A Tour of Caliban: FP GraphQL in Scala \u0026 Understanding Scala's Type System](https://www.youtube.com/watch?v=lgxUKsOH65k\u0026list=WL)\n    * [Spring Boot GraphQL Tutorial - Full Course](https://www.youtube.com/playlist?list=PLiwhu8iLxKwL1TU0RMM6z7TtkyW-3-5Wi)\n    * https://ghostdogpr.github.io/caliban\n    * https://www.manning.com/books/graphql-in-action\n    * https://medium.com/@ghostdogpr/graphql-in-scala-with-caliban-part-1-8ceb6099c3c2\n    * https://medium.com/@ghostdogpr/graphql-in-scala-with-caliban-part-2-c7762110c0f9\n    * https://medium.com/@ghostdogpr/graphql-in-scala-with-caliban-part-3-8962a02d5d64\n    * https://www.peerislands.io/how-to-use-graphql-to-build-bffs/\n    * https://graphql.org/\n    * https://www.apollographql.com/docs\n    * https://medium.com/the-marcy-lab-school/what-is-the-n-1-problem-in-graphql-dd4921cb3c1a\n    * https://www.apollographql.com/blog/graphql/explaining-graphql-connections/\n    * https://www.apollographql.com/blog/graphql/pagination/understanding-pagination-rest-graphql-and-relay\n    * https://www.sitepoint.com/paginating-real-time-data-cursor-based-pagination/\n    * https://shopify.dev/api/usage/pagination-graphql\n\n## preface\n* goals of this workshop\n    * introduction into GraphQL\n        * motivation\n        * schema\n        * query, mutation, subscription\n        * data loaders\n* workshop plan\n    * task1: implement query for returning all customers (with a size limit)\n    * task2: implement subscription for deleted customers\n    * task3: implement getting orders in batch\n\n## introduction\n* BFF - backend for frontend API\n    * different clients need  different sets of data\n        * Web, Iphone, Android, Tv\n    * instead of the frontend application aggregating data by calling multiple sources - create a BFF layer\n        * layer does the following:\n            * receive request from the client application\n            * call multiple backend services\n            * format and response according to what is needed by the client\n            * respond to the client\n    * pros\n        * simplify the frontend logic\n        * avoid over-fetching or under-fetching\n        * reduce the number of network calls from the client perspective\n* real world data representation\n    * best way: graph-like data structure\n        * data model is usually a graph of objects with relations between them\n    * why think of data in terms of resources (in URLs) or tables?\n\n## graphql\n* GraphQL = Graph Query Language\n* is a new API standard that provides\n    * more efficient,\n    * powerful\n    * flexible\n    * alternative to REST\n* can be written in any programming language\n* has two major parts\n    * structure = strongly typed schema\n        * schema = graph of fields that have types\n            * all possible data objects that can be read (or updated)\n        * client uses the schema to know what are the capabilities of API\n    * behavior = resolver functions\n        * each field in a GraphQL schema is backed by a resolver function\n        * resolver function defines what data to fetch for its field\n        * resolver function represents the instructions on how and where to access raw data\n* takes the custom endpoint idea to an extreme\n    * the whole server = single smart endpoint\n    * multiple round-trip problem\n        * client has to communicate with the server multiple times to gather all data\n        * API server knows how to answer questions about a single resource\n        * solution: GraphQL\n* 5 key characteristics\n    1. hierarchical: queries = hierarchies of data definitions\n    1. view-centric: built to satisfy frontend requirements\n    1. strongly-typed: typed context (schema) + queries are executed within this context\n    1. introspective: type system (schema) itself is queryable\n    1. version-free: tools for the continuous evolution\n* vs REST\n    * don't need any documentation like swagger\n    * REST = over-fetching (to many fields) and under-fetching (many calls to get data you need)\n        * client cannot specify which fields to select\n    * REST: each endpoint represents a resource\n        * multiple network requests\n    * REST: problem with versioning\n        * usually means new endpoints\n        * GraphQL: add new fields and types without removing the old ones\n            * clients can continue to use older features\n                * can also incrementally update their code to use new features\n            * important for mobile clients\n                * you cannot control the version of the API they are using\n            * GraphQL offers a way to deprecate (and hide)\n    * REST: turn into a mix of regular REST endpoints plus custom endpoints crafted for performance reasons\n* summary\n    * pros\n        * decouples clients from servers\n            * allows both of them to evolve and scale independently\n        * efficiency\n        * simplify client logic: client communicate with the GraphQL service\n            * GraphQL service communicates with the different services\n    * cons\n        * malicious queries\n            * complex queries\n                * solution: analyze AST complexity\n            * large result size\n                * solution: limit depth\n            * long execution time\n                * solution: limit the execution time\n                * solution: stream the results\n        * n+1 problem\n            * solution: data loader\n        * caching is no longer simple\n            * network layer - unsuitable as there is a common URL for all operations\n            * solution: granularity (per field)\n        * hard to return simple Map\n\n## schema\n* something like swagger: graphql/graphiql\n    * https://api.spacex.land/graphql/\n* example\n    ```\n    type Starship {\n      id: ID!\n      name: String! // non nullable\n      appearsIn: [Episode!]! // list of objects\n      length(unit: LengthUnit = METER): Float // argument\n    }\n    ```\n* good practices\n    * usually make the types of fields non-null\n    * however, make all root fields nullable\n        * in this case, nullability means that something went wrong but we’re allowing it to show other fields\n* scalar\n    * don't have any sub-fields\n    * predefined: ID, Boolean, Int, String\n\n## operations\n* three types of operations\n    * queries (READ operations)\n    * mutations (WRITE-then-READ operations)\n        * queries that have side effects\n    * subscriptions\n        * stream of responses\n        * used for real-time data monitoring\n        * require the use of a data-transport channel that supports continuous pushing\n        of data\n            * usually done with WebSockets\n\n### query\n* example\n    ```\n    {\n      hero {\n        name\n        friends {\n          name\n        }\n      }\n    }\n    ```\n* steps\n    1. validate the request against its schema\n    1. traverse the tree of fields and invoke the resolver functions\n* fragment\n    * example\n        ```\n        {\n          leftComparison: hero(episode: EMPIRE) {\n            ...comparisonFields // spread that fragment\n          }\n          rightComparison: hero(episode: JEDI) {\n            ...comparisonFields // spread that fragment\n          }\n        }\n\n        fragment comparisonFields on Character {\n          name\n          appearsIn\n          friends {\n            name\n          }\n        }\n        ```\n    * are the composition units of the language\n    * are the reusable piece of any GraphQL operation\n    * split operations into smaller parts\n    * data required by an application = sum of the data required by individual components\n        * makes a fragment the perfect match for a component\n        * represent the data requirements for a single component and then compose them\n\n### mutation\n* is always a WRITE operation followed by a READ operation\n* vs queries\n    * queries will be done in parallel\n    * mutations sequentially\n        * if an API consumer sends two mutation fields, the first is guaranteed to\n        finish before the second begins\n* example\n    ```\n    mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {\n      createReview(episode: $ep, review: $review) {\n        stars\n        commentary\n      }\n    }\n\n    { // variables\n      \"ep\": \"JEDI\",\n      \"review\": {\n        \"stars\": 5,\n        \"commentary\": \"This is a great movie!\"\n      }\n    }\n    ```\n\n### subscription\n* client should NOT use subscriptions to stay up to date with backend\n    * use poll intermittently with queries\n    * re-execute queries on demand when a user performs\n    a relevant action (such as clicking a button)\n* use subscriptions for\n    * small, incremental changes to large objects\n        * polling for a large object is expensive\n            * especially when most of the object's fields rarely change\n        * fetch the object's initial state with a query\n            * server can proactively push updates to individual fields\n    * low-latency, real-time updates\n        * example: a chat application\n\n## data loaders\n* problem\n    * querying for authors and books\n    * authors \"has many\" books\n    * we would like to achieve two SQL calls\n        ```\n        SELECT *\n        FROM authors;\n        -- pretend this returns 3 authors\n        SELECT *\n        FROM books\n        WHERE author_id in (1, 2, 3); -- an array of the author's ids\n        ```\n    * query\n        ```\n        {\n          query { // 1 call\n            authors {\n              name\n              books { // each book resolver would only get it’s own parent author: N calls\n                title\n              }\n            }\n          }\n        }\n        ```\n    * in REST: ORM will help\n    * in graphQl: each resolver function really only knows about its own parent object\n        * ORM won’t have the luxury of a list of author IDs anymore\n        * result: N+1 calls\n            ```\n            SELECT *\n            FROM authors;\n\n            SELECT *\n            FROM books\n            WHERE author_id in (1);\n\n            SELECT *\n            FROM books\n            WHERE author_id in (2);\n\n            SELECT *\n            FROM books\n            WHERE author_id in (3);\n            ```\n* solutions\n    * batching\n        * delay asking the database until we will have all appropriate IDs\n    * caching\n        * no application-level caching shared among requests\n        * rather simple memoization in the context of a single request\n    * example library: DataLoader (JavaScript utility library)\n\n## client cache\n* responses from REST are easy to cache\n    * dictionary nature\n        * specific URL gives certain data\n        * use the URL itself as the cache key\n* in graphQl: graph cache\n    * no URL-like primitive (globally unique identifier for a given object)\n    * best practice: expose such an identifier for clients to use\n        ```\n        {\n          starship(id:\"3003\") { // id field provides a globally unique key\n            id\n            name\n          }\n          droid(id:\"2001\") {\n            id\n            name\n            friends {\n              id\n              name\n            }\n          }\n        }\n        ```\n\n## pagination\n* two scenarios\n    * UX concern: too many items to display\n        * mental overload for the user to see them all at once\n    * performance concern: too many items to load\n        * it would overload our backend, the connection, or the client to load all of the items at once\n* types of pagination from the UX perspective\n    * numbered pages\n        * example: book, Google search\n        * expect it to be consistent over some period of time\n        * sql\n            ```\n            SELECT * FROM posts ORDER BY created_at LIMIT 10 OFFSET 20; // page 3, with a page size of 10\n            SELECT COUNT(*) FROM posts; // total number of entries or pages in the results\n            ```\n        * drawbacks\n            * only for mostly static content\n            * however, usually items are added and removed while the user is navigating\n                * leads to skipping items\n                * or displaying the same item twice\n                    * new item was added at the top of the list\n                        * skip and limit approach to show the item at the boundary between pages twice\n    * sequential pages like Reddit\n        * aren’t numbered\n        * content changes so rapidly - no point in page numbers at all\n        * specify the place in the list we want to begin, and how many items we want to fetch\n            * it doesn’t matter how many items were added to the top of the list\n            * we have a constant pointer to the specific spot where we left off\n                * pointer is called a cursor\n                * cursor is a piece of data\n                    * generally some kind of ID\n                    * represents a location in a paginated list\n        * example\n            ```\n            SELECT * FROM posts\n            WHERE created_at \u003c $after\n            ORDER BY created_at LIMIT $page_size;\n\n            https://www.reddit.com/?count=25\u0026after=t3_49i88b\n            ```\n        * good practice: encoded cursor with some metadata or a timestamp (instead of a row ID)\n            * resilient to row deletion\n            * we don’t want the query to fail if a specific item is removed\n    * infinite scroll like Twitter\n        * illusion of one very long page\n    * modern apps today use either the second or third approach\n        * app’s content is constantly changing\n        * doesn’t make sense to create the illusion of numbered pages\n\n### relay cursor connections\n* generic specification for how a GraphQL server should expose paginated data\n* generalized concepts we were talking about above\n    * `friends(first:2 after:$opaqueCursor) // vs friends(first:2 after:$friendId)`\n        * cursors are opaque and their format should not be relied upon\n            * suggestion: base64 encoding\n        * additional flexibility for pagination model changes\n            * user just uses opaque cursors\n* example\n    * request\n        ```\n        {\n          user {\n            id\n            name\n            friends(first: 10, after: \"opaqueCursor\") {\n              edges { // each edge has a reference to the user object of the friend, and a cursor\n                cursor // every item in the paginated list has its own cursor\n                node {\n                  id\n                  name\n                }\n              }\n              pageInfo {\n                hasNextPage\n              }\n            }\n          }\n        }\n        ```\n        * notice that if we want to, we can ask for 10 friends starting from the middle of the list we last fetched\n    * response\n        ```\n        {\n          \"data\": {\n            \"products\": {\n              \"pageInfo\": {\n                \"hasNextPage\": true,\n                \"hasPreviousPage\": false\n              },\n              \"edges\": [\n                {\n                  \"cursor\": \"eyJsYXN0X2lkIjoxMDA3OTc4ODg3NiwibGFzdF92YWx1ZSI6IjEwMDc5Nzg4ODc2In0=\",\n                  \"node\": {\n                    \"id\": \"1\",\n                    \"name\": \"Michal\"\n                  }\n                },\n                {\n                  \"cursor\": \"eyJsYXN0X2lkIjoxMDA3OTc5MzQyMCwibGFzdF92YWx1ZSI6IjEwMDc5NzkzNDIwIn0=\",\n                  \"node\": {\n                    \"id\": \"2\",\n                    \"name\": \"Marcin\"\n                  }\n                },\n                {\n                  \"cursor\": \"eyJsYXN0X2lkIjoxMDA3OTc5NDM4MCwibGFzdF92YWx1ZSI6IjEwMDc5Nzk0MzgwIn0=\",\n                  \"node\": {\n                    \"id\": \"3\",\n                    \"name\": \"Anna\"\n                  }\n                }\n              ]\n            }\n          },\n          ...\n        }\n        ```\n* glossary\n    * connection - paginated field on an object\n        * example: friends field on a user\n    * edge - metadata about one object in the paginated list\n        * includes a cursor to allow pagination starting from that object\n    * node - actual object\n    * pageInfo - info about more pages of data to fetch\n\n## security\n* critical threat: resource-exhaustion attacks (DOS attacks) with overly complex queries\n    * are not specific to GraphQL\n    * example: query for deeply nested relationships\n        * (user –\u003e friends –\u003e friends –\u003e friends …)\n    * example use field aliases to ask for the same field many times\n        ```\n        {\n          empireHero: hero(episode: EMPIRE) {\n            name\n          }\n          jediHero: hero(episode: JEDI) {\n            name\n          }\n        }\n        ```\n* solution\n    * cost analysis on the query\n    * enforce limits on the amount of data\n    * timeouts\n\n## caliban\n* features\n    * minimize boilerplate\n    * purely functional (strongly typed, explicit errors)\n    * user friendly\n    * schema / resolver separation\n* vs sangria\n    * sangria: lots of boilerplate (macros to the rescue)\n    * sangria: future based (effects are better)\n    * sangria: schema and resolved tied together\n* schema is derived automatically from the case classes\n    * mangolia - used for create schema for traits and case classes\n        * provide a schema for custom types\n            ```\n            implicit val nesSchema: Schema[NonEmptyString] = Schema.stringSchema.contramap(_.value)\n            ```\n            * many useful types: https://github.com/niqdev/caliban-extras\n    ```\n    val api = graphQL(resolver)\n    println(api.render) // prints derived schema\n    ```\n* schema deriving examples\n    * case classes\n        ```\n        case class Pug(name: String, nicknames: List[String], pictureUrl: Option[String])\n        ```\n        is transformed into\n        ```\n        type: Pug {\n            name: String,\n            nicknames: [String!]!,\n            pictureUrl: String // optionality -\u003e option\n        }\n        ```\n    * enums\n        ```\n        sealed trait Color\n        case object FAWN extends Color\n        ```\n        is transformed into\n        ```\n        type: enum Color { FAWN }\n        ```\n    * arguments\n        ```\n        case class PugName(name: String)\n        case class Queries(pug: PugName =\u003e Option[Pug])\n        ```\n        is transformed into\n        ```\n        type: Queries { pug(name: String!): Pug }\n        ```\n* n+1 problem\n    * solution: `ZQuery`\n        * parallelize queries\n        * cache identical queries\n        * batch queries if batching function provided\n* builtin wrappers\n    ```\n    val api = graphQL(...) @@\n      maxDdepth(30) @@\n      maxFields(200) @@\n      timeout(10 seconds) @@\n      printSlowQueries(1 second)\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala-graphql-caliban-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmtumilowicz%2Fscala-graphql-caliban-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmtumilowicz%2Fscala-graphql-caliban-workshop/lists"}