Deploy a GraphQL API as an Azure Function

In this article, learn how to deploy a GraphQL API to Azure in an Azure Function.

This sample demonstrates using the Apollo server in an Azure function to receive a GraphQL query and return the result.

{
    hello
}

The server responds with JSON:

{
    "hello": "Hello from GraphQL backend"
}

Prepare your development environment

Make sure the following are installed on your local developer workstation:

Clone and run the Azure Function GraphQL sample code

  1. Open a terminal window and cd to the directory where you want to clone the sample code.

  2. Clone the sample code repository:

    git clone https://github.com/Azure-Samples/js-e2e-azure-function-graphql-hello.git
    
  3. Open that directory in Visual Studio Code:

    cd js-e2e-azure-function-graphql-hello && code .
    
  4. Install the project dependencies:

    npm install
    
  5. Run the sample:

    npm start
    

Query local Azure Function with GraphQL using GraphQL playground

The npm package apollo-server-azure-functions includes a GraphQL playground that you can use to test your GraphQL API. Use this playground to test the GraphQL API locally.

  1. Open browser to http://localhost:7071/api/graphql

  2. Enter query {hello}

  3. View response {"data":{"hello":"Hello from GraphQL backend"}}

    A browser screenshot showing the GraphQL playground hosted from an Azure Function API

Query Azure Function with GraphQL using cURL

  1. In VS Code, open an integrated terminal.

  2. Enter the cURL command:

    curl 'http://localhost:7071/api/graphql' \
         -H 'content-type: application/json' \
         --data-raw '{"query":"{hello}"}' 
    
  3. View the response {"data":{"hello":"Hello from GraphQL backend"}}

Create your Azure Function resource from VS Code

  1. In VS Code, select the Azure explorer.

  2. In the Azure Functions section, select the Azure subscription to create the Azure Function resource in, then right-click to select Create Function App in Azure.

  3. Complete the prompts:

    Prompt Enter
    Enter a globally unique name for the new function app. Enter a unique name, which is used as the subdomain of the URL, such as YOURALIAS-azure-function-graphql-hello.
    Select a runtime stack. Node.js 14 LTS
    Select a location for new resources. Select a geographic location close to you.

    VS Code notifies you when the deployment completes.

Deploy your GraphQL API from VS Code

  1. In VS Code, still in the Azure explorer, find your new Azure Function resource under your subscription.

  2. Right-click the resource and select Deploy to Function App.

  3. Select output window from the notification to watch the deployment.

    When the deployment completes, continue to the next section.

Query your GraphQL API with cURL

  1. In VS Code, open an integrated terminal.

  2. Change the cURL command from using your local function to your remove function. Change the URL in the following command to use your Azure Function URL:

    curl 'https://diberry-azure-function-graphql-hello.azurewebsites.net/api/graphql' \
         -H 'content-type: application/json' \
         --data-raw '{"query":"{hello}"}' |
    

    The API responds with:

    {"data":{"hello":"Hello from our GraphQL backend!"}}
    

Review the code

The code used in this article requires the npm package apollo-server-azure-functions to resolve your GraphQL query.

The code for this query is in the ./graphql/index.ts file.

import { ApolloServer, gql} from "apollo-server-azure-functions";

// Construct a schema, using GraphQL schema language
const typeDefs = gql`
  type Query {
    hello: String
  }
`;

// Provide resolver functions for your schema fields
const resolvers = {
  Query: {
    hello: () => "Hello from our GraphQL backend!",
  },
};
// @ts-ignore
const server = new ApolloServer({ typeDefs, resolvers, debug: true,playground: true});

export default server.createHandler({
  cors: {
    origin: '*'
  },
});

The highlighted lines are described in the following table:

Line Description
const typeDefs = gql Define the GraphQL schema the API supports.
const resolvers Define the handlers for the GraphQL schema (known as resolvers in GraphQL). hello, from our schema, is given a resolver function to return data from the API: () => "Hello from our GraphQL backend!".
const server = new ApolloServer({ typeDefs, resolvers }); Create an Azure Function version of the Apollo server with the typeDefs, resolvers, and the playground.

Troubleshooting graphql API

Use the following troubleshooting guide to resolve any issues.

Issue Possible fix
cURL command doesn't return anything In VS Code, expand the Azure Function resource in the Azure explorer. Under the Files node, make sure all your local files have been moved to the remote location and the /dist folder has been generated. If the files are not present, redeploy the app and watch the deployment output for any errors. If the files do exist, run the cURL command again, adding --verbose to the end of the command to see what status code is returned.
API doesn't return anything - but the code is correct. The Azure Function returns the Apollo server's results if the ./graphql/function.json correctly states the name of the return binding as $return. If you played with the function.json file, make sure the http binding name is reset to the value of $return. Another possible issue is if you changed authLevel, also found in the function.json file from anonymous to another value, you need to either change the value back to anonymous or correctly pass in the authentication when you use the API.

