Functions
Overview
In this section, you'll learn how you can migrate the functionality of your serverless functions from the Graphcool Framework to Prisma. This includes hooks, resolvers and server-side subscriptions.
Hooks
Hooks in the Graphcool Framework are used for synchronous data validation and transformation. You can associate a hook with a certain mutation of your GraphQL API. Before the mutation is performed, the Graphcool Function executes the hook function which can either transform the incoming mutation arguments or throw an error if a validation rule is violated.
Assume this datamodel for the following example:
type User {
id: ID! @unique
name: String!
}
Further, assume your GraphQL server exposes the following application schema:
type Query {
users: [User!]!
}
type Mutation {
createUser(name: String!): User!
}
Data validation
The example we'll use for data validation is that we don't want to allow User
nodes where the name
has fewer than two letters.
Graphcool Framework
In a hook function, we'd ensure this constraint by implementing the following function and associating it with the createUser
mutation of the Graphcool Framework's GraphQL API:
event => {
if (event.data.name.length < 2) {
return {
error: `The provided name '${
event.data.name
}' is too short. A name must have at least two letters.`,
}
}
return { data: event.data }
}
Prisma
Now, with Prisma that functionality moves into the application layer, i.e. the implementation of your GraphQL server. More precisely, the check needs to be performed inside the createUser
resolver:
function createUser(parent, { name }, context, info) {
if (name.length < 2) {
throw new Error(`The provided name '${name}' is too short. A name must have at least two letters.`)
}
return context.db.mutation.createUser({ data: { name }, info)
}
Data transformation
For data transformation, the idea is similar. Again, the functionality that was previously implemented in a hook function now moves into your application layer.
As an example for data transformation we're implementing the use case that we only want to store the value for name
fields completely uppercased.
Graphcool Framework
event => {
const uppercaseName = event.data.name.toUppercase()
return { data: uppercaseName }
}
Prisma
As mentioned, Prisma solves the same issue inside the createUser
resolver:
function createUser(parent, { name }, context, info) {
const uppercaseName = name.toUppercase()
return context.db.mutation.createUser({ data: { name: uppercaseName }, info)
}
Resolver Functions
Resolver functions in the Graphcool Framework are used to extend the capabilities of the auto-generated CRUD API. Typical use cases include authentication (e.g. signup
and login
mutations) as well as integrating 3rd party services or wrapping REST APIs.
For the following example, we'll consider the use case of wrapping a REST API (based on the rest-wrapper
example from the Graphcool Framework). We'll use https://dog.ceo/api/breed/${breedName}/images/random
endpoint.
Graphcool Framework
With the Graphcool Framework, there are two parts to a resolver function:
- a schema extension written in SDL
- the resolver implementation in JavaScript
The schema extension needs to extend the Query
type and define a new root field that can be used by clients to submit a query:
type RandomBreedImagePayload {
url: String!
}
extend type Query {
randomBreedImage(breedName: String!): RandomBreedImagePayload!
}
The implementation of the resolver then retrieves the breedName
argument from the incoming event
and makes the call to the mentioned REST endpoint. It also needs to ensure the returned data has the right structure, i.e. it needs to adhere to the defined RandomBreedImagePayload
type.
require('isomorphic-fetch')
module.exports = event => {
const { breedName } = event.data
const url = `https://dog.ceo/api/breed/${breedName}/images/random`
return fetch(url)
.then(response => response.json())
.then(responseData => {
const randomBreedImageData = responseData.message
const randomBreedImage = { url: randomBreedImageData }
return { data: randomBreedImage }
})
}
Prisma
Similar as for hooks, the functionality of Graphcool Framework resolver functions is now implemented in the application layer and thus not directly taken care of by Prisma any more.
The application schema needs to define the corresponding root field:
type RandomBreedImagePayload {
url: String!
}
extend type Query {
randomBreedImage(breedName: String!): RandomBreedImagePayload!
}
The resolver is then simply part of your GraphQL server implementation:
function(parent, { breedName }, context, info) {
const url = `https://dog.ceo/api/breed/${breedName}/images/random`
return fetch(url)
.then(response => response.json())
.then(responseData => {
const randomBreedImageData = responseData.message
const randomBreedImage = { url: randomBreedImageData }
return { data: randomBreedImage }
})
}
Subscriptions
Server-side subscriptions are following the same concept in Prisma. However, with Prisma it is not possible to host the corresponding functions as managed functions anymore - instead they need to be configured through web hooks, pointing to an HTTP endpoint that you deployed yourself (e.g. using AWS Lamba, Google Cloud Functions, Zeit Now, ...).
The following example is based on the subscriptions
from the Graphcool Framework.
Graphcool Framework
To configure a server-side subscription in the Graphcool Framework, you need to provide two components:
- a GraphQL subscription query that defines what event you're subscribing for and what data you'd like to receive when the event is happening
- a handler that will be invoked when the event is happening - this can either be a managed function or a webhook
With the following subscription query, we express that we want to invoke the handler when a new User
node is created. The event payload should carry the id
and name
of the newly created User
.
createFirstArticle.graphql
subscription {
User(filter: { mutation_in: [CREATED] }) {
node {
id
name
}
}
}
We now specify the handler function as a managed function.
createFirstArticle.js
const { fromEvent } = require('graphcool-lib')
module.exports = event => {
// Retrieve payload from event
const { id, name } = event.data.User.node
// Create Graphcool API (based on https://github.com/graphcool/graphql-request)
const graphcool = fromEvent(event)
const api = graphcool.api('simple/v1')
// Create variables for mutation
const title = `My name is ${name}, and this is my first article!`
const variables = { authorId: id, title }
// Create mutation
const createArticleMutation = `
mutation ($title: String!, $authorId: ID!) {
createArticle(title: $title, authorId: $authorId) {
id
}
}
`
// Send mutation with variables
return api.request(createArticleMutation, variables)
}
Notice that the incoming event
has the following structure (adhering to the shape of the subscription query):
{
"data": {
"User": {
"node": {
"id": "cj8wscby6nl7u0133zu7c8a62",
"name": "Sarah"
}
}
}
}
Notice that the managed function is configured in graphcool.yml
, pointing to the subscription function and the implementation of the managed function:
functions:
createFirstArticle:
type: subscription
query: src/createFirstArticle.graphql
handler:
code: src/createFirstArticle.js
Prisma
With Prisma, you still configure the subscription in your service's root configuration file. However, the YAML keys are a bit different and you can only point to the handler as a webhook:
subscriptions:
createFirstArticle:
query: src/createFirstArticle.graphql
webhook: https://bcdeaxokbj.execute-api.eu-west-1.amazonaws.com/dev/createFirstArticle
This example assumes that you have deployed a serverless function to the endpoint https://bcdeaxokbj.execute-api.eu-west-1.amazonaws.com/dev/createFirstArticle
.