Hooks
Migration Guide

Migration Guide for Function Hooks

If you have a schema that was authored prior to the introduction of the Action and Function Hooks syntax, luckily migrating your schema across to use the new syntax is straightforward.

Start here

You will need to upgrade your Keel CLI to the latest version:

npm install -g keel

A New Schema Keyword

Previously, you'd define operations and functions within separate blocks:

schema.keel
model Post {
  fields {
    title Text
  }
 
  functions {
    get getPost(id)
  }
 
  operations {
    create createPost() with(title)
  }
}

Now both operations and functions are combined into a single actions block:

schema.keel
model Post {
  fields {
    title Text
  }
 
  actions {
    get getPost(id) {
      @function
    }
    create createPost() with(title)
  }
}

You may also notice that the getPost function has been annotated with the @function attribute.

Any time you want to write your own code for an action instead of using the built-in behaviour, apply the @function attribute, and run keel generate to create the corresponding {name}.ts file in your functions/ directory.

Migrating your functions code

With the up to date Keel CLI, you need to re-run Keel's code generator to get your project up to date:

keel generate

This will update any supporting JavaScript dependencies to the latest version, and regenerate Keel's SDK to reflect the hooks changes. After running keel generate, there should be some changes to your package.json and package-lock.json files.

Introducing the Hooks

The new Hooks API introduces four new lifecycle methods that can be used to modify the way functions work. A custom function no longer takes a callback function where you would write your implementation; instead the function receives an object with the following properties that you can configure:

  • beforeWrite: useful for when you want to modify the values due to be written to the database prior to insertion / update.
  • afterWrite: useful for when you want to perform side effects on the data that was successfully inserted/updated. Can be used to run custom permissions checks or create other data.
  • beforeQuery: can be used to extend the default query that will be executed against the database, or alternatively you can replace the query entirely with your own custom query.
  • afterQuery: can be used to run custom permissions checks on the data returned from the database, or to perform other side effects.

You can read more about each hook in the Hooks Documentation.

An example migration

Previously a function would look something like the below:

functions/createPost.ts
import { CreatePost, models } from '@teamkeel/sdk';
 
export default CreatePost(async (ctx, inputs) => {
  const post = await models.post.create({
    ...inputs
  });
 
  return post;
})

Now most of the code above is written for you automatically! We have instead introduced the concept of Hooks, which allow you to "hook in" to the underlying default implementation.

The example function above isn't doing anything special that the default implementation of the function isn't doing already, so it would become:

functions/createPost.ts
import { CreatePost, models } from '@teamkeel/sdk';
 
export default CreatePost();

But what if you wanted to modify some of the request inputs prior to inserting into the database? You could hook in to the beforeWrite hook to do that:

functions/createPost.ts
import { CreatePost, models } from '@teamkeel/sdk';
 
export default CreatePost({
  beforeWrite: async (ctx, inputs) => {
    return {
      ...inputs,
      title: `${inputs.title} (${inputs.subTitle})`
    }
  }
})

FAQs

Q: How can I migrate my function that requires creation of other relationship data (such as a foreign key) prior to insertion?

A: Imagine the scenario where you have two models: Author and Post. A Post needs to be associated with an author in our example, and we want to make it possible to create the Author and Post at the same time. You could use a beforeWrite hook to create the author first, obtain the Author's ID and pass it in when creating the post:

functions/createPostAndAuthor.ts
import { CreatePostAndAuthor, models } from '@teamkeel/sdk';
 
export default CreatePostAndAuthor({
  beforeWrite: async (ctx, inputs) => {
    // the input definition in the schema would include author.name
    const {
      author: { name }
    } = inputs;
 
    const author = await models.author.create({ name });
 
    // return the values to be written to the database
    // including the id of the author we just created
    return {
      title: inputs.title,
      authorId: author.id,
    }
  }
})

Q: How can I migrate my function that needs to create other data in the database after creating my model?

A: You can use the afterWrite hook to perform side effects such as creating related data:

functions/createPost.ts
import { CreatePost, models } from '@teamkeel/sdk';
 
export default CreatePost({
  afterWrite: async (ctx, inputs, post) => {
    // the post parameter is the value retrieved from the database
    // including its ID.
 
    await models.comments.create({
      postId: post.id,
      content: 'I found this really interesting!',
      identityId: ctx.identity.id,
    })
  }
})

Custom functions

If you have any Custom Functions defined in your schema, then no action is required to migrate these, as read and write action types do not leverage the Hooks API.

Console

You won't be able to use some functionality within the Console (opens in a new tab) such as the Schema Visualizer and API Explorer until you have fixed your schemas and deployed the changes.

Getting help

Please read over our Hooks guide, and if you have any questions, don't hesitate to reach out to us for a one-on-one session where we can help you migrate your Keel projects.