Did you run into an issue not described in the preceding table? Open an issue to let us know.

Design a second API to allow create, update, and read API endpoints

Consider a new API with the following endpoint to manage messages:

  • /api/mymessages/getMessage
  • /api/mymessages/getMessages
  • /api/mymessages/createMessage
  • /api/mymessages/updateMessage

A single data element has a unique identifer, content, and author data. Internal to the Azure Function, that looks like

{"79e4c338-162d-4c1e-a6f0-320bd78a7817": {
        "author": "dina",
        "content": "good morning"
    }
}

The resolver can reshape the data returned from the API to look like:

[
    {
        "id":"79e4c338-162d-4c1e-a6f0-320bd78a7817",
        "author": "dina",
        "content": "good morning"
    }
]

If the query only requests certain fields such as id and content, the Apollo server prunes the data instead of the resolver:

[
    {
        "id":"79e4c338-162d-4c1e-a6f0-320bd78a7817",
        "content": "good morning"
    }
]

The key is that the resolver can reshape the data and the Apollo server can prune the data to match the query.

Add second GraphQL API to Azure Function

  1. In VS Code, select View, then select the Command Palette.

  2. Search for and select Azure Functions: Create Function.

  3. Complete the prompts:

    Prompt Enter
    Select a template for your function HTTP trigger
    Provide a function name mymessages
    Authorization Anonymous

Add code for new GraphQL API

  1. In the ./mymessages/index.ts, replace the entire file with the following code:

    import { ApolloServer, gql} from "apollo-server-azure-functions";
    import { uuid } from 'uuidv4';
    
    const database = { [uuid()] :{"author": "dina", "content": "good morning"} };
    
    const typeDefs = gql`
        input MessageInput {
            content: String
            author: String
        }
    
        type Message {
            id: ID!
            content: String
            author: String
        }
    
        type Query {
            getMessage(id: ID!): Message,
            getMessages:[Message]
        }
    
        type Mutation {
            createMessage(input: MessageInput): Message
            updateMessage(id: ID!, input: MessageInput): Message
        }
    `;
    
    class Message {
    
        id: any;
        content: string;
        author: string;
    
        constructor(id: String, {content, author}) {
            this.id = id;
            this.content = content;
            this.author = author;
        }
    }
    
    const resolvers = {
        Mutation: {
            createMessage: (_, {input}) => {
                const id = uuid();
    
                database[id] = input;
                return new Message(id, input);
            },
            updateMessage: (_, {id, input}) => {
                if (!database[id]) {
                    throw new Error('no message exists with id ' + id);
                }
                database[id] = input;
                return new Message(id, input);
            },
        },
        Query: {
            getMessage: (_, {id}) => {
                if (!database[id]) {
                    throw new Error('no message exists with id ' + id);
                }
                return new Message(id, database[id]);
            },
            getMessages: (_, ) => {
                let arr = [];
                for (var key in database) {
                    if (database.hasOwnProperty(key)) {
                        arr.push({
                            id: key,
                            author: database[key].author,
                            content: database[key].content
                        });
                    }
                }
                return arr;
            },
        }
    };
    // @ts-ignore
    const server = new ApolloServer({ typeDefs, resolvers, playground: true});
    
    export default server.createHandler({
      cors: {
        origin: '*'
      },
    });
    
  2. In the ./mymessages/function.json file, change the second name object to use $return so that the Apollo server can return functionality correctly through the Azure Function:

    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
    
  3. In the VS Code integrated terminal, build and start the function:

    npm run build && npm start
    
  4. Open the playground in a browser http://localhost:7071/api/mymessages.

  5. Enter and run each of the following GraphQL queries in the playground. The id value you see will be different from the id values listed below because these values are randomly generated.

    Purpose Query Result
    Get all messages {getMessages{id, author, content}} {"data": {"getMessages": [{"id": "d8732ed5-26d8-4975-98a5-8923e320a77f","author": "dina", "content": "good morning"}]}}
    Add one messages mutation{createMessage(input:{author: "John Doe",content: "Oh happy day"}){id}} {"data": {"createMessage": {"id": "33febdf6-e618-4884-ae4d-90827280d2b2"}}}
    Get one message {getMessage(id: "33febdf6-e618-4884-ae4d-90827280d2b2"){id, content, author}} {"data": {"getMessage": {"id": "33febdf6-e618-4884-ae4d-90827280d2b2","content": "Oh happy day", "author": "John Doe"}}}
    Update one message mutation{ updateMessage (id: "33febdf6-e618-4884-ae4d-90827280d2b2",input:{author: "John Doe", content: "another great day..." }){id, content, author}} {"data": {"updateMessage": {"id": "33febdf6-e618-4884-ae4d-90827280d2b2","content": "another great day...","author": "John Doe"}}}
  6. To deploy to your cloud-based Azure Function, repeat the deployment steps.

Next steps