Using the Graphcool Framework, it’s really easy to create functions for validating or manipulating data, extend the base CRUD API or perform some backend logic when certain events happen.
There are 3 possible function types, and each type is suited for a specific use-case:
- Hook functions: These are for synchronous functions that run either right before or right after the database CRUD operation takes place. The most common use-case for a hook function before the operation is for validating the data, and the most common use-case for a hook after the operation would be to manipulate the returned data.
- Subscription functions: These are for running some backend logic when specific events happen in the GraphQL API. For example, when a new User is created, a subscription function could be called to create a customer in Stripe or to add the user to an email onboarding flow. Subscription functions use the power of GraphQL subscriptions to allow for a very familiar and straightforward query syntax.
- Resolver functions: For extending the base CRUD API and also wrap other APIs. For example, a resolver function can be used to wrap a REST API so that GraphQL queries can be run against the REST endpoint. Resolver functions are also used for authentication flows.
For all 3 types of functions, the definition goes into the graphcool.yml service definition file. Here’s for example the definition for the default function that we get when generating a new GraphQL service:
graphcool.yml (partial)
functions: hello: handler: code: src/hello.js type: resolver schema: src/hello.graphql
Here we define a Resolver function called hello, the schema for the API extension in a local .graphql
file and its local handler code is in a hello.js
file.
When specifying a local JavaScript file as the handler code, the function will be fully managed by Graphcool. All 3 types of functions can also call external handler code that’s either self-hosted or on a FaaS service like Zeit Now, Google Cloud Platform or AWS Lambda. When specifying an external function code, the webhook key is used instead. Here for example is the same function definition :
graphcool.yml (partial)
functions: hello: handler: webhook: https://hello-nzgkbwimkt.now.sh type: resolver schema: src/hello.graphql
Now that you have a high-level idea of what’s possible in terms of functions using the Graphcool framework, let’s explore each function type briefly to give you a more complete overview.
Hook Functions
Hook functions are ran synchronously either before (operationBefore) or after (operationAfter) the CRUD database operation. They are used mainly for data validation and manipulation.
Let’s create a simple operationAfter hook function that changes every occurrence of the word bacon to the 🥓 emoji when returning the data from a node of type Post. First, the function definition in the service’s graphcool.yml
file:
graphcool.yml (partial)
functions: baconizer: type: operationAfter operation: Post.create handler: code: ./src/baconizer.js
Our service’s schema is very simple and looks like this:
types.graphql
type Post @model { id: ID! @isUnique title: String! content: String! }
And here’s the code for our function handler:
./src/baconizer.js
export default event => { event.data.title = event.data.title.replace(/bacon/gi, '🥓'); event.data.content = event.data.content.replace(/bacon/gi, '🥓'); return event; };
Notice how we directly mutate the data that’s available from the event. We can now deploy our hook function using graphcool deploy
.
Now, if we run the GraphQL playground and run a mutation like this:
mutation { createPost (title: "More Bacon Please!", content: "I ❤️ bacon!") { title content } }
We’ll get the following response data back:
{ "data": { "createPost": { "title": "More 🥓 Please!", "content": "I ❤️ 🥓!" } } }
The data saved to the database will still be the original with the word bacon spelled out.
Subscription Functions
As an example of a subscription function, let’s send an email using the Mandrill API when a new user is created.
How service’s schema for the User type looks like this:
type User @model { id: ID! @isUnique email: String! name: String! }
Here’s our function definition:
graphcool.yml
functions: emailNotification: type: subscription query: ./src/emailNotification.graphql handler: code: src: ./src/emailNotification.js environment: MANDRILL_API: XXXXXXXXXXXXXXX
As you can see, you can provide environment variables as part of the function definition. For our query, we’ll use a subscription query that looks like this:
./src/emailNotification.graphql
subscription { User(filter: { mutation_in: [CREATED] }) { node { name email } } }
With this subscription query, the handler code will be called each time a new user is created. Our handler code looks like this:
./src/emailNotification.js
const mandrill = require('mandrill-api'); const emailClient = new mandrill.Mandrill(process.env.MANDRILL_API); export default event => { const { name, email } = event.data.User.node; return emailClient.messages.send({ message: { from_email: '[email protected]', from_name: 'Bob', to: [ { email: email, name: name } ], subject: 'Welcome on board!', html: 'We hope you like our app 😄' } }); };
We have access to the node’s name and email because that’s what we’re getting back from our subscription query, and we can access the environment variables using process.env
.
You’ll also need to add the mandrill-api package to your project:
$ npm install mandrill-api # or, using Yarn: $ yarn add mandrill-api
Resolver Functions
Resolver functions are used to extend the Graphcool CRUD API with new Query or Mutation types. In the following example we’ll extend the Query type by wrapping a WordPress REST API endpoint and returning the title and content for the latest posts from a WordPress blog.
First, our function definition in the graphcool.yml
service definition file:
graphcool.yml (partial)
functions: latestPosts: type: resolver schema: ./src/latestPosts.graphql handler: code: ./src/latestPosts.js
Then, our schema extension file, where we extend the Query type:
./src/latestPosts.graphql
type LatestPostsPayload { title: String! content: String! } extend type Query { latestPosts(nbOfPosts: Int!): [LatestPostsPayload!]! }
And then finally, our handler code takes care of calling the WordPress API endpoint and massaging the data to return data in the format that our new Query type expects:
./src/latestPosts.js
require('isomorphic-fetch'); export default event => { const wpEndpoint = `https://some-wordpress-website.com/wp-json/wp/v2/posts?per_page=${ event.data.nbOfPosts }`; return fetch(wpEndpoint) .then(response => response.json()) .then(responseData => { let latestPosts = []; responseData.forEach(post => { latestPosts.push({ title: post.title.rendered, content: post.content.rendered }); }); return { data: latestPosts }; }); };
Notice how we have access to the data passed as argument to the query in the event.data
object.
Before deploying the function, just add the isomorphic-fetch package to your project:
$ npm install isomorphic-fetch # or, using Yarn: $ yarn add isomorphic-fetch
With this Resolver function in place, we can deploy and then run a query like this against our GraphQL API to get the title of the 4 latest posts:
query { latestPosts(nbOfPosts: 4) { title } }