Page updated Nov 29, 2023

Add custom business logic (Experimental)

Coming soon: The custom business logic experience in Amplify (Gen 2) is still under development. Expect the API surface to change dramatically during the Gen-2 preview period. If you have any feedback on the desired developer experience, please file a GitHub issue.

The default GraphQL API provides a solid foundation for querying, mutating, and fetching data. Even so, you may need additional customization to tailor it to your app's specific requirements around data handling, response formatting, and more.

In the following sections, we walk through the three steps to create a custom mutation or query:

  1. Define a custom query or mutation
  2. Configure custom business logic handler code
  3. Invoke the custom query or mutation

Step 1 - Define a custom query or mutation

TypeWhen to choose
QueryWhen the request only needs to read data and will not modify any backend data
MutationWhen the request will modify backend data

For every custom query or mutation, you need to set a return type and, optionally, arguments. Use a.query() or a.mutation() to define your custom query or mutation in your amplify/data/resource.ts file:

import { type ClientSchema, a, defineData } from '@aws-amplify/backend'; const schema = a.schema({ // 1. Define your return type as a custom type EchoResponse: a.customType({ content: a.string(), executionDuration: a.float() }), // 2. Define your query with the return type and, optionally, arguments echo: a.query() // arguments that this query accepts .arguments({ content: a.string() }) // return type of the query .returns(a.ref('EchoResponse')) // only allow signed-in users to call this API .authorization([a.allow.private()]) }) export type Schema = ClientSchema<typeof schema>; export const data = defineData({ schema, })
1import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
2
3const schema = a.schema({
4 // 1. Define your return type as a custom type
5 EchoResponse: a.customType({
6 content: a.string(),
7 executionDuration: a.float()
8 }),
9
10 // 2. Define your query with the return type and, optionally, arguments
11 echo: a.query()
12 // arguments that this query accepts
13 .arguments({
14 content: a.string()
15 })
16 // return type of the query
17 .returns(a.ref('EchoResponse'))
18 // only allow signed-in users to call this API
19 .authorization([a.allow.private()])
20})
21
22export type Schema = ClientSchema<typeof schema>;
23
24export const data = defineData({
25 schema,
26})

Step 2 - Configure custom business logic handler code

Coming soon: The following example requires you to use JavaScript. We're working on adding TypeScript support natively for Gen 2's general availability. Follow us on X to keep up with updates to this functionality.

After your query or mutation is defined, you need to author your custom business logic in a Lambda function. In your amplify/data/ folder, create a new folder named after your mutation or query with an index.js file.

amplify/data/echo/index.js

exports.handler = async (event) => { const start = performance.now() return { content: event.arguments.name, executionDuration: performance.now() - start } }
1exports.handler = async (event) => {
2 const start = performance.now()
3 return {
4 content: event.arguments.name,
5 executionDuration: performance.now() - start
6 }
7}

In your amplify/data/resource.ts file, import the function using the experimental Func functionality and then reference the function with your query or mutation using the .function() modifier along with the functions map of defineData.

amplify/data/resource.ts

import { type ClientSchema, a, defineData, Func // Step 1 - Import "Func" to create new functions } from '@aws-amplify/backend'; const schema = a.schema({ EchoResponse: a.customType({ content: a.string(), executionDuration: a.float() }), echo: a.query() .arguments({ content: a.string() }) .returns(a.ref('EchoResponse')) .authorization([a.allow.private()]) // Step 2 - Define a function key ("echoHandler") that will be used // to map this query to the corresponding functions provided to // defineData(...) .function('echoHandler') }) export type Schema = ClientSchema<typeof schema>; export const data = defineData({ schema, // Step 3 - Map the function key to the newly created function functions: { 'echoHandler': Func.fromDir({ codePath: './echo', name: 'echoHandlerFunc', }) } })
1import {
2 type ClientSchema,
3 a,
4 defineData,
5 Func // Step 1 - Import "Func" to create new functions
6} from '@aws-amplify/backend';
7
8const schema = a.schema({
9 EchoResponse: a.customType({
10 content: a.string(),
11 executionDuration: a.float()
12 }),
13
14 echo: a.query()
15 .arguments({ content: a.string() })
16 .returns(a.ref('EchoResponse'))
17 .authorization([a.allow.private()])
18 // Step 2 - Define a function key ("echoHandler") that will be used
19 // to map this query to the corresponding functions provided to
20 // defineData(...)
21 .function('echoHandler')
22})
23
24export type Schema = ClientSchema<typeof schema>;
25
26export const data = defineData({
27 schema,
28 // Step 3 - Map the function key to the newly created function
29 functions: {
30 'echoHandler': Func.fromDir({
31 codePath: './echo',
32 name: 'echoHandlerFunc',
33 })
34 }
35})

Step 3 - Invoke the custom query or mutation

Coming soon: The following example requires you to manually generate GraphQL client code as an escape hatch to invoke the custom query or mutation. Amplify Data is built with GraphQL under the hood. While you're not expected to know GraphQL or have to interface with GraphQL directly when Amplify (Gen 2) becomes generally available, it is helpful to understand the underlying GraphQL concepts that enable Amplify Data's functionality.


For Gen 2's general availability, we plan on integrating this first class into the Data client, so you can call a custom query or mutation with code, such as:

const response = await client.queries.echo({ content: 'hello' })
1const response = await client.queries.echo({ content: 'hello' })

In the preview of Amplify (Gen 2), you must first generate GraphQL client code for your custom queries and mutations and then pass them into the client.graphql(...) operation. In your terminal, run the following command:

npx amplify generate graphql-client-code \ --format graphql-codegen \ --statement-target typescript \ --type-target typescript \ --out ./graphql \ --stack <YOUR_STACK_NAME_HERE> # You can find your stack name in your "npx amplify sandbox" # console logs. It has the following format: # amplify-<app-name>-<user-name>-sandbox-<unique-id>
1npx amplify generate graphql-client-code \
2--format graphql-codegen \
3--statement-target typescript \
4--type-target typescript \
5--out ./graphql \
6--stack <YOUR_STACK_NAME_HERE>
7# You can find your stack name in your "npx amplify sandbox"
8# console logs. It has the following format:
9# amplify-<app-name>-<user-name>-sandbox-<unique-id>

This should create the following set of GraphQL client helper code for you under graphql/:

  • API.ts – All the type definitions for queries, mutations, and subscriptions
  • mutations.ts – The GraphQL mutations from your API
  • queries.ts – The GraphQL queries from your API
  • subscriptions.ts – The GraphQL subscriptions from your API

To call the echo query from step 1, we need to import the echo function from queries.ts and pass it as a query to client.graphql(...).

import { generateClient } from 'aws-amplify/data' import { type Schema } from '@/amplify/data/resource' import * as queries from '@/graphql/queries' const client = generateClient<Schema>() async function callEcho() { const response = await client.graphql({ query: echo, variables: { content: "Echo me!" } }) console.log(response.data.echo?.content) console.log(response.data.echo?.executionDuration) }
1import { generateClient } from 'aws-amplify/data'
2import { type Schema } from '@/amplify/data/resource'
3import * as queries from '@/graphql/queries'
4
5const client = generateClient<Schema>()
6
7async function callEcho() {
8 const response = await client.graphql({
9 query: echo,
10 variables: {
11 content: "Echo me!"
12 }
13 })
14
15 console.log(response.data.echo?.content)
16 console.log(response.data.echo?.executionDuration)
17}