In this tutorial, we will learn how to integrate a Nest.js REST API into Hasura as an Action and also set up Event Triggers or Scheduled Trigger handlers using the @golevelup/nestjs-hasura module.
Firstly, let's install the necessary dependencies:
```bash
yarn add @nestjs/graphql graphql @nestjs/common @nestjs/core @nestjs/jwt @nestjs/passport @nestjs/typeorm bcryptjs class-validator dotenv express jsonwebtoken mongoose pg passport passport-jwt reflect-metadata
```
Next, we need to set up the environment variables. Create a `.env` file in your project root directory and add the following content:
```bash
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=mysecretpassword
DB_DATABASE=hasura-nestjs
JWT_SECRET=somesecret
```
Now, let's create a new Nest.js application:
```bash
yarn global add @nestjs/cli
nest new hasura-nestjs
cd hasura-nestjs
```
Next, we need to install the `@golevelup/nestjs-hasura` module and its peer dependencies:
```bash
yarn add @golevelup/nestjs-hasura graphql-tools graphql-typing-definitions graphql-relay-link graphql-subscriptions graphql-ws
```
Now, let's create a new module for handling payments:
```bash
nest generate service payment
```
This will generate the necessary files and folders. Now, we need to set up a fake payment handler that takes a `user_id`, `product_id`, and `quantity`, and then pretends to process a payment as a Hasura Action:
```typescript
// src/payment/payment.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class PaymentService {
private static products = [
{ id: 1, name: 'Milk', price: 2.5 },
{ id: 2, name: 'Apples', price: 1.25 },
{ id: 3, name: 'Eggs', price: 0.99 },
];
public calculateTotal(params: { product_id: number; quantity: number }): number {
return PaymentService.products.find((it) => it.id == params.product_id).price * params.quantity;
}
public processPayment(params: { total: number }): boolean {
console.log(`This is where you'd call a payment processor, and charge the customer for ${params.total}`);
return true;
}
}
```
Next, let's set up the `payment.controller.ts` to take a payload from a Hasura Action and call this service, returning `total`, `paymentResult`, and a fake `receiptNumber`:
```typescript
// src/payment/payment.controller.ts
import { Body, Controller, Post } from '@nestjs/common';
import { PaymentService } from './payment.service';
import { HasuraActionsPayload<Input extends {}, Session extends {}> } from '@golevelup/nestjs-hasura';
interface CreatePaymentForUserArgs {
user_id: number;
product_id: number;
quantity: number;
}
@Controller('payment')
export class PaymentController {
constructor(private readonly paymentService: PaymentService) {}
@Post('/createPaymentForUser')
createPaymentForUser(@Body() payload: HasuraActionsPayload<{ params: CreatePaymentForUserArgs }>>): any {
const total = this.paymentService.calculateTotal(payload.input.params);
const paymentResult = this.paymentService.processPayment({ total });
return {
total,
paymentResult,
receiptNumber: 1234567,
};
}
}
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}
}
```
Then you should see output like this:
```bash
This is where you'd call a payment processor, and charge the customer for 25
```
Now if we boot up our app again, with `yarn start:dev` again, we should see in the output:
```bash
[Nest] 21396 - 08/24/2021, 3:30:42 PM LOG [RouterExplorer] Mapped {/payment/createPaymentForUser, POST} route +1ms
```
And if we try to make a request to that endpoint giving it the same payload it would receive if it were called by Hasura, as a Hasura Action, like this:
```bash
POST http://localhost:3000/payment/createPaymentForUser HTTP/1.1
content-type: application/json
{
"action": {
"name": "createPaymentForUser"
},
"input": {
"params": {
"user_id": 1,
"product_id": 2,
"quantity": 10
}
},
"session_variables": {}