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
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
.
is for your source code/src
is the entry point for the function app/main
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 {}
- 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
- 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
Hosting
Upon completing the Basics and Hosting, press
Review + create
to deploy the function app.
- 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:
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- 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.
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.