graphql-engine/community/boilerplates/custom-resolvers/README.md

196 lines
4.9 KiB
Markdown
Raw Normal View History

# GraphQL Custom Resolver Example
This is a simple example of using a custom resolver with Hasura's GraphQL API.
## Motivation
Hasura GraphQL Engine provides instant GraphQL APIs over the tables and views of
any Postgres database. It also comes with a fine grained access control layer
that helps you restrict the data that can be consumed.
However, sometimes you might have to write custom resolvers to capture business
logic that is unrelated to the database or needs to execute a custom transaction
or write to the database.
In this example, we illustrate how to write custom resolvers and merge them with
the Hasura GraphQL Engine. We combine Hasura GraphQL Engine's GraphQL API
running at `https://bazookaand.herokuapp.com/v1alpha1/graphql` with the
following custom resolvers:
1. A `hello` query
2. A `count` query (that returns a counter from another data source )
3. A `increment_counter` mutation that increments the value of `count`.
4. A `user_average_age` query that makes directly makes an SQL query to Postgres
using knex.
You can use this as a boilerplate to write custom resolvers with Hasura GraphQL
Engine.
![Custom resolvers with Hasura GraphQL engine](./assets/custom-resolvers-diagram.png)
## Usage
1. Install the required dependencies.
```bash
npm install
```
2. Set appropriate environment variables for the GraphQL Engine URL, the access
key to GraphQL Engine and the Postgres connection string.
```bash
# without the /v1apha1/graphql part
export HASURA_GRAPHQL_ENGINE_URL='https://hge.herokuapp.com'
export X_HASURA_ACCESS_KEY='<access_key>'
# Only required for the direct SQL resolver
export PG_CONNECTION_STRING='<postgres-connection-string>'
```
3. Run the server
```bash
npm start
```
## Deployment
You can deploy this sample boilerplate with:
* Now
* Docker
### Deploy using [Now](https://zeit.co/now)
Run these commands to instantly deploy this boilerplate using Now.
```bash
git clone https://github.com/hasura/graphql-engine
cd community/boilerplates/custom-resolvers
now -e \
HASURA_GRAPHQL_ENGINE_URL='https://hge.herokuapp.com' -e \
X_HASURA_ACCESS_KEY='<access_key>' --npm
```
### Deploy the docker image
This project comes with a [`Dockerfile`](Dockerfile).
## Implementation Details
We will use Apollo's `graphql-tools` library to make a working GraphQL Schema
out of our custom resolvers. Finally, we will merge these resolvers with the
existing Hasura schema so that it can be queried under the same endpoint.
### Writing type definitions
The type definitions are written in standard GraphQL format. We need the
following queries in our custom logic:
```graphql
type Query {
# field hello will return "Hello World" which is a string
hello: String,
# field count will return an Int
count: Int,
# field user_average_age will return a Float
user_average_age: Float
}
type Mutation {
# field "increment_counter" will increment the counter and return type IncrementCounter
increment_counter: IncrementCounter,
# IncrementCounter simply returns the new value of the counter
new_count: Int
}
```
### Writing resolvers
Every resolver is a function that is executed with the following arguments in
the order below:
1. `root`: The root of the current field
2. `args`: The arguments provided in the query
3. `context`: The server context, which also consists of headers
4. `info`: The AST document related to the query made
The resolvers in our case are:
```js
const resolvers = {
// resolvers for queries
Query: {
hello: (root, args, context, info) => {
// return response
return 'Hello world!';
},
count: (root, args, context, info) => {
// return response
return count;
},
user_average_age: async (root, args, context, info) => {
// make SQL query using knex client
const response = await knexClient('user')
.avg('age');
// return response
return response[0].avg;
}
},
// resolvers for mutations
Mutation: {
increment_counter: (root, args, context, info) => {
// return response
return { new_count: ++count };
}
}
};
```
### Making a new schema out of these custom resolvers
Use `makeExecutableSchema()` function from the `graphql-tools` library to make a
schema out of the type definitions and resolvers above.
```js
import { makeExecutableSchema } from 'graphql-tools';
const executableCustomSchema = makeExecutableSchema({
typeDefs,
resolvers,
});
```
### Merging with existing Hasura schema and serving it
Merge these custom resolvers with the Hasura GraphQL Engine by using the
`mergeSchemas()` function from the `graphql-tools` library.
```js
import { mergeSchemas } from 'graphql-tools';
const newSchema = mergeSchemas({
schemas: [
executableCustomSchema,
executableHasuraSchema
]
});
const server = new ApolloServer({
schema: newSchema
});
server.listen().then(({ url }) => {
console.log(`Server running at ${url}`);
});
```
Check [this file](src/index.js) to see how it is done.