Home / Companies / Hasura / Blog / Post Details
Content Deep Dive

Build Fullstack Apps with NestJS, Hasura, and GraphQL APIs

Blog post from Hasura

Post Details
Company
Date Published
Author
Gavin Ray
Word Count
4,104
Language
English
Hacker News Points
-
Summary

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": {}