agmoss / nestjs-graphql-azure-functions

Serverless GraphQL API with Nest.js and Azure Functions
MIT License
19 stars 2 forks source link
azure-functions graphql nestjs serverless typescript

Nest Logo

Deploying Serverless NestJS GraphQL API to Azure Functions

Getting nest/azure-func-http and nest/graphql to play well together is tricky. There are several GH issues and SO posts on this topic with little in the way of a solution. Additionally, none of the official nest.js docs or samples contain a configuration with both graphql and azure functions.

The one source of reliable information on this topic is a blog post by trilon.io here. This is a good tutorial on creating a Nest.js REST api with nest/azure-func-http. However the tutorial steps to not carry over directly when creating a GraphQl API.

This repo and tutorial is a minimal example of a working integration of nest/azure-func-http and nest/graphql. I hope this helps some folks out!

Starting Point

I started this repo with the boilerplate from 23-type-graphql. This is a working repo with Typescript, GraphQL, and Nest but NOT nest/azure-func-http

Adding azure-func-http

$ nest add @nestjs/azure-func-http

This will install the function app boilerplate in the repo. Here is where this tutorial deviates from the trilion.io tutorial. Several of the default azure function configurations need to be altered along with some of your nest app code.

Steps

1. Change build script in package.json

- "build": "nest build"
+ "build": "rimraf dist && tsc -p tsconfig.build.json"

2. Remove the include from your tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "allowSyntheticDefaultImports": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true
  },
-  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

These two steps create seperate /src and /main directories in /dist.

3. Adjust Nest.js App

At this point the azure function will run but it will not resolve your GraphQL requests! Some changes need to be made to the nest app itself.

main.ts

import { ValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
+ app.enableCors();
+ app.setGlobalPrefix("api");
  await app.listen(3000);
}
bootstrap();

app.module.ts

import { Module } from "@nestjs/common";
import { GraphQLModule } from "@nestjs/graphql";
import { RecipesModule } from "./recipes/recipes.module";

@Module({
  imports: [
    RecipesModule,
    GraphQLModule.forRoot({
      installSubscriptionHandlers: true,
+     context: ({ req }) => ({ req }),
      autoSchemaFile: "schema.gql",
+     useGlobalPrefix: true
    })
  ]
})
export class AppModule {}
  1. Adjust function app config

host.json

{
  "version": "2.0",
+ "extensions": {
+   "http": {
+     "routePrefix": "api"
+   }
  }
}

index.ts

import { Context, HttpRequest } from "@azure/functions";
import { AzureHttpAdapter } from "@nestjs/azure-func-http";
import { createApp } from "../src/main.azure";

export default function(context: Context, req: HttpRequest): void {
+ context.res = {
+   headers: {
+     "Content-Type": "application/json"
+   }
+ };
  AzureHttpAdapter.handle(createApp, context, req);
}

Your GraphQL function app is good to go!!

$ npm run build && func host start

Testing out the app

Add a sample body to the create method in recipies.service.ts for testing.

recipies.service.ts

  async create(data: NewRecipeInput): Promise<Recipe> {
+    return {
+      id: "sample",
+      title: data.title,
+      description: data.description,
+      creationDate: new Date(),
+      ingredients: data.ingredients
+    } as Recipe;
- return {} as any;
  }

fire up http://localhost:7071/api/graphql and run a mutation

mutation($newRecipeData: NewRecipeInput!) {
  addRecipe(newRecipeData: $newRecipeData) {
    creationDate
  }
}

query variables

{
  "newRecipeData": {
    "title": "Salad",
    "description": "Im trying to be healthy and im disappointed in my self",
    "ingredients": ["leaves", "sadness"]
  }
}

you should get back something like....

{
  "data": {
    "addRecipe": {
      "creationDate": 1582340836192
    }
  }
}

Deploying to Azure Functions

The battle has been won but the war has just begun

  1. Create resource in Azure Portal

Navigate to your azure account and create a new Function App. The Create Function App workflow contains five tabs: Basics, Hosting, Monitoring, Tags, and Review + create. Settings in Basics and Hosting need to be adjusted, the other tabs can be left at their defaults.

Basics

Basics

Hosting

Hosting

Upon completing the Basics and Hosting, press Review + create to deploy the function app.

  1. Deploy your code to the Function App

Once created, head back to your editor and deploy your Nest Graphql App to this newly created Function App.

$ npm run build && func azure functionapp publish <APP_NAME>

This will create an output similar to the following:

Deploy

Note: This step requires Azure Functions Core Tools v3 for local development and deployment of Azure Functions. Instructions on how to get this on your development machine can be found here

  1. Use the deployed graphql endpoint

Wait a few minutes for the application to propagate then head to the deployed graphql endpoint: https://<APP_NAME>.azurewebsites.net/api/graphql

You should then be treated to the graphql playground.

Playground

Notes on Deployment

Getting the function to run remotely on azure is not clear cut. I have found that the best configuration options are Function app V3 and WEBSITES_NODE_DEFAULT_VERSION set to ~12

Documentation on how to changes these Azure related setting can be found here

If you are using vscode there is some helpful extensions for deploying function apps. Your can read about that here

Included in this repo is a template.zip. This contains the templated deployment parameters from the Deploying to Azure Functions section of this article.