# Orbit GraphQL

A cache server for your GraphQL API so you're ready for unexpected load

**What?**

1. Deploy it in front of GraphQL API, and it will start caching all requests passing through it.
2. Your cache gets automatically invalidated if anything changes in your application
3. Queries are only sent to origin if there is a cache MISS

**Wait, why is it needed?**

Because all GraphQL requests are on a single endpoint (usually `POST`) and resources are differentiated based on what your request body looks like, we can't use HTTP caching methods for them (say goodbye to etags, 304s, cache-control etc.)

Currently the ecosystem solves for the problem in two ways, client side cache or a server side cache.

Clients like [urql](https://github.com/urql-graphql/urql) can cache your API responses on the client side. Services like [Stellate](https://stellate.co/) act as a cache proxy that does the same thing on the server side.

Orbit GraphQL is an open-source alternative to a tool like Stellate (server side cache for your GraphQL API).

{% hint style="warning" %}
This project is not ready for production traffic yet, but hopefully it will be soon (if you [help](https://github.com/nshntarora/orbitgraphql))
{% endhint %}


# Quickstart

Get it running locally on your machine and see how it works!

### Prerequisites

To build the project locally, you need to have `Go` installed on your machine - [*Installing Go*](https://go.dev/doc/install)

### Running

Once you have the project cloned,

1. Run the following command in the project directory to install/download all dependencies

```
go mod tidy
```

2. Update the configuration in the `config.toml` file (add your origin URL, the port, etc.)
3. Run the command below to start the cache server

```
go run main.go
```

4. That's it! You can start making requests to your cache server.

### Building

You can build the executable for the server by running the command below

```
go build -o orbitgraphql main.go
```

This will build the binary on your machine.

Please note, the build binary will need the `config.toml` file in the same directory if you're providing configuration in the file. You can also provide configuration for the server using envionment variables.

### Docker

First thing you need to do is run the server. You can run it using Docker.

#### **Docker Build**

```sh
docker build . -t orbitgraphql
```

#### **Running the Docker container**

```sh
docker run -p 9090:9090 -e ORBIT_ORIGIN=http://localhost:8080/graphql orbitgraphql
```

All requests to `localhost:9090/` will be proxied to `localhost:8080/graphql` and requests which result in a cache `HIT` will be served directly.


# How does it work?

The cache server caches based on the `__typename` and a primary key in your response objects. `__typename` is an internal field in every GraphQL type, and I assume every unique object in your GraphQL will have a primary key that you will sending to your frontend.

When a request hits the Orbit GraphQL server, it converts it into an AST (abstract syntax tree), then appends the `__typename` field to every parent field and the query.

Using the `__typename` and `id` (*primary key - configurable*) fields in your response it builds a cache for the returned objects.

On subsequent requests, if the same `query` is made again with the same `variables`, and the result exists in the cache, Orbit builds the response object itself and sends that to your client.

For every `mutation` that hits the Orbit server, it forwards the request to the origin to make the mutation, and then checks the `__typename` and `id` fields returned by the mutation. Based on the response that is received, we know which object was updated and use it to invalidate the cache accordingly.

You can also invalidate the cache manually using the cache purging APIs.

This is not production ready yet.

Here is a non-exhaustive list of things planned for the project:

1. Support for Fragments.
2. Benchmarking.
3. Go/JavaScript clients for the administration APIs (used to flush cache).
4. Better observability setup (to help monitor how the cache server is performing).
5. Support for analytics on top of your GraphQL API to help you get insights on how your API is being consumed.

Currently, there's no plan to offer a hosted version for this (I'm scratching my own itch but, never say never)


# Configuration Options

A list of things you can configure for the cache server

You can configure the origin URL, the port the server runs on, the cache backend to use (currently in memory and redis are supported), and a few more things.

The system takes configuration in two ways,

1. The `config.toml` file in your project directory
2. Environment variables

The configuration is read from the `config.toml` file. You can also override the configuration using environment variables.

### Origin

The endpoint to which the cache should forward requests.

* **Configuration Key:** `origin`
* **Environment Variable:** `ORBIT_ORIGIN`
* **Default Value:** None. **Required**

### Port

The port that the cache will run on.

* **Configuration Key:** `port`
* **Environment Variable:** `ORBIT_PORT`
* **Default Value:** `9090`

### Cache Backend

The backend for caching values. Supported values are `redis` and `in_memory`. If you have cache backend configured as `redis` you will also need to provide Redis Host and Redis Port

* **Configuration Key:** `cache_backend`
* **Environment Variable:** `ORBIT_CACHE_BACKEND`
* **Default Value:** `"in_memory"`

### Redis Host

The host for the Redis cache backend.

* **Configuration Key:** `redis_host`
* **Environment Variable:** `ORBIT_REDIS_HOST`
* **Default Value:** `"localhost"`

### Redis Port

The port for the Redis cache backend.

* **Configuration Key:** `redis_port`
* **Environment Variable:** `ORBIT_REDIS_PORT`
* **Default Value:** `6379`

### Cache Header Name

The header name that returns cache status (`HIT`, `MISS`, or `BYPASS`).

* **Configuration Key:** `cache_header_name`
* **Environment Variable:** `ORBIT_CACHE_HEADER_NAME`
* **Default Value:** `"X-Orbit-Cache"`

### Cache TTL

The TTL (Time To Live) of the GraphQL cache in seconds. Default is 60 minutes.

* **Configuration Key:** `cache_ttl`
* **Environment Variable:** `ORBIT_CACHE_TTL`
* **Default Value:** `3600`

### Scope Headers

Headers used to scope the cache based on their unique values. To pass muliple headers add them as a comma separated string (example: `Authorization,X-API-Key`)

* **Configuration Key:** `scope_headers`
* **Environment Variable:** `ORBIT_SCOPE_HEADERS`
* **Default Value:** `"Authorization"`

### Primary Key Field

The field in GraphQL responses used to identify unique objects (this should be unique for every resource). Defaults to `id`.

* **Configuration Key:** `primary_key_field`
* **Environment Variable:** `ORBIT_PRIMARY_KEY_FIELD`
* **Default Value:** `"id"`

### Handlers GraphQL Path

The API path for GraphQL requests.

* **Configuration Key:** `handlers_graphql_path`
* **Environment Variable:** `ORBIT_HANDLERS_GRAPHQL_PATH`
* **Default Value:** `"/graphql"`

### Handlers Flush All Path

The API path to flush all cache.

* **Configuration Key:** `handlers_flush_all_path`
* **Environment Variable:** `ORBIT_HANDLERS_FLUSH_ALL_PATH`
* **Default Value:** `"/flush"`

### Handlers Flush By Type Path

The API path to flush cache by type.

* **Configuration Key:** `handlers_flush_by_type_path`
* **Environment Variable:** `ORBIT_HANDLERS_FLUSH_BY_TYPE_PATH`
* **Default Value:** `"/flush.type"`

### Handlers Debug Path

The API path for debugging.

* **Configuration Key:** `handlers_debug_path`
* **Environment Variable:** `ORBIT_HANDLERS_DEBUG_PATH`
* **Default Value:** `"/debug"`

### Handlers Health Path

The API path for health checks.

* **Configuration Key:** `handlers_health_path`
* **Environment Variable:** `ORBIT_HANDLERS_HEALTH_PATH`
* **Default Value:** `"/health"`

### Log Level

The level of logging. Supported values are `debug`, `info`, `warn`, `error`. Defaults to `info`.

* **Configuration Key:** `log_level`
* **Environment Variable:** `ORBIT_LOG_LEVEL`
* **Default Value:** `"info"`

### Log Format

The format for system logs. Supported values are `json` and `text`. Defaults to `text`.

* **Configuration Key:** `log_format`
* **Environment Variable:** `ORBIT_LOG_FORMAT`
* **Default Value:** `"text"`


# API reference


# GraphQL Endpoint

{% openapi src="/files/PzPSUHKKBVrnfAyrgfCr" path="/graphql" method="post" %}
[openapi.yml](https://2177719965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FngDw7dUBYbbj7x1Ez1mj%2Fuploads%2Flj2tnQG4cDjd7eBPAMpL%2Fopenapi.yml?alt=media)
{% endopenapi %}


# Cache Purge


# Flush Everything

{% openapi src="/files/PzPSUHKKBVrnfAyrgfCr" path="/flush" method="post" %}
[openapi.yml](https://2177719965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FngDw7dUBYbbj7x1Ez1mj%2Fuploads%2Flj2tnQG4cDjd7eBPAMpL%2Fopenapi.yml?alt=media)
{% endopenapi %}


# Flush a Resource

{% openapi src="/files/PzPSUHKKBVrnfAyrgfCr" path="/flush.type" method="post" %}
[openapi.yml](https://2177719965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FngDw7dUBYbbj7x1Ez1mj%2Fuploads%2Flj2tnQG4cDjd7eBPAMpL%2Fopenapi.yml?alt=media)
{% endopenapi %}


# Get Cache Data

{% openapi src="/files/PzPSUHKKBVrnfAyrgfCr" path="/debug" method="get" %}
[openapi.yml](https://2177719965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FngDw7dUBYbbj7x1Ez1mj%2Fuploads%2Flj2tnQG4cDjd7eBPAMpL%2Fopenapi.yml?alt=media)
{% endopenapi %}


# Healthcheck

{% openapi src="/files/PzPSUHKKBVrnfAyrgfCr" path="/health" method="get" %}
[openapi.yml](https://2177719965-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FngDw7dUBYbbj7x1Ez1mj%2Fuploads%2Flj2tnQG4cDjd7eBPAMpL%2Fopenapi.yml?alt=media)
{% endopenapi %}


