Creating Actions
Introduction
An action is a GraphQL query or mutation. You have to define the GraphQL type of the arguments that the query or mutation accepts and the GraphQL type of its response.
To create an action, you have to:
- Define the query or mutation
- Define the required types
- Create a handler
Let's look at examples for mutation and query type actions.
Setup
- Console
- CLI
- API
There is no setup required for defining actions via the console.
Install or update to the latest version of Hasura CLI.
You can either get started with an existing project or create a new project.
For a new project
hasura init
This will create a new project. You can set up your GraphQL engine endpoint (and admin secret if it exists) in the
config.yaml
.
Run hasura metadata export
so that you get server's metadata into the metadata/
directory.
For existing projects
Actions are supported only in the v2 config of the CLI. Check the config.yaml
of your Hasura project for the version
key.
If you see version: 1
(or do not see a version key), upgrade to version 2 by running:
hasura scripts update-config-v2
Run hasura metadata export
so that you get server's metadata into the metadata/
directory.
There is no setup required for defining actions via the actions metadata API.
Mutation type action
Let's start with a mutation that accepts a username and password, and returns an access token. We'll call this mutation
login
.
Step 1: Define your mutation and associated types
Start with defining the mutation and the required types. These types will reflect in the GraphQL schema.
- Console
- CLI
- API
Go to the Actions
tab on the console and click on Create
. This will take you to a page like this:
Define the action as follows in the Action Definition
editor.
type Mutation {
login(username: String!, password: String!): LoginResponse
}
In the above action, we called the returning object type to be LoginResponse
. Define it in the New types definition
as:
type LoginResponse {
accessToken: String!
}
To create an action, run
hasura actions create login
This will open up an editor with metadata/actions.graphql
. You can enter the action's mutation definition and the
required types in this file. For your login
mutation, replace the content of this file with the following and save:
type Mutation {
login(username: String!, password: String!): LoginResponse
}
type LoginResponse {
accessToken: String!
}
It is essential that the custom types used in the action are defined beforehand via the set_custom_types metadata API:
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "set_custom_types",
"args": {
"scalars": [],
"enums": [],
"input_objects": [],
"objects": [
{
"name": "LoginResponse",
"fields": [
{
"name": "accessToken",
"type": "String!"
}
]
}
]
}
}
Once the custom types are defined, we can create an action via the create_action metadata API:
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "create_action",
"args": {
"name": "Login",
"definition": {
"kind": "synchronous",
"type": "mutation",
"arguments": [
{
"name": "username",
"type": "String!"
},
{
"name": "password",
"type": "String!"
}
],
"output_type": "LoginResponse",
"handler": "https://hasura-actions-demo.glitch.me/login"
}
}
}
The above definition means:
- This action will be available in your GraphQL schema as a mutation called
login
. - It accepts two arguments called
username
andpassword
of typeString!
. - It returns an output type called
LoginResponse
. LoginResponse
is a simple object type with a field calledaccessToken
of typeString!
.
Step 2: Create the action handler
A handler is an HTTP webhook where you can perform the custom logic for the action.
In this case, we will just return an access token, but typically you would want to run all the business logic that the action demands. NodeJS/Express code for this handler would look something like:
const handler = (req, resp) => {
// You can access their arguments input at req.body.input
const { username, password } = req.body.input;
// perform your custom business logic
// check if the username and password are valid and login the user
// return the response
return resp.json({
accessToken: 'Ew8jkGCNDGAo7p35RV72e0Lk3RGJoJKB',
});
};
You can deploy this code somewhere and get the URI. For getting started quickly, we also have this handler ready at
https://hasura-actions-demo.glitch.me/login
.
Set the handler
Now, set the handler for the action:
- Console
- CLI
- API
Set the value of the handler
field to the above endpoint.
Go to metadata/actions.yaml
. You must see a handler like http://localhost:3000
or http://host.docker.internal:3000
under the action named login
. This is a default value taken from config.yaml
. Update the handler
to the above
endpoint.
The action handler must be set when creating an action via the create_action metadata API.
It can be updated later by using the update_action metadata API.
To manage handler endpoints across environments it is possible to template the endpoints using ENV variables.
e.g. https://my-handler-endpoint/addNumbers
can be templated to {{ACTION_BASE_ENDPOINT}}/addNumbers
where
ACTION_BASE_ENDPOINT
is an ENV variable whose value is set to https://my-handler-endpoint
If you are running Hasura using Docker, ensure that the Hasura Docker container can reach the handler endpoint. See this page for Docker networking.
Step 3: Finish action creation
Finally, to save the action:
- Console
- CLI
- API
Hit Create
.
Run hasura metadata apply
.
An action will be created when sending a request to the create_action metadata API.
Step 4: Try it out
In the Hasura console, head to the API
tab and try out the new action.
And that's it. You have extended your Hasura schema with a new mutation.
Query type action
Let's start with a basic query that accepts a list of numbers and returns their sum. We'll call this query addNumbers
.
Step 1: Define your query and associated types
Start with defining the query and the required types. These types will reflect in the GraphQL schema.
- Console
- CLI
- API
Go to the Actions
tab on the console and click on Create
. This will take you to a page like this:
Define the action as follows in the Action Definition
editor.
type Query {
addNumbers(numbers: [Int]): AddResult
}
In the above action, we called the returning object type to be AddResult
. Define it in the New types definition
as:
type AddResult {
sum: Int
}
To create an action, run
hasura actions create addNumbers
This will open up an editor with metadata/actions.graphql
. You can enter the action's query definition and the
required types in this file. For your addNumbers
query, replace the content of this file with the following and save:
type Query {
addNumbers(numbers: [Int]): AddResult
}
type AddResult {
sum: Int
}
It is essential that the custom types used in the action are defined beforehand via the set_custom_types metadata API:
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "set_custom_types",
"args": {
"scalars": [],
"enums": [],
"input_objects": [],
"objects": [
{
"name": "AddResult",
"fields": [
{
"name": "sum",
"type": "Int!"
}
]
}
]
}
}
Once the custom types are defined, we can create an action via the create_action metadata API:
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type":"create_action",
"args": {
"name":"addNumbers",
"definition": {
"kind":"synchronous",
"type": "query",
"arguments":[
{
"name":"numbers",
"type":"[Int]!"
}
],
"output_type":"AddResult",
"handler":"https://hasura-actions-demo.glitch.me/addNumbers"
}
}
}
The above definition means:
- This action will be available in your GraphQL schema as a query called
addNumbers
- It accepts an argument called
numbers
which is a list of integers. - It returns an output type called
AddResult
. AddResult
is a simple object type with a field calledsum
of type integer.
You can also use Type Generator feature to generate the types for your action:
Insert JSON samples for the request and the response, click "Insert Types" and Hasura generates the GraphQL types for you.
Step 2: Create the action handler
A handler is an HTTP webhook where you can perform the custom logic for the action.
In this case, it is the addition of the numbers. NodeJS/Express code for this handler would look something like:
const handler = (req, resp) => {
// You can access their arguments input at req.body.input
const { numbers } = req.body.input;
// perform your custom business logic
// return an error or response
try {
return resp.json({
sum: numbers.reduce((s, n) => s + n, 0),
});
} catch (e) {
console.error(e);
return resp.status(500).json({
message: 'unexpected',
});
}
};
You can deploy this code somewhere and get the URI. For getting started quickly, we also have this handler ready at
https://hasura-actions-demo.glitch.me/addNumbers
.
Set the handler
Now, set the handler for the action:
- Console
- CLI
- API
Set the value of the handler
field to the above endpoint.
Go to metadata/actions.yaml
. You must see a handler like http://localhost:3000
or http://host.docker.internal:3000
under the action named addNumbers
. This is a default value taken from config.yaml
.
Update the handler
to the above endpoint.
The action handler must be set when creating an action via the Once the custom types are defined, we can create an action via the create_action metadata API.
It can be updated later by using the update_action metadata API.
To manage handler endpoints across environments it is possible to template the endpoints using ENV variables.
e.g. https://my-handler-endpoint/addNumbers
can be templated to {{ACTION_BASE_ENDPOINT}}/addNumbers
where
ACTION_BASE_ENDPOINT
is an ENV variable whose value is set to https://my-handler-endpoint
Step 3: Finish action creation
Finally, to save the action:
- Console
- CLI
- API
Hit Create
.
Run hasura metadata apply
.
An action will be created when sending a request to the create_action metadata API.
Step 4: Try it out
In the Hasura console, head to the API
tab and try out the new action.
And that's it. You have extended your Hasura schema with a new query.
Introduction to Hasura Actions - View Recording.