Skip to content

Commit

Permalink
Improve documentation (#2236)
Browse files Browse the repository at this point in the history
* Improve landing page

* WIP

* WIP

* WIP

* Polish
  • Loading branch information
ghostdogpr authored May 18, 2024
1 parent 9f2ca14 commit b18d681
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 252 deletions.
8 changes: 3 additions & 5 deletions vuepress/docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ module.exports = {
},
{
text: 'Scaladoc',
link: 'https://javadoc.io/doc/com.github.ghostdogpr/caliban_2.12/'
link: 'https://javadoc.io/doc/com.github.ghostdogpr/caliban_3/'
},
],
sidebar: {
Expand All @@ -52,16 +52,14 @@ module.exports = {
children: [
'',
'schema',
'server-codegen',
'adapters',
'middleware',
'optimization',
'validation',
'introspection',
'adapters',
'interop',
'federation',
'relay-connections',
'schema-reporting',
'server-codegen',
]
},
{
Expand Down
Binary file removed vuepress/docs/.vuepress/public/altair.png
Binary file not shown.
6 changes: 3 additions & 3 deletions vuepress/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ actionText: Get Started →
actionLink: /docs/
features:
- title: High performance
details: While every public interface is pure and immutable, library internals have been optimized for speed.
details: While all public interfaces are pure and immutable, the library's internals are optimized for speed.
- title: Minimal boilerplate
details: No need to manually define a schema for every type in your API. Let the compiler do the boring work.
details: No need to manually define schemas for every type in your API. Let the compiler handle the tedious work.
- title: Excellent interoperability
details: Out-of-the-box support for major HTTP server libraries, effect types, Json libraries and more.
details: Out-of-the-box support for major HTTP server libraries, effect types, JSON libraries, and more.
---
136 changes: 30 additions & 106 deletions vuepress/docs/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

**Caliban** is a purely functional library for creating GraphQL servers and clients in Scala.

For more details on Caliban Client, see the [dedicated section](client.md). The rest of this page is about the backend part of the library.
For more details on Caliban Client, see the [dedicated section](client.md). The rest of this page focuses on the backend part of the library.

The design principles of Caliban are the following:

- **high performance**: while every public interface is pure and immutable, library internals have been optimized for speed, providing by far the best performance of any Scala GraphQL library.
- **minimal amount of boilerplate**: no need to manually define a schema for every type in your API. Let the compiler do the boring work.
- **excellent interoperability**: out-of-the-box support for major HTTP server libraries ([http4s](https://http4s.org/), [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html), [Pekko HTTP](https://github.com/apache/incubator-pekko-http), [Play](https://www.playframework.com/), [ZIO HTTP](https://github.com/dream11/zio-http)), effect types (Future, [ZIO](https://zio.dev/), [Cats Effect](https://typelevel.org/cats-effect/), [Monix](https://monix.io/)), Json libraries ([Circe](https://circe.github.io/circe/), [Jsoniter](https://github.com/plokhotnyuk/jsoniter-scala), [Play Json](https://github.com/playframework/play-json), [ZIO Json](https://github.com/zio/zio-json)), various integrations ([Apollo Tracing](https://github.com/apollographql/apollo-tracing), [Apollo Federation](https://www.apollographql.com/docs/federation/), [Tapir](https://tapir.softwaremill.com/en/latest/), etc.) and more.
- **high performance**: while all public interfaces are pure and immutable, the library's internals are optimized for speed, offering the best performance of any Scala GraphQL library.
- **minimal amount of boilerplate**: you don't need to manually define schemas for every type in your API. Let the compiler handle the tedious work.
- **excellent interoperability**: out-of-the-box support for major HTTP server libraries ([http4s](https://http4s.org/), [Akka HTTP](https://doc.akka.io/docs/akka-http/current/index.html), [Pekko HTTP](https://github.com/apache/incubator-pekko-http), [Play](https://www.playframework.com/), [ZIO HTTP](https://github.com/dream11/zio-http)), effect types (Future, [ZIO](https://zio.dev/), [Cats Effect](https://typelevel.org/cats-effect/), [Monix](https://monix.io/)), JSON libraries ([Circe](https://circe.github.io/circe/), [Jsoniter](https://github.com/plokhotnyuk/jsoniter-scala), [Play Json](https://github.com/playframework/play-json), [ZIO Json](https://github.com/zio/zio-json)), plus various integrations ([Apollo Tracing](https://github.com/apollographql/apollo-tracing), [Apollo Federation](https://www.apollographql.com/docs/federation/), [Tapir](https://tapir.softwaremill.com/en/latest/), etc.) and more.

## A simple example
## A simple schema

First, add the following dependency to your `build.sbt` file:

```scala
"com.github.ghostdogpr" %% "caliban" % "2.6.0"
```

Creating a GraphQL API with Caliban is as simple as creating a case class. Indeed, the whole GraphQL schema will be derived from a case class structure (its fields and the other types it references), and the resolver is just an instance of that case class.
Creating a GraphQL API with Caliban is as simple as creating a case class in Scala.
Indeed, the whole GraphQL schema will be derived from a case class hierarchy (its fields and the other types it references), and the resolver is just an instance of that case class.

Let's say we have a class `Character` and 2 functions: `getCharacters` and `getCharacter`:

Expand All @@ -29,7 +30,8 @@ def getCharacters: List[Character] = Nil
def getCharacter(name: String): Option[Character] = ???
```

Let's create a case class named `Queries` that will represent our API, with 2 fields named and modeled after the functions we want to expose (a _record of functions_). We then create a value of this class that calls our actual functions. This is our resolver.
Let's create a case class named `Queries` that will represent our API, with 2 fields named and modeled after the functions we want to expose.
We then create a value of this class that calls our actual functions. This is our resolver.

```scala mdoc:silent
// schema
Expand All @@ -40,8 +42,10 @@ case class Queries(characters: List[Character],
val queries = Queries(getCharacters, args => getCharacter(args.name))
```

The next step is creating our GraphQL API definition. First, we wrap our query resolver inside a `RootResolver`, the root object that contains queries, mutations and subscriptions. Only queries are mandatory.
Then we can call the `graphQL` function which will turn our simple resolver value into a GraphQL API definition.
The next step is creating our GraphQL API definition.

First, we wrap our query resolver inside a `RootResolver`, the root object that contains queries, mutations and subscriptions. Only queries are mandatory.
Then we call the `graphQL` function which will turn our simple resolver value into a GraphQL API definition.
The whole schema will be derived at compile time, meaning that if it compiles, it will be able to serve it.

```scala mdoc:silent
Expand All @@ -66,107 +70,24 @@ type Queries {
}
```

In order to process requests, you need to turn your API into an interpreter, which can be done easily by calling `.interpreter`.
An interpreter is a light wrapper around the API definition that allows plugging in some middleware and possibly modifying the environment and error types (see [Middleware](middleware.md) for more info).
Creating the interpreter may fail with a `ValidationError` if some type is found invalid.

```scala mdoc:silent
for {
interpreter <- api.interpreter
} yield interpreter
```

Now you can call `interpreter.execute` with a given GraphQL query, and you will get an `ZIO[R, Nothing, GraphQLResponse[CalibanError]]` as a response, with `GraphQLResponse` defined as follows:

```scala
case class GraphQLResponse[+E](data: ResponseValue, errors: List[E])
```

Use `ResponseValue#toString` to get the JSON representation of the result.

```scala mdoc:silent
val query = """
{
characters {
name
}
}"""

for {
interpreter <- api.interpreter
result <- interpreter.execute(query)
_ <- zio.ZIO.debug(result.data.toString)
} yield ()
```

A `CalibanError` can be:

- a `ParsingError`: the query has invalid syntax
- a `ValidationError`: the query was parsed but does not match the schema
- an `ExecutionError`: an error happened while executing the query

Caliban itself is not tied to any web framework, you are free to expose this function using the protocol and library of your choice.
The [caliban-http4s](https://github.com/ghostdogpr/caliban/tree/series/2.x/adapters/http4s) module provides an `Http4sAdapter` that exposes an interpreter over HTTP and WebSocket using http4s. There are also similar adapters for Akka HTTP, Pekko HTTP, Play and zio-http.
Read more on the [adapters' documentation](adapters.md).

::: tip Combining GraphQL APIs
You don't have to define all your root fields into a single case class: you can use smaller case classes and combine `GraphQL` objects using the `|+|` operator.

```scala
val api1 = graphQL(...)
val api2 = graphQL(...)

val api = api1 |+| api2
```

You can use `.rename` to change the names of the generated root types.
:::

## Mutations

Creating mutations is the same as queries, except you pass them as the second argument to `RootResolver`:

```scala mdoc:nest:silent
import zio.Task

case class CharacterArgs(name: String)
case class Mutations(deleteCharacter: CharacterArgs => Task[Boolean])
val mutations = Mutations(_ => ???)
val api = graphQL(RootResolver(queries, mutations))
```

## Subscriptions

Similarly, subscriptions are passed as the third argument to `RootResolver`:

```scala mdoc:compile-only
import zio.stream.ZStream

case class Subscriptions(deletedCharacter: ZStream[Any, Nothing, Character])
val subscriptions = Subscriptions(???)
val api = graphQL(RootResolver(queries, mutations, subscriptions))
```

All the fields of the subscription root case class MUST return `ZStream` or `? => ZStream` objects. When a subscription request is received, an output stream of `ResponseValue` (a `StreamValue`) will be returned wrapped inside an `ObjectValue`.

## Serving over HTTP
Once you have your API object, you can turn it into an interpreter (`api.interpreter`) and start processing requests (`interpreter.execute`).
The interpreter is not tied any web framework, so you are free to expose this function using the protocol and library of your choice.

The easiest (and most performant!) way to expose your API over HTTP is to use the optional `caliban-quick` module:
The easiest (and most performant!) way to expose your API over HTTP is to use the optional `caliban-quick` module based on [zio-http](https://github.com/zio/zio-http).

```scala
"com.github.ghostdogpr" %% "caliban-quick" % "2.6.0"
```

And then you can serve your GraphQL API over HTTP using a single command:
You can then serve your GraphQL API over HTTP using a single command:

```scala mdoc:compile-only
import caliban._
import caliban.quick._ // Adds syntax to `GraphQL`
import caliban.quick._ // adds extension methods to `api`

api.runServer(
api.unsafe.runServer(
port = 8080,
apiPath = "/api/graphql",
graphiqlPath = Some("/graphiql")
)
```

Expand All @@ -179,22 +100,25 @@ If you have any specific server requirements or need to interop with other libra
```scala
"com.github.ghostdogpr" %% "caliban-http4s" % "2.6.0" // routes for http4s
"com.github.ghostdogpr" %% "caliban-akka-http" % "2.6.0" // routes for akka-http
"com.github.ghostdogpr" %% "caliban-pekko-http" % "2.6.0" // routes for pekko-http
"com.github.ghostdogpr" %% "caliban-play" % "2.6.0" // routes for play
"com.github.ghostdogpr" %% "caliban-zio-http" % "2.6.0" // routes for zio-http
"com.github.ghostdogpr" %% "caliban-cats" % "2.6.0" // interop with cats effect

"com.github.ghostdogpr" %% "caliban-cats" % "2.6.0" // interop with cats-effect
"com.github.ghostdogpr" %% "caliban-monix" % "2.6.0" // interop with monix
"com.github.ghostdogpr" %% "caliban-tapir" % "2.6.0" // interop with tapir
"com.github.ghostdogpr" %% "caliban-federation" % "2.6.0" // interop with apollo federation
"com.github.ghostdogpr" %% "caliban-tracing" % "2.6.0" // interop with zio-telemetry

"com.github.ghostdogpr" %% "caliban-federation" % "2.6.0" // apollo federation
"com.github.ghostdogpr" %% "caliban-reporting" % "2.6.0" // apollo schema reporting
"com.github.ghostdogpr" %% "caliban-tracing" % "2.6.0" // open-telemetry
```

Support for JSON encoding / decoding of the inputs and responses for tapir-based adapters is enabled by adding **one** of the following dependencies to your `build.sbt` file:

```scala
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.11" // Circe
"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.11" // Jsoniter
"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.11" // Play JSON
"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.11" // ZIO JSON
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.2.11" // circe
"com.softwaremill.sttp.tapir" %% "tapir-jsoniter-scala" % "1.2.11" // jsoniter
"com.softwaremill.sttp.tapir" %% "tapir-json-play" % "1.2.11" // play-json
"com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.2.11" // zio-json
```

And then later in your code (you only need one!):
Expand Down
8 changes: 4 additions & 4 deletions vuepress/docs/docs/adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ Under the hood, adapters use the [tapir](https://tapir.softwaremill.com/en/lates

The following adapters are provided:
- `Http4sAdapter` exposes a route for http4s.
- `ZHttpAdapter` exposes a route for zio-http. This one doesn't support uploads yet.
- `QuickAdapter` exposes a route for zio-http.
- `PlayHttpAdapter` exposes a route for play.
- `AkkaHttpAdapter` exposes a route for akka.
- `PekkoHttpAdapter` exposes a route for pekko.

To use them, you first need to transform your `GraphQLInterpreter` into a new type of interpreter that supports the protocol you want to use.
To use them, you first need to transform your `GraphQLInterpreter` obtained from `api.interpreter` into a new type of interpreter that supports the protocol you want to use.
There are 3 of them:
- `HttpInterpreter` follows the [standard GraphQL protocol](https://graphql.org/learn/serving-over-http/#http-methods-headers-and-body)
- `HttpUploadInterpreter` follows the [GraphQL multipart request protocol](https://github.com/jaydenseric/graphql-multipart-request-spec)
Expand Down Expand Up @@ -138,11 +138,11 @@ import caliban.quick._

val api: GraphQL[Any] = ???

api.runServer(
api.unsafe.runServer(
port = 8080,
apiPath = "/api/graphql",
graphiqlPath = Some("/graphiql"),
uploadPath = Some("/upload/graphql"), // Optional, for enabling GraphQL uploads
uploadPath = Some("/upload/graphql"), // optional, for enabling GraphQL uploads
)
```

Expand Down
1 change: 1 addition & 0 deletions vuepress/docs/docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ The [examples](https://github.com/ghostdogpr/caliban/tree/series/2.x/examples/)
- [GraphQL API exposed with the high-performance HTTP adapter](https://github.com/ghostdogpr/caliban/tree/series/2.x/examples/src/main/scala/example/quick)
- [GraphQL API exposed with http4s](https://github.com/ghostdogpr/caliban/tree/series/2.x/examples/src/main/scala/example/http4s)
- [GraphQL API exposed with Akka HTTP](https://github.com/ghostdogpr/caliban/tree/series/2.x/examples/src/main/scala/example/akkahttp)
- [GraphQL API exposed with Pekko HTTP](https://github.com/ghostdogpr/caliban/tree/series/2.x/examples/src/main/scala/example/pekkohttp)
- [GraphQL API exposed with play](https://github.com/ghostdogpr/caliban/tree/series/2.x/examples/src/main/scala/example/play)
- [GraphQL API exposed with play's route file](https://github.com/rlavolee/caliban-play-with-route-file)
- [GraphQL API exposed with zio-http](https://github.com/ghostdogpr/caliban/tree/series/2.x/examples/src/main/scala/example/ziohttp)
Expand Down
2 changes: 1 addition & 1 deletion vuepress/docs/docs/interop.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Interop
# Interop with other libraries

If you prefer using [Cats Effect](https://github.com/typelevel/cats-effect) or [Monix](https://github.com/monix/monix) rather than ZIO, you can use the respective `caliban-cats` and `caliban-monix` modules.

Expand Down
9 changes: 0 additions & 9 deletions vuepress/docs/docs/introspection.md

This file was deleted.

26 changes: 13 additions & 13 deletions vuepress/docs/docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,22 +150,22 @@ Here is such an example that is part of the `federation` package which makes a s
a federated graph:

```scala
def federate[R](original: GraphQL[R]): GraphQL[R] = {
import Schema._
def federate[R](original: GraphQL[R]): GraphQL[R] = {
import Schema._

case class Query(
_service: _Service,
_fieldSet: FieldSet = FieldSet("")
)
case class Query(
_service: _Service,
_fieldSet: FieldSet = FieldSet("")
)

graphQL(RootResolver(Query(_service = _Service(original.render))), federationDirectives) |+| original
}
graphQL(RootResolver(Query(_service = _Service(original.render))), federationDirectives) |+| original
}

lazy val federated: GraphQLAspect[Nothing, Any] =
new GraphQLAspect[Nothing, Any] {
def apply[R1](original: GraphQL[R1]): GraphQL[R1] =
federate(original)
}
lazy val federated: GraphQLAspect[Nothing, Any] =
new GraphQLAspect[Nothing, Any] {
def apply[R1](original: GraphQL[R1]): GraphQL[R1] =
federate(original)
}
```

## Cost Estimation
Expand Down
2 changes: 1 addition & 1 deletion vuepress/docs/docs/schema-reporting.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Schema Reporting

The `caliban-reporting` module allows you to integrate with Apollo's [schema reporting protocol](https://www.apollographql.com/docs/studio/schema/schema-reporting-protocol/).
The `caliban-reporting` module allows you to integrate with Apollo's [schema reporting protocol](https://github.com/apollographql/apollo-schema-reporting-preview-docs/blob/master/schema-reporting-protocol.md).
This enables your servers to automatically publish updated schemas on start up without involving any additional tooling.

You can enable the settings by providing the `ReportingDaemon` to your `Runtime` during setup.
Expand Down
Loading

0 comments on commit b18d681

Please sign in to comment.